3351. Good Subsequences Sum
Sum of Good Subsequences
Counting and summing subsequences share a lot in spirit with doing the same for subarrays.
Before diving into problem 3351, let me briefly cover similarities and differences.
Subarrays vs. Subsequences: Similar but Different
A. Similar: Each Visited Element Tries to Be the "Tail"
When searching for subarrays, we let each visited index try to serve as subarray's tail,
asking how many subarrays end at index .
By the same logic, we can let each visited index serve as subsequence's tail.
This keeps the loop logic clean and avoids double-counting.
B. Different: How to Extend and Pass Down the Lineage
One of the main divergences. Subarrays require strict index continuity:
index can only extend subarrays that end at index .
But subsequences allow skipping indices. Any relative order that's preserved is valid:
index can connect to any subsequence ending at indices .
The Pattern in Problem 3351
For each currently visited index , we look at how many subsequences with tail value or have appeared in the previous iterations,
since the problem states that adjacent elements in each subsequence must have an absolute difference of exactly 1.
Say among the previous iterations there are:
I. subsequences with tail value , with total sum
II. subsequences with tail value , with total sum
We can immediately conclude two facts:
-
The number of subsequences with tail at index is
-
The number of subsequences with tail value of net increases by
Note that statements 1 and 2 use slightly different phrasing, because there is only one index ,
but before index , subsequences with tail value of may have already formed.
Take a moment to digest the difference between these two statements before you read on~~
-------------No rush, take your time-------------
Back to . Where does come from?
Don't forget: at index , we also have the right to start fresh, letting form a subsequence on its own.
With counting handled, summing follows naturally. We now have:
(1). Count of subsequences with tail value of net increases by
(2). Total sum of subsequences with tail value of net increases by
This is a bit tricky. I've used three pairs of parentheses to explain each part 😄
a. First bracket: index extends all subsequences with tail value of .
Index inherits their total sum ,
and is appended to all subsequences with tail value of .
b. Middle bracket: index extends all subsequences with tail value of .
Index inherits their total sum ,
and is appended to all subsequences with tail value of .
c. Last bracket: index lets form a standalone subsequence.
So parts a, b, and c together give the net increase in total sum for subsequences with tail value of .
After the full traversal, we sum up the total sums across all distinct tail values,
then apply modulo as required.
One thing to note: the number of subsequences is on the order of ,
and throughout our pipeline, we use subsequence counts to compute subsequence sums,
so we must apply modulo to both counts and sums to prevent overflow.

Time and space complexity: both .
- C++
- Python
#include <unordered_map>
#include <vector>
using namespace std;
int sumGoodSubsequences(vector<int>& nums) // LeetCode Q.3351.
{
long long modulo = pow(10, 9) + 7; // Long long prevents overflow.
unordered_map<int, long long> subseqCounts, subseqSums;
for (const auto& num : nums) {
for (const auto& validTail : {num - 1, num + 1}) {
long long prevCount = subseqCounts[validTail];
subseqCounts[num] += prevCount;
long long prevSum = subseqSums[validTail];
subseqSums[num] += prevSum + num * prevCount;
}
// Counts & sums are O(2^n) so must do modulo for future operations.
subseqCounts[num] += 1;
if (subseqCounts[num] > modulo)
subseqCounts[num] %= modulo;
subseqSums[num] += num;
if (subseqSums[num] > modulo)
subseqSums[num] %= modulo;
}
long long goodSubseqSum = 0;
for (const auto& pair : subseqSums)
goodSubseqSum += pair.second;
return goodSubseqSum % modulo; // Control value size.
}
def sum_good_subsequences(nums: list[int]) -> int: # LeetCode Q.3351.
modulo = 10 ** 9 + 7 # Prevents overflow.
unique_nums = set(nums)
subseqCounts: dict[int, int] = dict(
zip(unique_nums, [0] * len(unique_nums))
)
subseqSums: dict[int, int] = dict(
zip(unique_nums, [0] * len(unique_nums))
)
for num in nums:
for validTail in (num - 1, num + 1):
prev_count = subseqCounts.get(validTail, 0)
subseqCounts[num] += prev_count
prev_sum = subseqSums.get(validTail, 0)
subseqSums[num] += prev_sum + num * prev_count
# Counts & sums are O(2^n) so must do modulo for future operations.
subseqCounts[num] += 1
if subseqCounts[num] > modulo: subseqCounts[num] %= modulo
subseqSums[num] += num
if subseqSums[num] > modulo: subseqSums[num] %= modulo
return sum(subseqSums.values()) % modulo # Control value magnitude.
Follow-up Problem
If the problem asked for subarrays instead of subsequences, how would the optimal time and space complexity change?