LeetCode 3299 - Sum of Consecutive Subsequences
We are given an array nums, and we must consider all non-empty subsequences of that array. A subsequence preserves the original order of elements, but elements do not need to be contiguous.
Difficulty: 🔴 Hard
Topics: Array, Hash Table, Dynamic Programming
Solution
LeetCode 3299 - Sum of Consecutive Subsequences
Problem Understanding
We are given an array nums, and we must consider all non-empty subsequences of that array.
A subsequence preserves the original order of elements, but elements do not need to be contiguous.
A subsequence is considered consecutive if every adjacent pair differs by exactly +1 or exactly -1 throughout the entire subsequence.
For example:
[1, 2, 3]is consecutive because every step is+1.[5, 4, 3]is consecutive because every step is-1.[1, 2, 1]is not consecutive because the differences are+1and-1.[8, 6]is not consecutive because the difference is-2.
The value of a subsequence is simply the sum of its elements.
Our task is to compute the sum of the values of every consecutive non-empty subsequence and return the result modulo 10^9 + 7.
The constraints are important:
ncan be as large as100,000.- Values can be as large as
100,000.
Because the number of subsequences is exponential, it is impossible to enumerate all subsequences directly.
Some important observations:
- Every single element forms a valid consecutive subsequence.
- A valid subsequence of length greater than one must be entirely increasing by
1at every step or entirely decreasing by1at every step. - The same subsequence cannot be both increasing and decreasing unless its length is
1.
These observations lead directly to an efficient dynamic programming solution.
Approaches
Brute Force
The most direct approach is to generate every subsequence.
For each subsequence:
- Check whether all adjacent differences are
+1. - Check whether all adjacent differences are
-1. - If either condition holds, add the subsequence sum to the answer.
This approach is correct because it explicitly examines every possible subsequence.
Unfortunately, an array of length n has 2^n subsequences. Even for n = 50, this becomes completely infeasible, and the actual constraint is 100,000.
Therefore, a brute-force solution cannot be used.
Key Insight
A consecutive subsequence is completely determined by its last value and whether it is:
- increasing by
1, or - decreasing by
1.
Suppose we process the array from left to right.
When we encounter a value x:
- Any increasing consecutive subsequence ending with value
x - 1can be extended by appendingx. - Any decreasing consecutive subsequence ending with value
x + 1can be extended by appendingx.
This suggests maintaining dynamic programming states indexed by value.
Instead of storing actual subsequences, we store:
- how many valid subsequences end with a particular value,
- the total sum of values of those subsequences.
Using these aggregate statistics allows us to extend all relevant subsequences in constant time.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n · n) | O(n) | Enumerates all subsequences |
| Optimal DP + Hash Maps | O(n) | O(U) | Tracks counts and sums by ending value |
Here U is the number of distinct values encountered.
Algorithm Walkthrough
State Definition
Maintain four hash maps:
inc_count[v]= number of increasing consecutive subsequences ending with valuevinc_sum[v]= total value sum of those increasing subsequencesdec_count[v]= number of decreasing consecutive subsequences ending with valuevdec_sum[v]= total value sum of those decreasing subsequences
Processing a Value x
For increasing subsequences:
We can create:
- The singleton subsequence
[x]. - Any increasing subsequence ending with
x - 1, extended byx.
Therefore:
new_inc_count = 1 + inc_count[x - 1]
For sums:
- Singleton contributes
x. - Every extended subsequence gains an additional
x.
So:
new_inc_sum =
x
+ inc_sum[x - 1]
+ inc_count[x - 1] * x
Decreasing Subsequences
Similarly:
new_dec_count = 1 + dec_count[x + 1]
and
new_dec_sum =
x
+ dec_sum[x + 1]
+ dec_count[x + 1] * x
Add Contributions
Every increasing subsequence ending at the current position contributes through new_inc_sum.
Every decreasing subsequence ending at the current position contributes through new_dec_sum.
However, the singleton [x] appears in both groups.
Therefore:
answer += new_inc_sum + new_dec_sum - x
The subtraction removes the duplicate singleton contribution.
Update DP Tables
Store the newly created subsequences:
inc_count[x] += new_inc_count
inc_sum[x] += new_inc_sum
dec_count[x] += new_dec_count
dec_sum[x] += new_dec_sum
Why it Works
The key invariant is that after processing a prefix of the array:
inc_count[v]andinc_sum[v]represent all increasing consecutive subsequences ending with valuev.dec_count[v]anddec_sum[v]represent all decreasing consecutive subsequences ending with valuev.
When a new value x is processed, every valid subsequence that can legally extend to x must end with x - 1 for increasing sequences or x + 1 for decreasing sequences. The recurrence enumerates exactly those possibilities and no others.
Thus every valid consecutive subsequence is counted exactly once, except singletons which are counted once in each direction and corrected by subtracting x.
Python Solution
from typing import List
from collections import defaultdict
class Solution:
def getSum(self, nums: List[int]) -> int:
MOD = 10**9 + 7
inc_count = defaultdict(int)
inc_sum = defaultdict(int)
dec_count = defaultdict(int)
dec_sum = defaultdict(int)
answer = 0
for x in nums:
prev_inc_count = inc_count[x - 1]
prev_inc_sum = inc_sum[x - 1]
new_inc_count = (1 + prev_inc_count) % MOD
new_inc_sum = (
x
+ prev_inc_sum
+ prev_inc_count * x
) % MOD
prev_dec_count = dec_count[x + 1]
prev_dec_sum = dec_sum[x + 1]
new_dec_count = (1 + prev_dec_count) % MOD
new_dec_sum = (
x
+ prev_dec_sum
+ prev_dec_count * x
) % MOD
answer = (
answer
+ new_inc_sum
+ new_dec_sum
- x
) % MOD
inc_count[x] = (inc_count[x] + new_inc_count) % MOD
inc_sum[x] = (inc_sum[x] + new_inc_sum) % MOD
dec_count[x] = (dec_count[x] + new_dec_count) % MOD
dec_sum[x] = (dec_sum[x] + new_dec_sum) % MOD
return answer % MOD
The implementation directly follows the dynamic programming formulation.
The four hash maps store counts and value sums for increasing and decreasing subsequences separately.
For each number x, we first compute all new increasing subsequences by extending subsequences ending with x - 1. We then compute all new decreasing subsequences by extending subsequences ending with x + 1.
The newly formed subsequences contribute to the final answer immediately. Because singleton subsequences are included in both directions, we subtract x once to avoid double counting.
Finally, the DP tables are updated so future elements can extend these newly created subsequences.
Go Solution
func getSum(nums []int) int {
const MOD int64 = 1000000007
incCount := map[int]int64{}
incSum := map[int]int64{}
decCount := map[int]int64{}
decSum := map[int]int64{}
var answer int64 = 0
for _, x := range nums {
v := int64(x)
prevIncCount := incCount[x-1]
prevIncSum := incSum[x-1]
newIncCount := (1 + prevIncCount) % MOD
newIncSum := (v + prevIncSum + prevIncCount*v) % MOD
prevDecCount := decCount[x+1]
prevDecSum := decSum[x+1]
newDecCount := (1 + prevDecCount) % MOD
newDecSum := (v + prevDecSum + prevDecCount*v) % MOD
answer = (answer + newIncSum + newDecSum - v) % MOD
if answer < 0 {
answer += MOD
}
incCount[x] = (incCount[x] + newIncCount) % MOD
incSum[x] = (incSum[x] + newIncSum) % MOD
decCount[x] = (decCount[x] + newDecCount) % MOD
decSum[x] = (decSum[x] + newDecSum) % MOD
}
return int(answer)
}
The Go version uses map[int]int64 instead of Python dictionaries.
All arithmetic is performed using int64 because the intermediate values can become very large before taking the modulo. Go maps return the zero value for missing keys, which naturally matches the DP recurrence.
Worked Examples
Example 1
nums = [1, 2]
Processing 1
| Variable | Value |
|---|---|
| new_inc_count | 1 |
| new_inc_sum | 1 |
| new_dec_count | 1 |
| new_dec_sum | 1 |
| answer | 1 |
DP state:
| Map | Content |
|---|---|
| inc_count | {1: 1} |
| inc_sum | {1: 1} |
| dec_count | {1: 1} |
| dec_sum | {1: 1} |
Processing 2
Increasing extensions come from value 1.
| Variable | Value |
|---|---|
| prev_inc_count | 1 |
| prev_inc_sum | 1 |
| new_inc_sum | 5 |
The increasing subsequences are:
[2] -> 2
[1,2] -> 3
Total = 5.
No decreasing extension exists.
| Variable | Value |
|---|---|
| new_dec_sum | 2 |
Answer update:
answer += 5 + 2 - 2
Final answer:
6
Example 2
nums = [1, 4, 2, 3]
After processing 1
Answer = 1
After processing 4
Answer = 5
Current valid subsequences:
[1]
[4]
After processing 2
Increasing extension from value 1:
[2]
[1,2]
Contribution:
2 + 3 = 5
Answer becomes:
10
After processing 3
Increasing extension from value 2:
[3]
[2,3]
[1,2,3]
Contribution:
3 + 5 + 6 = 14
Decreasing extension from value 4:
[4,3]
Contribution:
7
Total added:
14 + 10 - 3 = 21
Final answer:
31
Matching the expected output.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Constant amount of work per element |
| Space | O(U) | Hash maps store information for distinct values |
The algorithm processes each array element exactly once. Every lookup and update in the hash maps is expected O(1), so the total running time is linear in the size of the input. The space usage is proportional to the number of distinct values that appear.
Test Cases
sol = Solution()
assert sol.getSum([1, 2]) == 6 # example 1
assert sol.getSum([1, 4, 2, 3]) == 31 # example 2
assert sol.getSum([5]) == 5 # single element
assert sol.getSum([1, 3, 5]) == 9 # only singletons
assert sol.getSum([3, 2, 1]) == 20 # decreasing chain
assert sol.getSum([1, 2, 3]) == 20 # increasing chain
assert sol.getSum([1, 1]) == 4 # duplicate values
assert sol.getSum([1, 2, 1]) == 10 # mixed directions
assert sol.getSum([2, 2, 2]) == 6 # all equal
assert sol.getSum([1, 2, 2, 3]) == 32 # duplicate middle value
assert sol.getSum([100000]) == 100000 # maximum value boundary
Test Summary
| Test | Why |
|---|---|
[1,2] |
Smallest non-trivial increasing case |
[1,4,2,3] |
Official example with both directions |
[5] |
Single-element subsequence |
[1,3,5] |
No extensions possible |
[3,2,1] |
Pure decreasing chain |
[1,2,3] |
Pure increasing chain |
[1,1] |
Duplicate values |
[1,2,1] |
Direction changes in the array |
[2,2,2] |
All elements identical |
[1,2,2,3] |
Multiple ways to extend through duplicates |
[100000] |
Maximum value boundary |
Edge Cases
Single Element Array
When the array contains only one element, the only valid subsequence is the element itself. A common mistake is to focus exclusively on extension logic and forget that every element creates a singleton subsequence. The recurrence explicitly includes the singleton through the +1 count and the +x sum contribution.
Duplicate Values
Values such as [1,1,1] cannot extend one another because consecutive subsequences require a difference of exactly 1 or -1. The algorithm only looks up x-1 and x+1, so equal values never create invalid extensions.
Multiple Occurrences of the Same Number
Consider [1,2,1,2]. Different positions can generate different subsequences ending with the same value. Storing only one state per value would lose information. The solution accumulates counts and sums for all subsequences ending with a particular value, ensuring every valid subsequence remains available for future extensions.
Large Inputs
The number of valid subsequences can be enormous, far exceeding standard integer limits. The implementation performs all updates modulo 10^9 + 7, preventing overflow and satisfying the problem requirements.