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 all1 <= i < n
.arr[i] - arr[i - 1] == -1
for all1 <= 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.
-
Understanding Contributions: Each element
nums[i]
contributes to the value of consecutive subsequences. By calculating how many such subsequences includenums[i]
, we can determine its contribution. -
Identifying Patterns: We focus on two main patterns: strictly increasing and strictly decreasing subsequences.
-
Two-Pass Calculation: For each pattern, we use two arrays,
left
andright
, 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.
-
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:
-
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.
- We need to figure out how frequently each
-
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.
- A function
-
Two Supporting Arrays:
left
: This array is used to count how many strictly increasing subsequences end with the elementnums[i] - 1
before eachnums[i]
.right
: This array counts how many strictly increasing subsequences start with the elementnums[i] + 1
after eachnums[i]
.
-
Implementation Steps in
calc(nums)
:- Initialize two arrays
left
andright
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.
- Initialize two arrays
-
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.
- First, calculate contributions using
-
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 EvaluatorExample 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.
- Increasing subsequences:
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 thecalc(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.
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!