Facebook Pixel

3299. Sum of Consecutive Subsequences 🔒


Problem Description

We define an array arr of length n as consecutive if it satisfies one of the following conditions:

  • arr[i] - arr[i - 1] == 1 for all 1 <= i < n.
  • arr[i] - arr[i - 1] == -1 for all 1 <= i < n.

The value of an array is the sum of its elements.

For example, [3, 4, 5] is a consecutive array with a value of 12, and [9, 8] is another consecutive array with a value of 17. In contrast, [3, 4, 3] and [8, 6] are not consecutive.

Given an array of integers nums, return the sum of the values of all consecutive non-empty subsequences.

Since the result may be very large, return it modulo (10^9 + 7).

Note that an array of length 1 is also considered consecutive.

Intuition

To solve this problem, the key is to determine how often each element in nums contributes to a consecutive subsequence of length greater than 1.

  1. Understanding Contributions: Each element nums[i] contributes to the value of consecutive subsequences. By calculating how many such subsequences include nums[i], we can determine its contribution.

  2. Identifying Patterns: We focus on two main patterns: strictly increasing and strictly decreasing subsequences.

  3. Two-Pass Calculation: For each pattern, we use two arrays, left and right, to track potential subsequences:

    • Left Array: This array tracks the number of strictly increasing subsequences ending just before the current element.
    • Right Array: This array tracks the number of strictly increasing subsequences starting right after the current element.
  4. Total Calculation for Subsequence Contributions: By computing these patterns separately using a function calc(nums), and reversing the array to handle both increasing and decreasing subsequences, we can calculate the overall sum by:

    • Calculating contributions for strictly increasing subsequences.
    • Reversing the array to calculate contributions for strictly decreasing subsequences.
    • Adding the total contribution from all elements.

Through these steps, the solution efficiently processes the array nums, combining contributions from all possible consecutive subsequences to deliver the desired result.

Learn more about Dynamic Programming patterns.

Solution Approach

The solution utilizes an enumeration technique to determine the contribution of each element in the array nums to the sum of all possible consecutive subsequences. Below is a detailed explanation of the approach used:

  1. Contributions Calculation:

    • We need to figure out how frequently each nums[i] appears in consecutive subsequences longer than 1. The contribution of an element in such subsequences is the product of its frequency and its value.
  2. Calc Function:

    • A function calc(nums) is implemented to compute the contributions for a given ordering (strictly increasing or decreasing). This function helps calculate the sum of values of all subsequences that are consecutive.
  3. Two Supporting Arrays:

    • left: This array is used to count how many strictly increasing subsequences end with the element nums[i] - 1 before each nums[i].
    • right: This array counts how many strictly increasing subsequences start with the element nums[i] + 1 after each nums[i].
  4. Implementation Steps in calc(nums):

    • Initialize two arrays left and right filled with zeros.
    • Use a counter to determine the number of subsequences ending with nums[i] - 1 as you traverse the list.
    • Traverse from left to right and update left array.
    • Reset the counter and traverse from right to left to fill the right array.
  5. Combining Results:

    • First, calculate contributions using calc(nums) for strictly increasing subsequences.
    • Reverse nums and calculate contributions again for strictly decreasing subsequences.
    • Add the sum of all elements from the original array for subsequences of length 1.
  6. Modulo Operation:

    • The final computed sum needs to be returned modulo (10^9 + 7) to manage large number overflow.

This approach ensures that the solution is efficient, incorporating both directions of sequence formation with a complexity of ( O(n) ).

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 we have an array nums = [2, 3, 2, 1]. We need to find the sum of values of all consecutive non-empty subsequences, where the conditions for consecutiveness are defined by the difference being 1 or -1 between consecutive elements.

Step 1: Identify Consecutive Subsequences

  • Based on the definitions, identify all non-empty consecutive subsequences:
    • Increasing subsequences: [2, 3] (value 5), and [2, 3, 2] (value 7)
    • Decreasing subsequences: [3, 2] (value 5), [3, 2, 1] (value 6), and [2, 1] (value 3)
    • Each individual element is also a consecutive subsequence by itself: [2], [3], [2], [1] with values 2, 3, 2, and 1 respectively.

Step 2: Calculate Values Using the Calc Function

  • Increasing Order Contributions: Using the calc(nums) function for strictly increasing subsequences:

    • left array represents subsequences ending at each index: [0, 1, 1, 0]
    • right array represents subsequences starting at each index: [1, 0, 0, 0]
    • Calculate contributions for increasing sequences: using these arrays and summing corresponding subsequences' values.
  • Decreasing Order Contributions: Reverse nums to handle the strictly decreasing subsequences: Using the calc(nums) function for strictly decreasing subsequences:

    • nums reversed: [1, 2, 3, 2]
    • left array updated after reversal: [0, 1, 1, 0]
    • right array updated after reversal: [0, 1, 0, 0]
    • Calculate contributions for decreasing sequences.

