Skip to content

Commit a847f28

Browse files
committed
Added problems: 2902 - Count of Sub-Multisets with Bounded Sum
1 parent 911d513 commit a847f28

File tree

3 files changed

+177
-0
lines changed

3 files changed

+177
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Note that this license applies only to my solution code and not to the LeetCode
6969
| 🟠 2442. [Count Number of Distinct Integers After Reverse Operations](https://leetcode.com/problems/count-number-of-distinct-integers-after-reverse-operations/) | [🦀](src/problems/p2442_count_number_of_distinct_integers_after_reverse_operations.rs) | Hash Set |
7070
| 🟠 2471. [Minimum Number of Operations to Sort a Binary Tree by Level](https://leetcode.com/problems/minimum-number-of-operations-to-sort-a-binary-tree-by-level/) | [🦀](src/problems/p2471_minimum_number_of_operations_to_sort_a_binary_tree_by_level.rs) | |
7171
| 🟠 2857. [Count Pairs of Points With Distance k](https://leetcode.com/problems/count-pairs-of-points-with-distance-k/) | [🦀](src/problems/p2471_count_pairs_of_points_with_distance_k.rs) | Hash Map |
72+
| 🔴 2902. [Count of Sub-Multisets With Bounded Sum](https://leetcode.com/problems/count-of-sub-multisets-with-bounded-sum/) | [🦀](src/problems/p2902_count_of_sub_multisets_with_bounded_sum.rs) | Dynamic Programming |
7273
| 🟠 2933. [High-Access Employees](https://leetcode.com/problems/high-access-employees/) | [🦀](src/problems/p2933_high_access_employees.rs) | Hash Map |
7374
| 🔴 2940. [Find Building Where Alice and Bob Can Meet](https://leetcode.com/problems/find-building-where-alice-and-bob-can-meet/) | [🦀](src/problems/p2940_find_building_where_alice_and_bob_can_meet.rs) | Priority Queue |
7475
| 🟠 3021. [Alice and Bob Playing Flower Game](https://leetcode.com/problems/alice-and-bob-playing-flower-game/) | [🦀](src/problems/p3021_alice_and_bob_playing_flower_game.rs) | |

src/problems/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub mod p2399_check_distances_between_same_letters;
3535
pub mod p2442_count_number_of_distinct_integers_after_reverse_operations;
3636
pub mod p2471_count_pairs_of_points_with_distance_k;
3737
pub mod p2471_minimum_number_of_operations_to_sort_a_binary_tree_by_level;
38+
pub mod p2902_count_of_sub_multisets_with_bounded_sum;
3839
pub mod p2933_high_access_employees;
3940
pub mod p2940_find_building_where_alice_and_bob_can_meet;
4041
pub mod p3021_alice_and_bob_playing_flower_game;
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
//! # LeetCode Problem: 2902 - Count of Sub-Multisets with Bounded Sum
2+
//!
3+
//! Difficulty: Hard
4+
//!
5+
//! Link: https://leetcode.com/problems/count-of-sub-multisets-with-bounded-sum/
6+
//!
7+
//! ## Complexity Analysis
8+
//! - Time Complexity: O(r × unique_numbers)
9+
//! - Space Complexity: O(r)
10+
//!
11+
//! ## A bit of theory
12+
//!
13+
//! We're using Dynamic Programming to solve this problem.
14+
//! In `dp` we store the number of ways to make the sum in a multiset.
15+
//! So, dp[i] = "The number of different ways to select numbers from our
16+
//! array such that their sum equals exactly i"
17+
//!
18+
//! Let's say we have the array [1, 2, 2] and we want sums up to 5.
19+
//! dp[0] = 1 # 1 way to make sum 0: pick nothing {}
20+
//! dp[1] = 1 # 1 way to make sum 1: pick {1}
21+
//! dp[2] = 2 # 2 ways to make sum 2: pick {2} or {2} (we have two 2's)
22+
//! dp[3] = 2 # 2 ways to make sum 3: pick {1,2} or {1,2} (using different 2's)
23+
//! dp[4] = 1 # 1 way to make sum 4: pick {2,2}
24+
//! dp[5] = 1 # 1 way to make sum 5: pick {1,2,2}
25+
//!
26+
//! To solve our problem (count sums between l and r),
27+
//! we add up dp[l] + dp[l+1] + ... + dp[r]
28+
//!
29+
//! This is fundamentally a Bounded Knapsack problem because:
30+
//! - We have items (numbers) with limited quantities (frequencies)
31+
//! - We want to count combinations that achieve certain sums (weights)
32+
//! - Each item can be used 0 to freq times
33+
//!
34+
//! But, for optimum performance, we can use a modified version
35+
//! of the Bounded Knapsack problem.
36+
//! That is, we can use a hybrid version of the Bounded Knapsack problem:
37+
//! Unbounded Knapsack + Bounded Correction.
38+
//!
39+
//! The rationale behind this hybrid is that:
40+
//!
41+
//! Direct Bounded Knapsack would be
42+
//!
43+
//! for count in range(freq + 1):
44+
//! dp[target] += dp[target - count * num]
45+
//!
46+
//! Time Complexity: O(r × unique_numbers × max_frequency) - potentially too slow!
47+
//!
48+
//! Our Hybrid Approach would be
49+
//! Unbounded: O(r × unique_numbers)
50+
//! Correction: O(r × unique_numbers)
51+
//! Total Time Complexity: O(r × unique_numbers) - much faster!
52+
//!
53+
//! Additional Optimization: Remainder Class Processing
54+
//!
55+
//! for remainder in range(num):
56+
//!
57+
//! This processes positions by their remainder when divided by num,
58+
//! which is a common optimization for knapsack problems with specific item weights.
59+
//!
60+
//! The algorithm breakdown:
61+
//! 1. Handle Zeros Specially
62+
//! 2. Track Maximum Meaningful Sum
63+
//! 3. The Two-Phase Magic
64+
//! 3.1. Phase 1: Add Unlimited Uses (Unbounded Knapsack)
65+
//! 3.2. Phase 2: Remove Excess Uses (Bounded Correction)
66+
//! 4. Count Final Answer
67+
pub struct Solution;
68+
69+
impl Solution {
70+
pub fn count_sub_multisets(nums: Vec<i32>, l: i32, r: i32) -> i32 {
71+
const MOD: i32 = 1_000_000_007;
72+
73+
// Convert to usize for array indexing
74+
let r_usize = r as usize;
75+
76+
// 1. Handle Zeros Specially
77+
// Initialize DP array
78+
let mut dp = vec![0; r_usize + 1];
79+
dp[0] = 1; // There is exactly one way to make sum 0. It's an empty multiset.
80+
81+
// Count frequencies
82+
let mut counter = std::collections::HashMap::new();
83+
let mut zero_count = 0;
84+
85+
for &num in &nums {
86+
if num == 0 {
87+
zero_count += 1;
88+
} else {
89+
*counter.entry(num).or_insert(0) += 1;
90+
}
91+
}
92+
93+
// Handle zeros - each zero can be included or excluded without affecting the sum
94+
dp[0] = if zero_count > 0 { zero_count + 1 } else { 1 };
95+
96+
// 2. Track Maximum Meaningful Sum
97+
let mut ms: usize = 1;
98+
99+
for (&num, &freq) in &counter {
100+
if num > r {
101+
continue;
102+
}
103+
104+
let num_usize = num as usize;
105+
106+
// Update maximum sum bound
107+
if ms < dp.len() {
108+
ms = std::cmp::min(dp.len(), ms + freq as usize * num_usize);
109+
}
110+
111+
// 3. The Two-Phase Magic
112+
113+
// 3.1. Phase 1: Add Unlimited Uses (Unbounded Knapsack)
114+
for remainder in 0..num_usize {
115+
let mut k = remainder + num_usize;
116+
while k < ms {
117+
dp[k] = (dp[k] + dp[k - num_usize]) % MOD;
118+
k += num_usize;
119+
}
120+
}
121+
122+
// 3.2. Phase 2: Remove Excess Uses (Bounded Correction)
123+
// Process in reverse order within each remainder class
124+
for remainder in 0..num_usize {
125+
// Start from largest k in this remainder class
126+
let mut k =
127+
((ms - 1).saturating_sub(remainder) / num_usize) * num_usize + remainder;
128+
129+
while k >= remainder {
130+
if (freq as usize + 1) * num_usize > k {
131+
break;
132+
}
133+
// Subtract excess contributions
134+
dp[k] = (dp[k] + MOD - dp[k - (freq as usize + 1) * num_usize] % MOD) % MOD;
135+
136+
if k < num_usize {
137+
break; // Prevent underflow
138+
}
139+
k -= num_usize;
140+
}
141+
}
142+
}
143+
144+
// 4. Count Final Answer
145+
// Sum results in range [l, r]
146+
let l_usize = l as usize;
147+
let mut result = 0;
148+
for i in l_usize..=std::cmp::min(r_usize, ms - 1) {
149+
result = (result + dp[i]) % MOD;
150+
}
151+
152+
result
153+
}
154+
}
155+
156+
#[cfg(test)]
157+
mod tests {
158+
use super::Solution;
159+
160+
#[test]
161+
fn test_count_sub_multisets() {
162+
let test_cases = [
163+
(vec![1, 2, 2, 3], 6, 6, 1),
164+
(vec![2, 1, 4, 2, 7], 1, 5, 7),
165+
(vec![1, 2, 1, 3, 5, 2], 3, 5, 9),
166+
];
167+
for (idx, (nums, l, r, expected)) in test_cases.iter().enumerate() {
168+
let result = Solution::count_sub_multisets(nums.clone(), *l, *r);
169+
assert_eq!(
170+
result, *expected,
171+
"Test case #{idx}: with input {nums:?}, l={l:?}, r={r:?}, expected {expected}, got {result}"
172+
);
173+
}
174+
}
175+
}

0 commit comments

Comments
 (0)