Generate All Combinations Sum K in DSA Rust - Time & Space Complexity
We want to understand how the time needed to find all combinations that add up to a target grows as the input size changes.
How does the number of combinations affect the work done?
Analyze the time complexity of the following code snippet.
fn backtrack(nums: &Vec<i32>, target: i32, start: usize, path: &mut Vec<i32>, res: &mut Vec<Vec<i32>>) {
if target == 0 {
res.push(path.clone());
return;
}
for i in start..nums.len() {
if nums[i] > target {
break;
}
path.push(nums[i]);
backtrack(nums, target - nums[i], i + 1, path, res);
path.pop();
}
}
This code finds all unique combinations of numbers that sum to a target by trying each number and exploring further choices recursively.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive calls exploring combinations.
- How many times: Potentially exploring many subsets, depending on input size and target.
As input size grows, the number of possible combinations grows very fast, because each number can be chosen or not.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | Up to 2^10 = 1,024 combinations explored |
| 20 | Up to 2^20 = 1,048,576 combinations explored |
| 30 | Up to 2^30 = 1,073,741,824 combinations explored |
Pattern observation: The work doubles roughly with each added input element, growing exponentially.
Time Complexity: O(2^n * n)
This means the time grows exponentially with input size, because we explore many subsets, and copying each combination takes extra time.
[X] Wrong: "The time is just O(n) because we only loop through the list once."
[OK] Correct: The code explores many combinations recursively, not just a single pass, so the work grows much faster than the input size.
Understanding this exponential growth helps you explain why some problems need careful pruning or different approaches, showing your grasp of algorithm limits.
"What if we allowed numbers to be reused unlimited times? How would the time complexity change?"
Practice
Solution
Step 1: Understand the problem goal
The problem asks to find all unique sets of numbers that add exactly to the target sum K.Step 2: Clarify what combinations mean
Combinations mean groups of numbers without regard to order, and each number can be used once or multiple times depending on problem rules.Final Answer:
All unique combinations of numbers that add up to K -> Option AQuick Check:
Combinations sum K = unique sets summing to K [OK]
- Confusing sum with product
- Looking for single numbers instead of combinations
- Ignoring uniqueness of combinations
Solution
Step 1: Identify input types
The candidates are passed as a slice reference &[i32] to avoid ownership issues and allow read-only access.Step 2: Identify return type
The function returns a vector of vectors of i32, representing all combinations found.Final Answer:
fn combination_sum(candidates: &[i32], k: i32) -> Vec -> Option DQuick Check:
Function signature with slice input and Vec output [OK]
- Returning i32 instead of combinations
- Using Vec<i32> input causing ownership issues
- Wrong return type for multiple combinations
fn main() {
let candidates = vec![2, 3, 6, 7];
let target = 7;
let result = combination_sum(&candidates, target);
println!("{:?}", result);
}
fn combination_sum(candidates: &[i32], target: i32) -> Vec {
fn backtrack(start: usize, target: i32, path: &mut Vec<i32>, res: &mut Vec, candidates: &[i32]) {
if target == 0 {
res.push(path.clone());
return;
}
for i in start..candidates.len() {
if candidates[i] > target {
continue;
}
path.push(candidates[i]);
backtrack(i, target - candidates[i], path, res, candidates);
path.pop();
}
}
let mut res = vec![];
let mut path = vec![];
backtrack(0, target, &mut path, &mut res, candidates);
res
}Solution
Step 1: Understand backtracking logic
The function tries combinations starting from each candidate, allowing repeats, and collects those summing to target 7.Step 2: Trace valid combinations
Valid combinations are [2, 2, 3] (2+2+3=7) and [7] itself.Final Answer:
[[2, 2, 3], [7]] -> Option CQuick Check:
Backtracking finds combinations summing to 7 [OK]
- Including invalid sums like 2+3+6
- Ignoring repeated use of candidates
- Missing single element combination [7]
fn combination_sum(candidates: &[i32], target: i32) -> Vec {
fn backtrack(start: usize, target: i32, path: &mut Vec<i32>, res: &mut Vec, candidates: &[i32]) {
if target == 0 {
res.push(path.clone());
return;
}
for i in start..candidates.len() {
if candidates[i] >= target {
continue;
}
path.push(candidates[i]);
backtrack(i, target - candidates[i], path, res, candidates);
path.pop();
}
}
let mut res = vec![];
let mut path = vec![];
backtrack(0, target, &mut path, &mut res, candidates);
res
}Solution
Step 1: Analyze the pruning condition
The condition 'if candidates[i] >= target' skips candidates equal to target, missing valid combinations like [target] itself.Step 2: Correct the condition
Changing to 'candidates[i] > target' allows candidates equal to target to be included, fixing the bug.Final Answer:
The condition should be candidates[i] > target, not >= target -> Option BQuick Check:
Pruning must allow candidates equal to target [OK]
- Skipping candidates equal to target
- Incorrect recursive index causing duplicates
- Not cloning path before pushing
Solution
Step 1: List all combinations summing to 4
Possible combinations using unlimited candidates are [1,1,1,1], [1,1,2], [1,3], and [2,2].Step 2: Check for duplicates and invalid entries
[3,1] is a duplicate of [1,3] (order doesn't matter), and [4] is invalid as 4 is not in candidates.Final Answer:
[[1,1,1,1], [1,1,2], [1,3], [2,2]] -> Option AQuick Check:
Unique combinations without duplicates or invalid numbers [OK]
- Including duplicates with different order
- Adding numbers not in candidates
- Missing valid combinations
Ready to prove it?
Pass the test and this pattern moves unknown