Step 3: Combine Results

  • Include the contribution of each subsequence:
    • Sum contributions from both increasing and decreasing subsequences.
    • Add values of each single-element subsequence: [2], [3], [2], and [1].

Step 4: Apply Modulo Operation

  • Compute the total sum of contributions and take modulo (10^9 + 7) to ensure manageable output size.

Thus, by following these steps, we manage to efficiently calculate the sum of the values of all consecutive subsequences in nums, giving us the final result.

Solution Implementation

1from typing import List
2from collections import Counter
3
4class Solution:
5    def getSum(self, nums: List[int]) -> int:
6        def calc(nums: List[int]) -> int:
7            n = len(nums)
8            left = [0] * n
9            right = [0] * n
10            cnt = Counter()
11
12            # Calculate left contribution
13            for i in range(1, n):
14                cnt[nums[i - 1]] += 1 + cnt[nums[i - 1] - 1]  # Incrementing counter for pairs
15                left[i] = cnt[nums[i] - 1]  # Left contributions
16
17            cnt = Counter()  # Reset counter for right side calculation
18
19            # Calculate right contribution
20            for i in range(n - 2, -1, -1):
21                cnt[nums[i + 1]] += 1 + cnt[nums[i + 1] + 1]  # Incrementing counter for pairs
22                right[i] = cnt[nums[i] + 1]  # Right contributions
23
24            # Calculate the overall sum for this pass
25            return sum((l + r + l * r) * x for l, r, x in zip(left, right, nums)) % mod
26
27        mod = 10**9 + 7  # Modulus value
28        x = calc(nums)  # Calculation for original order
29        nums.reverse()  # Reverse the array
30        y = calc(nums)  # Calculation for reversed order
31
32        # Return the combined result
33        return (x + y + sum(nums)) % mod
34
1import java.util.Arrays;
2import java.util.HashMap;
3import java.util.Map;
4
5class Solution {
6    private final int MOD = (int) 1e9 + 7; // Constant for modulo operations
7
8    public int getSum(int[] nums) {
9        long x = calc(nums); // Calculation for original array
10
11        // Reverse the array
12        for (int i = 0, j = nums.length - 1; i < j; ++i, --j) {
13            int temp = nums[i];
14            nums[i] = nums[j];
15            nums[j] = temp;
16        }
17
18        long y = calc(nums); // Calculation for reversed array
19        long arraySum = Arrays.stream(nums).asLongStream().sum(); // Sum of array elements
20      
21        // Return the final result after modulo operation
22        return (int) ((x + y + arraySum) % MOD);
23    }
24
25    private long calc(int[] nums) {
26        int n = nums.length;
27        long[] left = new long[n]; // Left DP array
28        long[] right = new long[n]; // Right DP array
29        Map<Integer, Long> countMap = new HashMap<>(); // Map to count occurrences based on conditions
30
31        // Calculate left DP values
32        for (int i = 1; i < n; ++i) {
33            countMap.merge(nums[i - 1], 1 + countMap.getOrDefault(nums[i - 1] - 1, 0L), Long::sum);
34            left[i] = countMap.getOrDefault(nums[i] - 1, 0L);
35        }
36
37        countMap.clear(); // Clear map to reuse for right DP calculation
38
39        // Calculate right DP values
40        for (int i = n - 2; i >= 0; --i) {
41            countMap.merge(nums[i + 1], 1 + countMap.getOrDefault(nums[i + 1] + 1, 0L), Long::sum);
42            right[i] = countMap.getOrDefault(nums[i] + 1, 0L);
43        }
44
45        long result = 0;
46
47        // Compute the total result considering left, right, and nums[i]
48        for (int i = 0; i < n; ++i) {
49            result = (result + (left[i] + right[i] + left[i] * right[i] % MOD) * nums[i] % MOD) % MOD;
50        }
51
52        return result;
53    }
54}
55
1class Solution {
2public:
3    int getSum(std::vector<int>& nums) {
4        using ll = long long;
5        const int mod = 1e9 + 7;
6      
7        // Lambda function to calculate a specific value based on left and right accumulations
8        auto calc = [&](const std::vector<int>& nums) -> ll {
9            int n = nums.size();
10            std::vector<ll> left(n), right(n); // Vectors to store left and right accumulations
11            std::unordered_map<int, ll> countMap; // Map to count occurrences
12
13            // Calculate left accumulations
14            for (int i = 1; i < n; ++i) {
15                countMap[nums[i - 1]] += 1 + countMap[nums[i - 1] - 1];
16                left[i] = countMap[nums[i] - 1]; // Store result in left accumulations
17            }
18
19            // Clear the map to reuse it for right accumulations
20            countMap.clear();
21
22            // Calculate right accumulations
23            for (int i = n - 2; i >= 0; --i) {
24                countMap[nums[i + 1]] += 1 + countMap[nums[i + 1] + 1];
25                right[i] = countMap[nums[i] + 1]; // Store result in right accumulations
26            }
27
28            ll result = 0; // Initialize result
29            // Accumulate the final result from left, right, and their product
30            for (int i = 0; i < n; ++i) {
31                result = (result + (left[i] + right[i] + left[i] * right[i] % mod) * nums[i] % mod) % mod;
32            }
33            return result; // Return the calculated result
34        };
35
36        // Calculate the sum given the original order of nums
37        ll x = calc(nums);
38        // Reverse the nums vector
39        std::reverse(nums.begin(), nums.end());
40        // Calculate the sum given the reversed order of nums
41        ll y = calc(nums);
42        // Calculate total sum of elements in the nums vector
43        ll s = std::accumulate(nums.begin(), nums.end(), 0LL);
44        // Return the final sum modulated by mod
45        return static_cast<int>((x + y + s) % mod);
46    }
47};
48
1// Convert the given C++ logic to TypeScript and add explanatory comments
2
3// Define constants
4const mod: number = 1e9 + 7;
5
6// Lambda function to calculate a specific value based on left and right accumulations
7const calc = (nums: number[]): number => {
8    const n: number = nums.length;
9    const left: bigint[] = new Array(n).fill(0n);  // Array to store left accumulations
10    const right: bigint[] = new Array(n).fill(0n); // Array to store right accumulations
11    const countMap: Map<number, bigint> = new Map();  // Map to count occurrences
12
13    // Calculate left accumulations
14    for (let i = 1; i < n; i++) {
15        const prevCount = countMap.get(nums[i - 1] - 1) || 0n;
16        countMap.set(nums[i - 1], (countMap.get(nums[i - 1]) || 0n) + 1n + prevCount);
17        left[i] = countMap.get(nums[i] - 1) || 0n; // Store result in left accumulations
18    }
19
20    // Clear the map to reuse it for right accumulations
21    countMap.clear();
22
23    // Calculate right accumulations
24    for (let i = n - 2; i >= 0; i--) {
25        const nextCount = countMap.get(nums[i + 1] + 1) || 0n;
26        countMap.set(nums[i + 1], (countMap.get(nums[i + 1]) || 0n) + 1n + nextCount);
27        right[i] = countMap.get(nums[i] + 1) || 0n; // Store result in right accumulations
28    }
29
30    let result: bigint = 0n; // Initialize result
31
32    // Accumulate the final result from left, right, and their product
33    for (let i = 0; i < n; i++) {
34        const product = left[i] * right[i] % BigInt(mod);
35        result = (result + (left[i] + right[i] + product) * BigInt(nums[i]) % BigInt(mod)) % BigInt(mod);
36    }
37  
38    return Number(result); // Return the calculated result converted to a number
39};
40
41// Main function to calculate the sum
42function getSum(nums: number[]): number {
43    const x: number = calc(nums); // Calculate the sum given the original order of nums
44    nums.reverse(); // Reverse the nums array
45    const y: number = calc(nums); // Calculate the sum given the reversed order of nums
46    const s: number = nums.reduce((acc, num) => acc + num, 0); // Calculate total sum of elements in the nums vector
47
48    // Return the final sum modulated by mod
49    return (x + y + s) % mod;
50}
51

Time and Space Complexity

The time complexity of the code is O(n), where n is the length of the array nums. This is because the function calc iterates over the list nums twice in two separate loops, each running in O(n) time. Additionally, the zip and sum functions are applied on lists of size n, which also run in O(n) time. Since these operations are done in sequence, the overall time complexity remains O(n).

The space complexity of the code is O(n), due to the additional data structures used. Specifically, two lists left and right of size n are created. Moreover, the Counter object stores data proportional to the number of unique elements in nums, which, in the worst case, could lead to an additional space complexity of O(n). Therefore, the total space complexity is O(n).

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

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

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


Load More