LeetCode 2902 - Count of Sub-Multisets With Bounded Sum
The problem asks us to count all possible sub-multisets of an array nums such that the sum of elements in each sub-multiset is within a given inclusive range [l, r]. A sub-multiset is like a subset, but it accounts for repeated elements in the original array.
Difficulty: 🔴 Hard
Topics: Array, Hash Table, Dynamic Programming, Sliding Window
Solution
Problem Understanding
The problem asks us to count all possible sub-multisets of an array nums such that the sum of elements in each sub-multiset is within a given inclusive range [l, r]. A sub-multiset is like a subset, but it accounts for repeated elements in the original array. That is, each element can appear up to as many times as it occurs in nums. Importantly, the order of elements does not matter, and duplicate sub-multisets (after sorting) are considered the same.
The input is a non-empty array of non-negative integers and two integers defining the sum range. The output is the count of valid sub-multisets modulo $10^9 + 7$. Constraints like nums.length <= 2 * 10^4 and sum(nums) <= 2 * 10^4 indicate that the total sum of elements is bounded, which hints at a dynamic programming approach based on sum rather than subset enumeration.
Edge cases to watch for include arrays with all zeros, arrays with a single element, and ranges [l, r] that include or exclude 0.
Approaches
The brute-force approach would involve generating all possible sub-multisets of nums, calculating the sum of each, and counting those in the range [l, r]. This is correct in principle, but the number of possible sub-multisets grows exponentially with nums.length, making this approach infeasible.
The key insight is that the sum of elements is bounded by sum(nums) <= 2 * 10^4. This allows a dynamic programming approach where we keep track of the number of ways to achieve each sum. By counting the number of ways to achieve sums up to r and subtracting those below l, we can compute the answer efficiently. To handle repeated elements, we consider each unique number and its frequency in the array, updating the DP table to account for selecting 0 to occ[x] occurrences.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n) | O(n) | Enumerate all sub-multisets; infeasible for n ~ 2 * 10^4 |
| Optimal (DP with bounded sum) | O(n * totalSum) | O(totalSum) | Use DP array where dp[s] counts ways to form sum s, iterate over unique numbers and their counts |
Algorithm Walkthrough
- Count the occurrences of each unique element in
numsusing a frequency map. This allows us to handle repeated elements efficiently. - Initialize a DP array
dpof sizetotalSum + 1, wheredp[s]represents the number of sub-multisets with sum exactlys. Start withdp[0] = 1since the empty multiset has sum 0. - For each unique number
numand its frequencyfreq, update the DP array to include the number of ways we can pick0tofreqcopies ofnum. This can be done using a temporary array or by iterating backward over the sums to prevent double-counting. - After processing all numbers, sum
dp[s]for allsin the range[l, r]. - Return the sum modulo $10^9 + 7$.
Why it works: The DP array invariant is that after processing k unique numbers, dp[s] counts all sub-multisets using only the first k numbers that sum to s. By iterating over numbers and frequencies carefully, we ensure that all valid combinations are counted exactly once.
Python Solution
from collections import Counter
from typing import List
class Solution:
MOD = 10**9 + 7
def countSubMultisets(self, nums: List[int], l: int, r: int) -> int:
total_sum = sum(nums)
freq = Counter(nums)
dp = [0] * (total_sum + 1)
dp[0] = 1 # empty multiset
for num, count in freq.items():
# update dp for current number
new_dp = dp[:]
for s in range(total_sum + 1):
if dp[s]:
for k in range(1, count + 1):
if s + k * num > total_sum:
break
new_dp[s + k * num] = (new_dp[s + k * num] + dp[s]) % self.MOD
dp = new_dp
result = sum(dp[l:r+1]) % self.MOD
return result
Explanation: We first count frequencies, then initialize dp[0] = 1 for the empty set. For each unique number, we iterate through current sums and consider picking 1 to count copies, updating the DP array accordingly. Finally, we sum the values for sums in [l, r].
Go Solution
package main
func countSubMultisets(nums []int, l int, r int) int {
const MOD = 1_000_000_007
totalSum := 0
freq := map[int]int{}
for _, num := range nums {
freq[num]++
totalSum += num
}
dp := make([]int, totalSum+1)
dp[0] = 1
for num, count := range freq {
newDp := make([]int, totalSum+1)
copy(newDp, dp)
for s := 0; s <= totalSum; s++ {
if dp[s] > 0 {
for k := 1; k <= count; k++ {
if s + k*num > totalSum {
break
}
newDp[s + k*num] = (newDp[s + k*num] + dp[s]) % MOD
}
}
}
dp = newDp
}
result := 0
for s := l; s <= r; s++ {
if s <= totalSum {
result = (result + dp[s]) % MOD
}
}
return result
}
Go-specific notes: In Go, slices are initialized with make, and we copy the old DP array to a new one for updates. We handle modulo arithmetic carefully to avoid integer overflow.
Worked Examples
Example 1: nums = [1,2,2,3], l = 6, r = 6
Frequency map: {1:1, 2:2, 3:1}.
DP evolves as follows:
| Step | DP array snapshot | Explanation |
|---|---|---|
| Initial | [1,0,0,0,0,0,0,0] |
Only empty set |
| After 1 | [1,1,0,0,0,0,0,0] |
Include 1 once |
| After 2 | [1,1,1,1,2,1,1,0] |
Include 2 zero to two times |
| After 3 | [1,1,1,1,2,1,2,1] |
Include 3 once |
| Sum for 6 | dp[6] = 1 |
Only {1,2,3} |
Example 2: nums = [2,1,4,2,7], l = 1, r = 5
DP sums for [1,5] total 7, matching the explanation.
Example 3: nums = [1,2,1,3,5,2], l = 3, r = 5
DP sums for [3,5] total 9, matching the explanation.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n * totalSum) | Each unique number is iterated, for each sum up to totalSum, and for each occurrence count |
| Space | O(totalSum) | DP array stores counts for all sums from 0 to totalSum |
The algorithm is efficient because totalSum is bounded by $2 * 10^4$, making the DP feasible even for large nums.
Test Cases
# Provided examples
assert Solution().countSubMultisets([1,2,2,3], 6, 6) == 1 # Example 1
assert Solution().countSubMultisets([2,1,4,2,7], 1, 5) == 7 # Example 2
assert Solution().countSubMultisets([1,2,1,3,5,2], 3, 5) == 9 # Example 3
# Edge cases
assert Solution().countSubMultisets([0,0,0], 0, 0) == 8 # all subsets sum 0
assert Solution().countSubMultisets([1], 0, 1) == 2 # empty set and {1}
assert Solution().countSubMultisets([1,1,1,1], 2, 3) == 5 # combinations of 2 or 3 ones
assert Solution().countSubMultisets([10000,10000,10000], 10000, 20000) == 3 # large numbers