LeetCode 3247 - Number of Subsequences with Odd Sum
The problem asks us to count how many subsequences of the given array have an odd sum. A subsequence is formed by choosing any subset of elements while preserving their original order. Unlike subarrays, subsequences do not need to be contiguous.
Difficulty: 🟡 Medium
Topics: Array, Math, Dynamic Programming, Combinatorics
Solution
Problem Understanding
The problem asks us to count how many subsequences of the given array have an odd sum.
A subsequence is formed by choosing any subset of elements while preserving their original order. Unlike subarrays, subsequences do not need to be contiguous. For an array of length n, there are 2^n total subsequences, including the empty subsequence.
We only care about subsequences whose sum of elements is odd. Since the total number of subsequences can become extremely large, the answer must be returned modulo 10^9 + 7.
The input consists of:
nums, an integer array1 <= nums.length <= 10^51 <= nums[i] <= 10^9
The large constraint on nums.length immediately tells us that any exponential approach is impossible. Even an O(n^2) solution would be too slow for 10^5 elements. We need a linear or near-linear algorithm.
The most important observation is that only the parity of each number matters:
- Even numbers do not change parity when added
- Odd numbers flip parity when added
For example:
- even + even = even
- even + odd = odd
- odd + odd = even
This means we do not need the actual sums, only whether the current sum is even or odd.
Some important edge cases include:
- Arrays containing only even numbers, where no odd-sum subsequence exists
- Arrays containing only odd numbers, where many subsequences become odd
- Arrays of size
1 - Very large arrays, where efficiency and modulo handling become essential
The problem guarantees all values are positive integers, so we never need to deal with negative parity behavior.
Approaches
Brute Force Approach
The brute force solution generates every possible subsequence, computes its sum, and checks whether that sum is odd.
Since each element can either be included or excluded, there are 2^n possible subsequences. For each subsequence, we may need up to O(n) work to compute its sum.
This approach is correct because it explicitly checks every possible subsequence and counts exactly those whose sum is odd.
However, it is far too slow for n = 10^5.
For example:
n = 20already gives over one million subsequencesn = 50gives more than one quadrillion subsequences
Therefore, brute force is computationally infeasible.
Optimal Dynamic Programming Approach
The key insight is that we only need to track parity.
At any point while processing the array, every subsequence belongs to one of two categories:
- subsequences with even sum
- subsequences with odd sum
We maintain two counts:
even_countodd_count
When we process a new number:
-
If the number is even:
-
adding it preserves parity
-
even subsequences stay even
-
odd subsequences stay odd
-
If the number is odd:
-
adding it flips parity
-
even subsequences become odd
-
odd subsequences become even
This allows us to update the counts in constant time per element.
Because we only store two integers and process the array once, the solution becomes extremely efficient.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n × 2^n) | O(2^n) | Generates all subsequences |
| Optimal | O(n) | O(1) | Tracks counts of even and odd subsequences |
Algorithm Walkthrough
- Initialize two counters:
even_count = 1odd_count = 0
We start with one even subsequence, the empty subsequence whose sum is 0.
2. Iterate through each number in nums.
3. If the current number is even:
-
Every existing even subsequence can either:
-
ignore the number
-
include the number
-
Both choices remain even.
Similarly, every odd subsequence remains odd.
Therefore:
new_even = even_count * 2new_odd = odd_count * 2
- If the current number is odd:
- Adding it flips parity.
- Existing even subsequences become odd when including the number.
- Existing odd subsequences become even when including the number.
The updates become:
new_even = even_count + odd_countnew_odd = even_count + odd_count
- Apply modulo
10^9 + 7after each update to avoid overflow. - Continue until all elements are processed.
- Return
odd_count, which represents the number of subsequences with odd sum.
Why it works
The algorithm maintains the invariant that after processing the first i elements:
even_countequals the number of subsequences with even sumodd_countequals the number of subsequences with odd sum
Every new element either preserves parity, if even, or flips parity, if odd. Since every subsequence either includes or excludes the current element, the transitions account for all possibilities exactly once. Therefore, after processing the entire array, odd_count is the correct answer.
Python Solution
from typing import List
class Solution:
def subsequenceCount(self, nums: List[int]) -> int:
MOD = 10**9 + 7
even_count = 1
odd_count = 0
for num in nums:
if num % 2 == 0:
even_count = (even_count * 2) % MOD
odd_count = (odd_count * 2) % MOD
else:
total = (even_count + odd_count) % MOD
even_count = total
odd_count = total
return odd_count
The implementation directly follows the dynamic programming logic described earlier.
We initialize even_count to 1 because the empty subsequence has an even sum of 0. Initially, there are no odd-sum subsequences.
For each number:
- If the number is even, parity does not change when adding it to an existing subsequence. Therefore, both counts simply double.
- If the number is odd, parity flips when including the number, so both new counts become the total number of previous subsequences.
The modulo operation is applied during updates to ensure values stay within bounds.
Finally, the method returns odd_count, which represents all odd-sum subsequences.
Go Solution
func subsequenceCount(nums []int) int {
const MOD int = 1e9 + 7
evenCount := 1
oddCount := 0
for _, num := range nums {
if num%2 == 0 {
evenCount = (evenCount * 2) % MOD
oddCount = (oddCount * 2) % MOD
} else {
total := (evenCount + oddCount) % MOD
evenCount = total
oddCount = total
}
}
return oddCount
}
The Go implementation is structurally identical to the Python version.
Go requires explicit integer declarations, so we define MOD as an integer constant. Since the values remain within modulo bounds throughout execution, standard int arithmetic is sufficient on LeetCode's platform.
The algorithm still uses constant extra space and processes the array in a single pass.
Worked Examples
Example 1
Input:
nums = [1, 1, 1]
Initial state:
| Step | Number | even_count | odd_count |
|---|---|---|---|
| Start | - | 1 | 0 |
Process first 1:
- Odd number
total = 1 + 0 = 1
| Step | Number | even_count | odd_count |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
Process second 1:
- Odd number
total = 1 + 1 = 2
| Step | Number | even_count | odd_count |
|---|---|---|---|
| 2 | 1 | 2 | 2 |
Process third 1:
- Odd number
total = 2 + 2 = 4
| Step | Number | even_count | odd_count |
|---|---|---|---|
| 3 | 1 | 4 | 4 |
Final answer:
4
Example 2
Input:
nums = [1, 2, 2]
Initial state:
| Step | Number | even_count | odd_count |
|---|---|---|---|
| Start | - | 1 | 0 |
Process 1:
- Odd number
total = 1
| Step | Number | even_count | odd_count |
|---|---|---|---|
| 1 | 1 | 1 | 1 |
Process 2:
- Even number
- Counts double
| Step | Number | even_count | odd_count |
|---|---|---|---|
| 2 | 2 | 2 | 2 |
Process second 2:
- Even number
- Counts double again
| Step | Number | even_count | odd_count |
|---|---|---|---|
| 3 | 2 | 4 | 4 |
Final answer:
4
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Single pass through the array |
| Space | O(1) | Only two counters are stored |
The algorithm processes each element exactly once and performs only constant-time operations during each iteration. No additional arrays, maps, or recursion are used, so the auxiliary space remains constant.
Test Cases
from typing import List
class Solution:
def subsequenceCount(self, nums: List[int]) -> int:
MOD = 10**9 + 7
even_count = 1
odd_count = 0
for num in nums:
if num % 2 == 0:
even_count = (even_count * 2) % MOD
odd_count = (odd_count * 2) % MOD
else:
total = (even_count + odd_count) % MOD
even_count = total
odd_count = total
return odd_count
sol = Solution()
assert sol.subsequenceCount([1, 1, 1]) == 4 # all odd numbers
assert sol.subsequenceCount([1, 2, 2]) == 4 # one odd, two evens
assert sol.subsequenceCount([2, 4, 6]) == 0 # all even numbers
assert sol.subsequenceCount([1]) == 1 # single odd element
assert sol.subsequenceCount([2]) == 0 # single even element
assert sol.subsequenceCount([1, 3]) == 2 # two odd subsequences
assert sol.subsequenceCount([1, 2]) == 2 # odd subsequences double with even
assert sol.subsequenceCount([1, 1, 2]) == 4 # mixed parity
assert sol.subsequenceCount([5, 7, 9, 11]) == 8 # all odd values
assert sol.subsequenceCount([10**9] * 5) == 0 # large even values
| Test | Why |
|---|---|
[1,1,1] |
Verifies repeated odd parity flips |
[1,2,2] |
Verifies even numbers preserve parity |
[2,4,6] |
Ensures no odd subsequences exist |
[1] |
Smallest odd input |
[2] |
Smallest even input |
[1,3] |
Tests multiple odd elements |
[1,2] |
Ensures even numbers double counts |
[1,1,2] |
Mixed parity interaction |
[5,7,9,11] |
Larger all-odd scenario |
[10^9,10^9,10^9,10^9,10^9] |
Large even values, parity-only logic |
Edge Cases
All Numbers Are Even
An array like [2, 4, 6, 8] is an important edge case because adding even numbers never changes parity. Every subsequence sum remains even, so the correct answer is 0.
A buggy implementation might accidentally count the empty subsequence or mishandle parity transitions. This solution correctly keeps odd_count at zero throughout processing.
Single Element Arrays
Arrays of length one are common sources of off-by-one errors.
For [1], the only non-empty subsequence is odd, so the answer is 1.
For [2], the only non-empty subsequence is even, so the answer is 0.
The initialization with even_count = 1 correctly represents the empty subsequence and allows these minimal cases to work naturally.
Large Inputs
The constraint allows up to 10^5 elements, meaning the number of subsequences grows astronomically large.
Without modulo operations, integer overflow would occur quickly. The implementation applies modulo arithmetic after every update, ensuring correctness even for massive inputs.
The linear runtime also guarantees the solution remains efficient at maximum input size.