3351. Sum of Good Subsequences
Problem Description
You are given an integer array nums
. A good subsequence is defined as a subsequence of nums
where the absolute difference between any two consecutive elements in the subsequence is exactly 1.
Return the sum of all possible good subsequences of nums
.
Since the answer may be very large, return it modulo 10^9 + 7
.
Note that a subsequence of size 1 is considered good by definition.
Intuition
To solve this problem, the goal is finding and calculating the sum of all good subsequences of the given array nums
where consecutive elements have an absolute difference of 1.
A straightforward approach might involve generating all possible subsequences, checking their validity according to the given conditions, and computing the sum. However, this method is inefficient because the number of potential subsequences grows exponentially with the array's size.
Instead, a more efficient approach leverages dynamic programming by maintaining two maps (f
and g
) to store the intermediate results during the computation. Here:
f[x]
represents the cumulative sum of good subsequences up to the elementx
.g[x]
counts the number of times elementx
contributes to forming a good subsequence.
For every element x
in nums
, calculate and update f[x]
and g[x]
by considering its contribution in conjunction with its neighboring elements (x-1
and x+1
). This accounts for all subsequences that can be extended with x
.
The algorithm efficiently computes the sum of all possible good subsequences by iteratively processing each element and updating the maps. Finally, it returns the sum of values in f
, taken modulo 10^9 + 7
, to handle large numbers.
Learn more about Dynamic Programming patterns.
Solution Approach
The solution employs a dynamic programming approach, making use of two main data structures: dictionaries f
and g
from the collections
module.
Here's a step-by-step explanation of the solution:
-
Initialize Constants and Data Structures:
- Set
mod
to10^9 + 7
to manage large numbers via modular arithmetic. - Use
f
to store the cumulative sums of good subsequences ending with each unique number. - Use
g
to count occurrences of good subsequences that can be extended by each number.
- Set
-
Iterate Over the Input Array:
- For each number
x
in the arraynums
, update the data structures:- Increment
f[x]
by the current value ofx
, which represents its individual contribution as a single-element subsequence. - Increment
g[x]
to account forx
itself as a subsequence.
- Increment
- For each number
-
Consider Neighboring Elements:
- For each
x
, check its neighborsx-1
andx+1
. - Update
f[x]
andf[x]
by adding contributions from these neighbors:- Add to
f[x]
the sum off[x-1]
and contributions fromg[x-1]
multiplied byx
, computing the sum of subsequences that can be extended. - Similarly, add contributions involving
x+1
.
- Add to
- For each
-
Sum and Modulo Operation:
- Finally, compute the sum of all values in
f
to get the total sum of all good subsequences. - Return this sum modulo
10^9 + 7
to handle potentially large numbers.
- Finally, compute the sum of all values in
This method efficiently constructs a solution by recognizing patterns and leveraging relationships between consecutive elements, significantly reducing the computational complexity compared to brute force approaches.
The given code implements this logic:
class Solution:
def sumOfGoodSubsequences(self, nums: List[int]) -> int:
mod = 10**9 + 7
f = defaultdict(int)
g = defaultdict(int)
for x in nums:
f[x] += x
g[x] += 1
f[x] += f[x - 1] + g[x - 1] * x
g[x] += g[x - 1]
f[x] += f[x + 1] + g[x + 1] * x
g[x] += g[x + 1]
return sum(f.values()) % mod
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider a small example to illustrate the solution approach:
Suppose the input array nums
is [1, 2, 3]
.
-
Initialize Constants and Data Structures:
mod = 10^9 + 7
f
,g
: both initialized as default dictionaries to store cumulative sums and subsequence counts, respectively.
-
Iterate Over the Input Array:
-
For
x = 1
:f[1]
+=1
(since1
can form a subsequence by itself)g[1]
+=1
(counting the subsequence[1]
)- Consider
x-1 (0)
andx+1 (2)
:f[1]
remains the same, asx-1
is not part ofnums
.f[2]
will be updated whenx = 2
is processed.
-
For
x = 2
:f[2]
+=2
(subsequence[2]
)g[2]
+=1
- Consider
x-1 (1)
andx+1 (3)
:f[2]
+=f[1] + g[1] * 2
:f[2]
now accounts for subsequences[1, 2]
,[2]
.
f[3]
will be updated whenx = 3
is processed.
-
For
x = 3
:f[3]
+=3
(subsequence[3]
)g[3]
+=1
- Consider
x-1 (2)
andx+1 (4)
:f[3]
+=f[2] + g[2] * 3
:f[3]
now accounts for subsequences[1, 2, 3]
,[2, 3]
,[3]
.
-
-
Sum and Modulo Operation:
- Calculate
sum(f.values())
: this gives the sum of all good subsequences, which are[1]
,[2]
,[1, 2]
,[3]
,[2, 3]
,[1, 2, 3]
. - Return the result modulo
10^9 + 7
.
- Calculate
Using dynamic programming through the f
and g
mappings allows the calculation of good subsequences efficiently.
Solution Implementation
1from typing import List
2from collections import defaultdict
3
4class Solution:
5 def sumOfGoodSubsequences(self, nums: List[int]) -> int:
6 mod = 10**9 + 7 # Define the modulo constant to prevent overflow
7 sum_map = defaultdict(int) # Dictionary to store sum of subsequences ending with each element
8 count_map = defaultdict(int) # Dictionary to store count of subsequences ending with each element
9
10 # Iterate through each number in the list
11 for x in nums:
12 # Increase the sum and count for the current element
13 sum_map[x] += x
14 count_map[x] += 1
15
16 # Add contributions from the previous element (x-1)
17 sum_map[x] += sum_map[x - 1] + count_map[x - 1] * x
18 count_map[x] += count_map[x - 1]
19
20 # Add contributions from the next element (x+1)
21 sum_map[x] += sum_map[x + 1] + count_map[x + 1] * x
22 count_map[x] += count_map[x + 1]
23
24 # Compute the total sum of all subsequences and apply the modulo operation
25 return sum(sum_map.values()) % mod
26
1class Solution {
2 public int sumOfGoodSubsequences(int[] nums) {
3 final int MOD = (int) 1e9 + 7;
4 int max = 0;
5 // Find the maximum element in the array.
6 for (int num : nums) {
7 max = Math.max(max, num);
8 }
9
10 // f[i] stores cumulative sum of all subsequences ending with i.
11 long[] f = new long[max + 1];
12 // g[i] stores the count of all subsequences ending with i.
13 long[] g = new long[max + 1];
14
15 for (int num : nums) {
16 // Update f and g for the current number
17 f[num] = (f[num] + num) % MOD; // Include current number
18 g[num] = (g[num] + 1) % MOD; // Include current number itself as a subsequence
19
20 // Consider cases where the subsequences can be extended from previous numbers
21 if (num > 0) {
22 f[num] = (f[num] + f[num - 1] + (g[num - 1] * num % MOD)) % MOD;
23 g[num] = (g[num] + g[num - 1]) % MOD;
24 }
25
26 // Consider cases where the subsequences can be extended to the next number
27 if (num + 1 <= max) {
28 f[num] = (f[num] + f[num + 1] + (g[num + 1] * num % MOD)) % MOD;
29 g[num] = (g[num] + g[num + 1]) % MOD;
30 }
31 }
32
33 // Compute the final answer by summing all f[i]
34 long totalSum = 0;
35 for (long sum : f) {
36 totalSum = (totalSum + sum) % MOD;
37 }
38
39 return (int) totalSum;
40 }
41}
42
1#include <vector>
2#include <numeric>
3// Removed the usage of ranges::max to use standard library for better portability
4#include <algorithm>
5
6class Solution {
7public:
8 int sumOfGoodSubsequences(std::vector<int>& nums) {
9 const int mod = 1e9 + 7; // Define the module for result consistency
10 int maxValue = *std::max_element(nums.begin(), nums.end()); // Find the max value in nums
11
12 std::vector<long long> f(maxValue + 1), g(maxValue + 1); // Initializing vectors f and g
13
14 // Iterate through each number in the vector
15 for (int x : nums) {
16 // Update f and g for current value x
17 f[x] += x;
18 g[x] += 1;
19
20 // Consider subsequences ending with or before x
21 if (x > 0) {
22 f[x] = (f[x] + f[x - 1] + g[x - 1] * x % mod) % mod;
23 g[x] = (g[x] + g[x - 1]) % mod;
24 }
25
26 // Consider subsequences including x + 1
27 if (x + 1 <= maxValue) {
28 f[x] = (f[x] + f[x + 1] + g[x + 1] * x % mod) % mod;
29 g[x] = (g[x] + g[x + 1]) % mod;
30 }
31 }
32
33 // Calculate and return the total sum of good subsequences modulo mod
34 return std::accumulate(f.begin(), f.end(), 0LL) % mod;
35 }
36};
37
1// The function calculates the sum of 'good' subsequences.
2function sumOfGoodSubsequences(nums: number[]): number {
3 const mod = 10 ** 9 + 7; // Define the modulus for the result to prevent overflow
4 const maxNum = Math.max(...nums); // Find the maximum number in the list
5 const sumArray: number[] = Array(maxNum + 1).fill(0); // Array to store cumulative sums
6 const countArray: number[] = Array(maxNum + 1).fill(0); // Array to store counts of occurrences
7
8 // Iterate over each number in the input array
9 for (const num of nums) {
10 // Update the sum and count arrays with the current number
11 sumArray[num] = (sumArray[num] + num) % mod;
12 countArray[num] += 1;
13
14 // If the current number is greater than 0, update using predecessors
15 if (num > 0) {
16 sumArray[num] = (sumArray[num] + sumArray[num - 1] + ((countArray[num - 1] * num) % mod)) % mod;
17 countArray[num] = (countArray[num] + countArray[num - 1]) % mod;
18 }
19
20 // If there are successors of the current number, update using successors
21 if (num + 1 <= maxNum) {
22 sumArray[num] = (sumArray[num] + sumArray[num + 1] + ((countArray[num + 1] * num) % mod)) % mod;
23 countArray[num] = (countArray[num] + countArray[num + 1]) % mod;
24 }
25 }
26
27 // Reduce the sumArray to get the total sum, returned modulo mod
28 return sumArray.reduce((accumulatedSum, currentSum) => (accumulatedSum + currentSum) % mod, 0);
29}
30
Time and Space Complexity
The time complexity of the code is O(n)
, where n
is the number of elements in the nums
list. This is because each element in the list is processed once through a series of constant-time operations.
The space complexity of the code is O(n)
. This results from using two defaultdict
objects, f
and g
, which store information related to each unique element in nums
. In the worst case, these dictionaries could hold as many entries as there are elements in nums
.
Learn more about how to find time and space complexity quickly.
Which of these pictures shows the visit order of a depth-first search?
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!