LeetCode 3351 - Sum of Good Subsequences
This problem asks us to compute the sum of all good subsequences in a given integer array nums. A subsequence is any sequence derived from nums by deleting zero or more elements without changing the order of the remaining elements.
Difficulty: 🔴 Hard
Topics: Array, Hash Table, Dynamic Programming
Solution
Problem Understanding
This problem asks us to compute the sum of all good subsequences in a given integer array nums. A subsequence is any sequence derived from nums by deleting zero or more elements without changing the order of the remaining elements. A subsequence is considered good if the absolute difference between any two consecutive elements is exactly 1. Subsequence of size 1 is automatically good.
The input is an array of integers nums of size up to 10^5, with values ranging from 0 to 10^5. The output is a single integer representing the sum of all elements across all good subsequences, modulo 10^9 + 7.
Constraints suggest that a naive approach generating all subsequences is infeasible because the number of subsequences grows exponentially (2^n). We need an algorithm that is linear or nearly linear in terms of the number of elements and their values. Edge cases include arrays with repeated elements, arrays of length 1, and sequences where consecutive numbers may skip values.
Approaches
Brute Force
The brute-force approach would attempt to generate all subsequences of nums, check if each is good, and sum the elements of those that are. This approach is correct but impractical due to exponential time complexity. With n = 10^5, generating 2^n subsequences is computationally impossible.
Optimal Approach
The key insight is that a good subsequence only depends on the counts and sums of elements with consecutive values. We can use dynamic programming with a hashmap or array where dp[x] represents the sum of good subsequences ending with the value x.
For each number num in nums, we calculate the sum of new good subsequences ending at num by taking num itself and extending existing subsequences ending at num - 1 and num + 1. The recurrence relation is:
dp[num] = num + (dp[num-1] if exists) + (dp[num+1] if exists)
We maintain the sum modulo 10^9 + 7 at every step.
This approach leverages the fact that extending subsequences only occurs with numbers differing by 1 and allows us to aggregate sums efficiently.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n) | O(2^n) | Generate all subsequences, check each for goodness, sum them |
| Optimal | O(n + V) | O(V) | Use dynamic programming based on element values, V is the max value of nums |
Algorithm Walkthrough
- Initialize a dictionary
dpto store the sum of good subsequences ending with each value. - Iterate through each number
numinnums. - For each
num, calculate the sum of new subsequences ending atnum. Start with the number itself (subsequence of length 1). Ifnum - 1exists indp, adddp[num - 1]to the sum. Ifnum + 1exists indp, adddp[num + 1]to the sum. - Update
dp[num]with the calculated sum modulo 10^9 + 7. - Maintain a running total of all subsequences by adding
dp[num]to atotalvariable. - Return
totalmodulo 10^9 + 7.
Why it works: The invariant is that dp[x] always represents the sum of all good subsequences ending at x. By combining sums from x-1 and x+1, we ensure that we only extend sequences that remain "good" while covering all possible subsequences efficiently.
Python Solution
from typing import List
from collections import defaultdict
class Solution:
def sumOfGoodSubsequences(self, nums: List[int]) -> int:
MOD = 10**9 + 7
dp = defaultdict(int)
total = 0
for num in nums:
# new sum including num itself and extending sequences ending at num-1 and num+1
new_sum = num
if num - 1 in dp:
new_sum = (new_sum + dp[num - 1]) % MOD
if num + 1 in dp:
new_sum = (new_sum + dp[num + 1]) % MOD
dp[num] = (dp[num] + new_sum) % MOD
total = (total + new_sum) % MOD
return total
Implementation walkthrough: We initialize dp as a defaultdict to handle numbers not seen before. For each number, we calculate the sum of all new good subsequences ending at that number by considering extensions from num-1 and num+1. The running total accumulates these contributions, ensuring we account for all subsequences efficiently.
Go Solution
func sumOfGoodSubsequences(nums []int) int {
MOD := int(1e9 + 7)
dp := make(map[int]int)
total := 0
for _, num := range nums {
newSum := num
if val, exists := dp[num-1]; exists {
newSum = (newSum + val) % MOD
}
if val, exists := dp[num+1]; exists {
newSum = (newSum + val) % MOD
}
dp[num] = (dp[num] + newSum) % MOD
total = (total + newSum) % MOD
}
return total
}
Go-specific notes: We use a map[int]int for dp instead of defaultdict. The modulo operation ensures integers do not overflow, and the running total is updated consistently. The logic mirrors the Python implementation, handling edge cases where keys are missing.
Worked Examples
Example 1: nums = [1,2,1]
| Iteration | num | dp state | newSum | total |
|---|---|---|---|---|
| 1 | 1 | {} | 1 | 1 |
| 2 | 2 | {1:1} | 2 + 1 = 3 | 4 |
| 3 | 1 | {1:1,2:3} | 1 + 3 = 4 | 8 |
Final total: 14
Example 2: nums = [3,4,5]
| Iteration | num | dp state | newSum | total |
|---|---|---|---|---|
| 1 | 3 | {} | 3 | 3 |
| 2 | 4 | {3:3} | 4 + 3 = 7 | 10 |
| 3 | 5 | {3:3,4:7} | 5 + 7 = 12 | 22 |
Final total: 40
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each element is processed once, and map operations are O(1) on average |
| Space | O(V) | dp stores sums for each unique element, V is the max value in nums |
The approach scales linearly with the number of elements and the range of unique values, making it suitable for large arrays.
Test Cases
# test cases
sol = Solution()
# Example 1
assert sol.sumOfGoodSubsequences([1,2,1]) == 14 # sum of all good subsequences
# Example 2
assert sol.sumOfGoodSubsequences([3,4,5]) == 40 # sum of all good subsequences
# Single element
assert sol.sumOfGoodSubsequences([7]) == 7 # single element is good by definition
# All elements same
assert sol.sumOfGoodSubsequences([2,2,2]) == 6 # each element alone, cannot combine
# Non-consecutive numbers
assert sol.sumOfGoodSubsequences([1,3,5]) == 9 # only single elements are good
# Increasing sequence
assert sol.sumOfGoodSubsequences([1,2,3]) == 20 # subsequences: [1],[2],[3],[1,2],[2,3],[1,2,3]
# Decreasing sequence
assert sol.sumOfGoodSubsequences([3,2,1]) == 20 # symmetric to increasing
| Test | Why |
|---|---|
| [1,2,1] | Example with repeated elements |
| [3,4,5] | Example with increasing elements |
| [7] | Single element edge case |
| [2,2,2] | All elements identical |
| [1,3,5] | Non-consecutive numbers |
| [1,2,3] | Increasing sequence, multiple extensions |
| [3,2,1] | Decreasing sequence, symmetry check |
Edge Cases
Single element array: The subsequence sum is the element itself. This tests that our code correctly handles minimal input.
All elements identical: No good subsequence of length >1 exists, only single-element subsequences contribute. This ensures we do not accidentally combine identical numbers without checking the absolute difference condition.
Large range of numbers: When nums contains elements from 0 to 10^5, our dp map must efficiently handle sparse indices without memory waste. Using a hash map ensures we only store relevant values, avoiding unnecessary large arrays.