891. Sum of Subsequence Widths
Problem Description
You are given an array of integers nums
. For any subsequence of this array, the width is defined as the difference between the maximum and minimum elements in that subsequence.
Your task is to find the sum of widths of all possible non-empty subsequences of nums
. Since this sum can be very large, return the result modulo 10^9 + 7
.
A subsequence is formed by selecting some (or all) elements from the original array while maintaining their relative order. Elements don't need to be consecutive. For example, if nums = [0,3,1,6,2,2,7]
, then [3,6,2,7]
is a valid subsequence.
Example breakdown:
- If
nums = [2,1,3]
, the non-empty subsequences are:[2]
with width = 0 (max=2, min=2)[1]
with width = 0 (max=1, min=1)[3]
with width = 0 (max=3, min=3)[2,1]
with width = 1 (max=2, min=1)[2,3]
with width = 1 (max=3, min=2)[1,3]
with width = 2 (max=3, min=1)[2,1,3]
with width = 2 (max=3, min=1)- Total sum = 0 + 0 + 0 + 1 + 1 + 2 + 2 = 6
The solution uses a mathematical approach after sorting the array. For each element at position i
, it calculates how many times that element contributes as a maximum versus a minimum across all subsequences. The power of 2 (p
) represents the number of subsequences where the current element can be either the maximum or minimum based on its position.
Intuition
The key insight is that instead of enumerating all subsequences (which would be exponentially expensive), we can think about the contribution of each element to the total sum.
First, let's consider what happens when we sort the array. After sorting, for any subsequence, the width is simply max_element - min_element
.
Now, here's the crucial observation: for each element in the sorted array, we need to figure out:
- In how many subsequences does this element appear as the maximum?
- In how many subsequences does this element appear as the minimum?
Let's say we have a sorted array and we're looking at element at index i
:
- For this element to be the maximum in a subsequence, we can only include elements from indices
0
toi-1
(all smaller elements). We have2^i
ways to form such subsequences (each of thei
elements before can either be included or not). - For this element to be the minimum in a subsequence, we can only include elements from indices
i+1
ton-1
(all larger elements). We have2^(n-1-i)
ways to form such subsequences.
So element nums[i]
contributes:
+nums[i] * 2^i
to the sum (when it's the maximum)-nums[i] * 2^(n-1-i)
to the sum (when it's the minimum)
The clever trick in the solution is to pair up the positive and negative contributions. Notice that nums[i]
as a maximum (contributing positively) can be paired with nums[n-1-i]
as a minimum (contributing negatively). Both have the same power of 2 coefficient: 2^i
.
Therefore, we can iterate through the array once and for each position i
, calculate: (nums[i] - nums[n-1-i]) * 2^i
. This gives us the net contribution for this pair of positions, eliminating the need to calculate positive and negative contributions separately.
Solution Approach
The implementation follows the mathematical insight we developed:
-
Sort the array: First, we sort
nums
in ascending order. This allows us to easily identify which elements can be maximums and minimums in subsequences. -
Initialize variables:
mod = 10^9 + 7
for the modulo operationans = 0
to accumulate the total sump = 1
to represent2^i
(starts at2^0 = 1
)
-
Single pass calculation: We iterate through each index
i
and its corresponding valuev
:for i, v in enumerate(nums): ans = (ans + (v - nums[-i - 1]) * p) % mod p = (p << 1) % mod
Let's break down what happens in each iteration:
v
isnums[i]
(current element)nums[-i - 1]
isnums[n-1-i]
(element from the opposite end)(v - nums[-i - 1]) * p
calculates the net contribution:- When
v
acts as maximum in subsequences: contributes+v * 2^i
- When
nums[n-1-i]
acts as minimum in subsequences: contributes-nums[n-1-i] * 2^i
- Combined:
(nums[i] - nums[n-1-i]) * 2^i
- When
-
Power of 2 update: After each iteration, we update
p
by left-shifting (p << 1
), which is equivalent to multiplying by 2. This maintainsp = 2^(i+1)
for the next iteration. -
Modulo operations: We apply modulo at each step to prevent integer overflow and get the final answer modulo
10^9 + 7
.
Time Complexity: O(n log n)
due to sorting, where n
is the length of the array.
Space Complexity: O(1)
if we don't count the space used for sorting (typically O(log n)
for the sorting algorithm's recursion stack).
The elegance of this solution lies in avoiding the exponential complexity of enumerating all subsequences by instead calculating each element's contribution mathematically based on its position in the sorted array.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [2, 1, 3]
.
Step 1: Sort the array
- Original:
[2, 1, 3]
- Sorted:
[1, 2, 3]
Step 2: Initialize variables
mod = 10^9 + 7
ans = 0
p = 1
(represents 2^0)
Step 3: Iterate through the sorted array
Iteration 1 (i=0, v=1):
- Current element:
v = 1
- Opposite element:
nums[-0-1] = nums[-1] = nums[2] = 3
- Contribution:
(1 - 3) * 1 = -2 * 1 = -2
- Update answer:
ans = (0 + (-2)) % mod = -2
(becomes10^9 + 5
after modulo) - Update power:
p = 1 << 1 = 2
(now represents 2^1)
Iteration 2 (i=1, v=2):
- Current element:
v = 2
- Opposite element:
nums[-1-1] = nums[-2] = nums[1] = 2
- Contribution:
(2 - 2) * 2 = 0 * 2 = 0
- Update answer:
ans = (-2 + 0) % mod = -2
- Update power:
p = 2 << 1 = 4
(now represents 2^2)
Iteration 3 (i=2, v=3):
- Current element:
v = 3
- Opposite element:
nums[-2-1] = nums[-3] = nums[0] = 1
- Contribution:
(3 - 1) * 4 = 2 * 4 = 8
- Update answer:
ans = (-2 + 8) % mod = 6
- Update power:
p = 4 << 1 = 8
(now represents 2^3)
Final answer: 6
Let's verify this makes sense:
- Element 1: appears as min in 4 subsequences ([1], [1,2], [1,3], [1,2,3]) → contributes -1×4 = -4
- Element 1: appears as max in 1 subsequence ([1]) → contributes +1×1 = +1
- Element 2: appears as min in 2 subsequences ([2], [2,3]) → contributes -2×2 = -4
- Element 2: appears as max in 2 subsequences ([2], [1,2]) → contributes +2×2 = +4
- Element 3: appears as min in 1 subsequence ([3]) → contributes -3×1 = -3
- Element 3: appears as max in 4 subsequences ([3], [1,3], [2,3], [1,2,3]) → contributes +3×4 = +12
Total: -4 + 1 + (-4) + 4 + (-3) + 12 = 6 ✓
The algorithm cleverly pairs these contributions using the sorted array positions to compute the same result efficiently.
Solution Implementation
1class Solution:
2 def sumSubseqWidths(self, nums: List[int]) -> int:
3 """
4 Calculate the sum of widths of all non-empty subsequences.
5 Width is defined as the difference between max and min elements.
6
7 Key insight: After sorting, for each element at position i:
8 - It will be the maximum in 2^i subsequences (all subsets of elements before it)
9 - It will be the minimum in 2^(n-i-1) subsequences (all subsets of elements after it)
10 """
11 MOD = 10**9 + 7
12
13 # Sort the array to easily identify min/max contributions
14 nums.sort()
15
16 # Initialize result and power of 2
17 result = 0
18 power_of_two = 1
19
20 # Process each element and its contribution
21 for index, value in enumerate(nums):
22 # For element at index i:
23 # - As maximum: contributes +value * 2^i times
24 # - As minimum: contributes -value * 2^(n-i-1) times
25 # nums[-index-1] gives the element from the end (symmetric position)
26 contribution = (value - nums[-index - 1]) * power_of_two
27 result = (result + contribution) % MOD
28
29 # Update power of 2 for next iteration (2^(i+1))
30 power_of_two = (power_of_two * 2) % MOD
31
32 return result
33
1class Solution {
2 // Modulo value for preventing integer overflow
3 private static final int MODULO = 1_000_000_007;
4
5 /**
6 * Calculates the sum of widths of all subsequences.
7 * Width of a subsequence is defined as (max element - min element).
8 *
9 * @param nums Input array of integers
10 * @return Sum of all subsequence widths modulo 10^9 + 7
11 */
12 public int sumSubseqWidths(int[] nums) {
13 // Sort the array to efficiently calculate contributions
14 Arrays.sort(nums);
15
16 // Initialize result and power of 2
17 long totalSum = 0;
18 long powerOfTwo = 1;
19 int arrayLength = nums.length;
20
21 // Iterate through each element in the sorted array
22 for (int index = 0; index < arrayLength; index++) {
23 // For each element at position i:
24 // - It appears as maximum in 2^i subsequences (all subsets of elements to its left)
25 // - It appears as minimum in 2^(n-i-1) subsequences (all subsets of elements to its right)
26 //
27 // The contribution is calculated as:
28 // nums[i] * 2^i (when it's maximum) - nums[n-i-1] * 2^i (when corresponding element is minimum)
29 // This can be rewritten as: (nums[i] - nums[n-i-1]) * 2^i
30
31 // Calculate contribution and add MODULO to handle negative values correctly
32 long contribution = (nums[index] - nums[arrayLength - index - 1]) * powerOfTwo;
33 totalSum = (totalSum + contribution + MODULO) % MODULO;
34
35 // Update power of 2 for next iteration (2^(i+1) = 2 * 2^i)
36 powerOfTwo = (powerOfTwo * 2) % MODULO;
37 }
38
39 return (int) totalSum;
40 }
41}
42
1class Solution {
2public:
3 const int MOD = 1e9 + 7;
4
5 int sumSubseqWidths(vector<int>& nums) {
6 // Sort the array to easily identify min and max elements in subsequences
7 sort(nums.begin(), nums.end());
8
9 long totalSum = 0;
10 long powerOfTwo = 1;
11 int arraySize = nums.size();
12
13 // For each element at position i:
14 // - It appears as maximum in 2^i subsequences (all subsets of elements before it)
15 // - It appears as minimum in 2^(n-1-i) subsequences (all subsets of elements after it)
16 // The contribution is: nums[i] * (2^i - 2^(n-1-i))
17 for (int i = 0; i < arraySize; ++i) {
18 // Calculate contribution of current element as max minus its contribution as min
19 // nums[i] contributes positively when it's the maximum (in 2^i subsequences)
20 // nums[arraySize - 1 - i] contributes negatively when it's the minimum (in 2^i subsequences)
21 // This clever indexing allows us to compute both contributions in one loop
22 long contribution = (long)(nums[i] - nums[arraySize - 1 - i]) * powerOfTwo;
23
24 // Add contribution to total sum with modulo arithmetic
25 // Adding MOD before modulo ensures the result is non-negative
26 totalSum = (totalSum + contribution + MOD) % MOD;
27
28 // Update power of 2 for next iteration (2^(i+1))
29 powerOfTwo = (powerOfTwo * 2) % MOD;
30 }
31
32 return totalSum;
33 }
34};
35
1const MOD = 1e9 + 7;
2
3function sumSubseqWidths(nums: number[]): number {
4 // Sort the array to easily identify min and max elements in subsequences
5 nums.sort((a, b) => a - b);
6
7 let totalSum = 0;
8 let powerOfTwo = 1;
9 const arraySize = nums.length;
10
11 // For each element at position i:
12 // - It appears as maximum in 2^i subsequences (all subsets of elements before it)
13 // - It appears as minimum in 2^(n-1-i) subsequences (all subsets of elements after it)
14 // The contribution is: nums[i] * (2^i - 2^(n-1-i))
15 for (let i = 0; i < arraySize; i++) {
16 // Calculate contribution of current element as max minus its contribution as min
17 // nums[i] contributes positively when it's the maximum (in 2^i subsequences)
18 // nums[arraySize - 1 - i] contributes negatively when it's the minimum (in 2^i subsequences)
19 // This clever indexing allows us to compute both contributions in one loop
20 const contribution = (nums[i] - nums[arraySize - 1 - i]) * powerOfTwo;
21
22 // Add contribution to total sum with modulo arithmetic
23 // Adding MOD before modulo ensures the result is non-negative
24 totalSum = (totalSum + contribution % MOD + MOD) % MOD;
25
26 // Update power of 2 for next iteration (2^(i+1))
27 powerOfTwo = (powerOfTwo * 2) % MOD;
28 }
29
30 return totalSum;
31}
32
Time and Space Complexity
Time Complexity: O(n log n)
The time complexity is dominated by the sorting operation nums.sort()
, which takes O(n log n)
time where n
is the length of the input array. After sorting, there is a single loop that iterates through all n
elements, performing constant-time operations in each iteration (arithmetic operations and modulo). Therefore, the overall time complexity is O(n log n) + O(n) = O(n log n)
.
Space Complexity: O(1)
or O(n)
The space complexity depends on the sorting algorithm implementation:
- If we consider the sorting to be in-place (like Python's Timsort which modifies the input list), the additional space used is
O(1)
for the variablesmod
,ans
,p
,i
, andv
. - However, Python's sort() method actually uses
O(n)
space in the worst case due to the temporary arrays created during the merge operations in Timsort. - If we strictly consider only the extra space used by our algorithm (excluding the sort), it's
O(1)
.
The most accurate answer would be O(n)
space complexity due to the sorting algorithm's space requirements.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow Without Proper Modulo Application
The Pitfall:
A common mistake is forgetting to apply modulo operations at intermediate steps, especially when calculating powers of 2. Since we're dealing with potentially large arrays (up to 10^5 elements), 2^i
can quickly exceed integer limits. For example, 2^100
is astronomically large and will cause overflow even in languages with 64-bit integers.
Incorrect Implementation:
def sumSubseqWidths(self, nums: List[int]) -> int:
MOD = 10**9 + 7
nums.sort()
result = 0
power_of_two = 1
for index, value in enumerate(nums):
contribution = (value - nums[-index - 1]) * power_of_two
result += contribution # Missing modulo here!
power_of_two *= 2 # Missing modulo here!
return result % MOD # Only applying modulo at the end
Why This Fails:
power_of_two
grows exponentially and will overflow after about 30-60 iterations- The intermediate
result
can also overflow before the final modulo - Python handles big integers automatically, but in other languages this would cause undefined behavior
Correct Solution: Apply modulo at every arithmetic operation that could potentially overflow:
for index, value in enumerate(nums):
contribution = (value - nums[-index - 1]) * power_of_two
result = (result + contribution) % MOD # Apply modulo after addition
power_of_two = (power_of_two * 2) % MOD # Apply modulo after multiplication
2. Misunderstanding the Index Symmetry
The Pitfall:
Incorrectly calculating the symmetric element from the opposite end of the sorted array. The formula nums[-index - 1]
might be confusing and lead to off-by-one errors.
Incorrect Implementation:
for index, value in enumerate(nums):
# Wrong: Using nums[-index] instead of nums[-index - 1]
contribution = (value - nums[-index]) * power_of_two
# This would incorrectly pair nums[0] with nums[0] instead of nums[n-1]
Why This Matters:
- For an array of length
n
, when we're at indexi
, we want to pair it with indexn-1-i
- Using negative indexing:
nums[-1]
is the last element,nums[-2]
is second-to-last, etc. - So for index
i
, we neednums[-(i+1)]
ornums[-i-1]
to getnums[n-1-i]
Verification Example:
For nums = [1, 2, 3, 4]
(already sorted):
- When
i=0
: We wantnums[0]
paired withnums[3]
→nums[-0-1] = nums[-1] = nums[3]
✓ - When
i=1
: We wantnums[1]
paired withnums[2]
→nums[-1-1] = nums[-2] = nums[2]
✓
3. Not Sorting the Array First
The Pitfall: Forgetting to sort the array or assuming it's already sorted can completely break the algorithm.
Why Sorting is Critical: The entire mathematical approach relies on the fact that after sorting:
- Elements to the left of position
i
are all smaller (can form subsequences wherenums[i]
is maximum) - Elements to the right of position
i
are all larger (can form subsequences wherenums[i]
is minimum)
Without sorting, the counting logic falls apart and produces incorrect results.
Prevention:
Always include nums.sort()
at the beginning of the solution, even if test cases appear sorted.
How does quick sort divide the problem into subproblems?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!