3082. Find the Sum of the Power of All Subsequences
Problem Description
You are given an integer array nums
of length n
and a positive integer k
.
The problem introduces a concept called the power of an array. The power is defined as the total number of subsequences across all possible subsequences of nums
that have a sum equal to k
.
To understand this better, let's break it down:
- First, consider all possible subsequences of the array
nums
(including the empty subsequence and the full array itself) - For each of these subsequences, count how many of its own subsequences sum to
k
- The power is the sum of all these counts
For example, if nums = [1, 2, 3]
and k = 3
:
- The subsequence
[1, 2]
has one subsequence that sums to 3:[1, 2]
itself - The subsequence
[3]
has one subsequence that sums to 3:[3]
itself - The subsequence
[1, 2, 3]
has two subsequences that sum to 3:[1, 2]
and[3]
- Other subsequences contribute 0 to the power
Your task is to calculate the sum of power across all subsequences of nums
and return the result modulo 10^9 + 7
(since the answer can be very large).
The key insight is that for each element in nums
, we need to consider:
- Whether it's included in the subsequence
S
we're examining - If it's in
S
, whether it's also part of the subsequenceT
(withinS
) that sums tok
This leads to tracking the number of ways to form subsequences with specific sums, accounting for elements that are present but not used in the sum versus elements that actively contribute to reaching the target sum k
.
Intuition
The key insight is recognizing that we need to count subsequences within subsequences. This double-counting problem initially seems complex, but we can simplify it by thinking about the contribution of each element.
Consider what happens when we process each element in nums
. For any element x
, we need to track:
- All the ways it can be part of a subsequence
S
- Within each
S
that containsx
, whetherx
contributes to a sum ofk
This naturally leads us to dynamic programming where we track the number of ways to achieve each possible sum using the elements we've seen so far.
The critical observation is that for each element x
at position i
, there are three scenarios:
x
is not included in subsequenceS
at allx
is in subsequenceS
but doesn't contribute to the sum (it's just "present" but not used)x
is in subsequenceS
and actively contributes to achieving sumk
Why do we care about scenario 2? Because when an element is in S
but not used for the sum, it still affects the count - it creates a different subsequence S
that might have its own subsequences summing to k
.
This realization leads to the pattern: when we don't use element x
for the sum (scenarios 1 and 2), we're essentially doubling the previous count f[i-1][j]
because x
can either be absent or present-but-unused. When we do use x
(scenario 3), we add the ways to make sum j-x
from previous elements.
The state transition f[i][j] = f[i-1][j] * 2 + f[i-1][j-x]
elegantly captures all three scenarios:
- The
f[i-1][j] * 2
term accounts forx
being either absent fromS
or inS
but not contributing to the sum - The
f[i-1][j-x]
term accounts forx
being inS
and contributing to the sum
This approach ensures we count every valid configuration exactly once, giving us the total "power" of the array.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement the solution using a 2D dynamic programming approach. Let's define f[i][j]
as the number of ways to form subsequences using the first i
elements of nums
such that each subsequence has a sum equal to j
.
Initialization:
- Create a 2D array
f
of size(n+1) × (k+1)
wheren
is the length ofnums
- Set
f[0][0] = 1
, representing one way to achieve sum 0 with 0 elements (the empty subsequence) - All other entries start at 0
State Transition:
For each element x
at position i
(1-indexed), and for each possible sum j
from 0 to k
:
f[i][j] = f[i-1][j] × 2 + f[i-1][j-x] (if j >= x) f[i][j] = f[i-1][j] × 2 (if j < x)
The transition formula captures three scenarios:
- Element
x
is not in subsequenceS
: contributesf[i-1][j]
- Element
x
is in subsequenceS
but not in subsequenceT
(not used for sum): contributesf[i-1][j]
- Element
x
is in bothS
andT
(used for sum): contributesf[i-1][j-x]
The first two scenarios together give us f[i-1][j] × 2
.
Implementation Details:
class Solution:
def sumOfPower(self, nums: List[int], k: int) -> int:
mod = 10**9 + 7
n = len(nums)
f = [[0] * (k + 1) for _ in range(n + 1)]
f[0][0] = 1
for i, x in enumerate(nums, 1):
for j in range(k + 1):
# Cases where x is not used for the sum
f[i][j] = f[i - 1][j] * 2 % mod
# Case where x is used for the sum
if j >= x:
f[i][j] = (f[i][j] + f[i - 1][j - x]) % mod
return f[n][k]
Time Complexity: O(n × k)
where n
is the length of the array and k
is the target sum. We iterate through each element and for each element, we update all possible sums from 0 to k
.
Space Complexity: O(n × k)
for the 2D DP table. This can be optimized to O(k)
using rolling arrays since we only need the previous row to compute the current row.
The final answer f[n][k]
represents the total number of subsequence pairs (S, T)
where S
is a subsequence of nums
and T
is a subsequence of S
with sum equal to k
, which is exactly the "power" of the array as defined in the problem.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with nums = [1, 2, 3]
and k = 3
.
We'll build our DP table f[i][j]
where f[i][j]
represents the number of ways to form subsequences using the first i
elements with sum j
.
Initial State:
f[0][0] = 1 (empty subsequence) f[0][1] = f[0][2] = f[0][3] = 0
Processing element 1 (value = 1): For each sum j from 0 to 3:
- j = 0:
f[1][0] = f[0][0] × 2 + f[0][-1]
= 1 × 2 + 0 = 2- Two ways: {} (1 not in S), {1} with sum 0 (1 in S but not used)
- j = 1:
f[1][1] = f[0][1] × 2 + f[0][0]
= 0 × 2 + 1 = 1- One way: {1} with sum 1 (1 used for sum)
- j = 2:
f[1][2] = f[0][2] × 2
= 0 × 2 = 0 - j = 3:
f[1][3] = f[0][3] × 2
= 0 × 2 = 0
Processing element 2 (value = 2):
- j = 0:
f[2][0] = f[1][0] × 2
= 2 × 2 = 4- Four ways: {}, {1}, {2}, {1,2} all with sum 0
- j = 1:
f[2][1] = f[1][1] × 2
= 1 × 2 = 2- Two ways: {1} with sum 1, {1,2} with sum 1 (only 1 used)
- j = 2:
f[2][2] = f[1][2] × 2 + f[1][0]
= 0 × 2 + 2 = 2- Two ways: {2} with sum 2, {1,2} with sum 2 (only 2 used)
- j = 3:
f[2][3] = f[1][3] × 2 + f[1][1]
= 0 × 2 + 1 = 1- One way: {1,2} with sum 3 (both used)
Processing element 3 (value = 3):
- j = 0:
f[3][0] = f[2][0] × 2
= 4 × 2 = 8 - j = 1:
f[3][1] = f[2][1] × 2
= 2 × 2 = 4 - j = 2:
f[3][2] = f[2][2] × 2
= 2 × 2 = 4 - j = 3:
f[3][3] = f[2][3] × 2 + f[2][0]
= 1 × 2 + 4 = 6
Final Answer: f[3][3] = 6
This means there are 6 total subsequence pairs (S, T) where S is a subsequence of [1,2,3] and T is a subsequence of S with sum 3:
- S = {1,2}, T = {1,2}
- S = {3}, T = {3}
- S = {1,3}, T = {3}
- S = {2,3}, T = {3}
- S = {1,2,3}, T = {1,2}
- S = {1,2,3}, T = {3}
The DP approach correctly counts all these contributions by tracking how elements can be included in S but not used for the sum versus being used to achieve the target sum k.
Solution Implementation
1class Solution:
2 def sumOfPower(self, nums: List[int], k: int) -> int:
3 # Define modulo for preventing integer overflow
4 MOD = 10**9 + 7
5
6 # Get the length of input array
7 n = len(nums)
8
9 # Initialize DP table
10 # dp[i][j] represents the sum of power considering first i elements
11 # where subsequences sum to j
12 dp = [[0] * (k + 1) for _ in range(n + 1)]
13
14 # Base case: empty subsequence with sum 0 has power 1
15 dp[0][0] = 1
16
17 # Iterate through each element in nums
18 for i, num in enumerate(nums, 1):
19 # For each possible sum from 0 to k
20 for target_sum in range(k + 1):
21 # Case 1: Don't include current element
22 # All subsequences from previous state contribute with doubled power
23 # (since current element can be either included or excluded in other positions)
24 dp[i][target_sum] = dp[i - 1][target_sum] * 2 % MOD
25
26 # Case 2: Include current element if it doesn't exceed target sum
27 if target_sum >= num:
28 # Add the power from subsequences that sum to (target_sum - num)
29 dp[i][target_sum] = (dp[i][target_sum] + dp[i - 1][target_sum - num]) % MOD
30
31 # Return the total power for subsequences that sum to k using all n elements
32 return dp[n][k]
33
1class Solution {
2 public int sumOfPower(int[] nums, int k) {
3 // Modulo value for preventing integer overflow
4 final int MOD = (int) 1e9 + 7;
5
6 // Get the length of the input array
7 int n = nums.length;
8
9 // dp[i][j] represents the number of ways to achieve sum j
10 // using the first i elements, considering all possible subsequences
11 int[][] dp = new int[n + 1][k + 1];
12
13 // Base case: empty subsequence has sum 0, there's exactly 1 way
14 dp[0][0] = 1;
15
16 // Iterate through each element
17 for (int i = 1; i <= n; ++i) {
18 // Iterate through all possible sums from 0 to k
19 for (int j = 0; j <= k; ++j) {
20 // Case 1: Don't include current element nums[i-1]
21 // All previous subsequences can either include or exclude nums[i-1]
22 // So we multiply by 2 (for the power contribution)
23 dp[i][j] = (dp[i - 1][j] * 2) % MOD;
24
25 // Case 2: Include current element nums[i-1] in the subsequence
26 // Only possible if current sum j >= nums[i-1]
27 if (j >= nums[i - 1]) {
28 dp[i][j] = (dp[i][j] + dp[i - 1][j - nums[i - 1]]) % MOD;
29 }
30 }
31 }
32
33 // Return the number of ways to achieve sum k using all n elements
34 return dp[n][k];
35 }
36}
37
1class Solution {
2public:
3 int sumOfPower(vector<int>& nums, int k) {
4 const int MOD = 1000000007; // Modulo value for preventing overflow
5 int n = nums.size();
6
7 // dp[i][j] represents the number of ways to form subsequences
8 // using first i elements with sum equal to j
9 int dp[n + 1][k + 1];
10 memset(dp, 0, sizeof(dp));
11
12 // Base case: empty subsequence has sum 0
13 dp[0][0] = 1;
14
15 // Iterate through each element
16 for (int i = 1; i <= n; ++i) {
17 // Iterate through all possible sums
18 for (int j = 0; j <= k; ++j) {
19 // Case 1: Don't include current element nums[i-1]
20 // All subsequences from previous elements are still valid
21 // Each can either include or exclude current element (multiply by 2)
22 dp[i][j] = (dp[i - 1][j] * 2) % MOD;
23
24 // Case 2: Include current element nums[i-1]
25 // Only possible if current sum j >= nums[i-1]
26 if (j >= nums[i - 1]) {
27 dp[i][j] = (dp[i][j] + dp[i - 1][j - nums[i - 1]]) % MOD;
28 }
29 }
30 }
31
32 // Return the number of subsequences with sum equal to k
33 return dp[n][k];
34 }
35};
36
1/**
2 * Calculates the sum of power of subsequences with sum equal to k
3 * Uses dynamic programming to count valid subsequences
4 * @param nums - Array of numbers to form subsequences from
5 * @param k - Target sum for subsequences
6 * @returns The sum of power modulo 10^9 + 7
7 */
8function sumOfPower(nums: number[], k: number): number {
9 const MOD: number = 1000000007; // 10^9 + 7 for modulo operation
10 const arrayLength: number = nums.length;
11
12 // dp[i][j] represents the number of ways to form subsequences
13 // using first i elements with sum equal to j
14 const dp: number[][] = Array.from(
15 { length: arrayLength + 1 },
16 () => Array(k + 1).fill(0)
17 );
18
19 // Base case: one way to form sum 0 with 0 elements (empty subsequence)
20 dp[0][0] = 1;
21
22 // Fill the DP table
23 for (let i = 1; i <= arrayLength; i++) {
24 for (let currentSum = 0; currentSum <= k; currentSum++) {
25 // Case 1: Don't include current element
26 // Multiply by 2 to account for all previous subsequences
27 dp[i][currentSum] = (dp[i - 1][currentSum] * 2) % MOD;
28
29 // Case 2: Include current element if possible
30 const currentElement: number = nums[i - 1];
31 if (currentSum >= currentElement) {
32 dp[i][currentSum] = (
33 dp[i][currentSum] +
34 dp[i - 1][currentSum - currentElement]
35 ) % MOD;
36 }
37 }
38 }
39
40 // Return the number of ways to form sum k using all n elements
41 return dp[arrayLength][k];
42}
43
Time and Space Complexity
The time complexity is O(n × k)
, where n
is the length of the array nums
and k
is the given positive integer. This is because we have two nested loops: the outer loop iterates through all n
elements of nums
(from index 1 to n), and the inner loop iterates through all possible sums from 0 to k
. Each operation inside the nested loops takes constant time O(1)
.
The space complexity is O(n × k)
. We create a 2D DP table f
with dimensions (n + 1) × (k + 1)
to store the intermediate results. This table requires O((n + 1) × (k + 1)) = O(n × k)
space. The additional variables used (mod
, n
, i
, j
, x
) only require constant space O(1)
, so the overall space complexity is dominated by the DP table.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Problem Definition
The most common pitfall is misinterpreting what "power" means. Many initially think it's about counting subsequences that sum to k
, but it's actually about counting subsequence pairs (S, T) where S is a subsequence of nums and T is a subsequence of S that sums to k.
Wrong interpretation:
# This only counts subsequences that sum to k
def wrongApproach(nums, k):
count = 0
# Generate all subsequences and check if sum equals k
for subsequence in all_subsequences(nums):
if sum(subsequence) == k:
count += 1
return count
Correct understanding: Each element can be in three states:
- Not in subsequence S at all
- In subsequence S but not used in the sum (not in T)
- In subsequence S and used in the sum (in T)
2. Incorrect DP State Transition
A common mistake is forgetting to multiply by 2 when an element is not used for the sum, missing the fact that the element can either be present in S but not in T, or not present in S at all.
Incorrect:
# Missing the factor of 2
for i, num in enumerate(nums, 1):
for target_sum in range(k + 1):
dp[i][target_sum] = dp[i - 1][target_sum] # Wrong! Missing multiplication by 2
if target_sum >= num:
dp[i][target_sum] = (dp[i][target_sum] + dp[i - 1][target_sum - num]) % MOD
Correct:
for i, num in enumerate(nums, 1):
for target_sum in range(k + 1):
dp[i][target_sum] = dp[i - 1][target_sum] * 2 % MOD # Correct: multiply by 2
if target_sum >= num:
dp[i][target_sum] = (dp[i][target_sum] + dp[i - 1][target_sum - num]) % MOD
3. Forgetting Modulo Operations
Since the result can be very large, forgetting to apply modulo at each step can cause integer overflow or incorrect results.
Incorrect:
# Missing modulo operations dp[i][target_sum] = dp[i - 1][target_sum] * 2 + dp[i - 1][target_sum - num]
Correct:
dp[i][target_sum] = dp[i - 1][target_sum] * 2 % MOD if target_sum >= num: dp[i][target_sum] = (dp[i][target_sum] + dp[i - 1][target_sum - num]) % MOD
4. Space Optimization Pitfall
When optimizing space from O(n×k) to O(k) using a 1D array, a common mistake is updating values in the wrong order, causing previously computed values to be overwritten before they're used.
Incorrect space optimization:
# Wrong: iterating forward causes values to be overwritten prematurely
dp = [0] * (k + 1)
dp[0] = 1
for num in nums:
for target_sum in range(k + 1): # Wrong direction!
dp[target_sum] = dp[target_sum] * 2 % MOD
if target_sum >= num:
dp[target_sum] = (dp[target_sum] + dp[target_sum - num]) % MOD
Correct space optimization:
# Correct: iterate backward to avoid overwriting needed values
dp = [0] * (k + 1)
dp[0] = 1
for num in nums:
for target_sum in range(k, -1, -1): # Iterate backward
dp[target_sum] = dp[target_sum] * 2 % MOD
if target_sum >= num:
dp[target_sum] = (dp[target_sum] + dp[target_sum - num]) % MOD
5. Off-by-One Errors in Indexing
Using 0-based vs 1-based indexing incorrectly can lead to accessing wrong array positions.
Solution: Be consistent with indexing. The provided solution uses 1-based indexing for the DP table rows (with enumerate(nums, 1)
) to make the logic clearer, but you could also use 0-based indexing throughout as long as you're consistent.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!