Facebook Pixel

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 element x.
  • g[x] counts the number of times element x 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:

  1. Initialize Constants and Data Structures:

    • Set mod to 10^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.
  2. Iterate Over the Input Array:

    • For each number x in the array nums, update the data structures:
      • Increment f[x] by the current value of x, which represents its individual contribution as a single-element subsequence.
      • Increment g[x] to account for x itself as a subsequence.
  3. Consider Neighboring Elements:

    • For each x, check its neighbors x-1 and x+1.
    • Update f[x] and f[x] by adding contributions from these neighbors:
      • Add to f[x] the sum of f[x-1] and contributions from g[x-1] multiplied by x, computing the sum of subsequences that can be extended.
      • Similarly, add contributions involving x+1.
  4. 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.

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 Evaluator

Example Walkthrough

Let's consider a small example to illustrate the solution approach:

Suppose the input array nums is [1, 2, 3].

  1. Initialize Constants and Data Structures:

    • mod = 10^9 + 7
    • f, g: both initialized as default dictionaries to store cumulative sums and subsequence counts, respectively.
  2. Iterate Over the Input Array:

    • For x = 1:

      • f[1] += 1 (since 1 can form a subsequence by itself)
      • g[1] += 1 (counting the subsequence [1])
      • Consider x-1 (0) and x+1 (2):
        • f[1] remains the same, as x-1 is not part of nums.
        • f[2] will be updated when x = 2 is processed.
    • For x = 2:

      • f[2] += 2 (subsequence [2])
      • g[2] += 1
      • Consider x-1 (1) and x+1 (3):
        • f[2] += f[1] + g[1] * 2:
          • f[2] now accounts for subsequences [1, 2], [2].
        • f[3] will be updated when x = 3 is processed.
    • For x = 3:

      • f[3] += 3 (subsequence [3])
      • g[3] += 1
      • Consider x-1 (2) and x+1 (4):
        • f[3] += f[2] + g[2] * 3:
          • f[3] now accounts for subsequences [1, 2, 3], [2, 3], [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.

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.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

Which of these pictures shows the visit order of a depth-first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!


Load More