title: Combination Sum III slug: combination-sum-iii difficulty: medium leetcode_id: 216 leetcode_url: https://leetcode.com/problems/combination-sum-iii/ categories: - arrays - recursion patterns: - slug: backtracking is_optimal: true function_signature: "def combination_sum3(k: int, n: int) -> list[list[int]]:" test_cases: visible: - input: { k: 3, n: 7 } expected: [[1, 2, 4]] - input: { k: 3, n: 9 } expected: [[1, 2, 6], [1, 3, 5], [2, 3, 4]] - input: { k: 4, n: 1 } expected: [] hidden: - input: { k: 2, n: 3 } expected: [[1, 2]] - input: { k: 2, n: 17 } expected: [[8, 9]] - input: { k: 2, n: 18 } expected: [] - input: { k: 9, n: 45 } expected: [[1, 2, 3, 4, 5, 6, 7, 8, 9]] - input: { k: 3, n: 15 } expected: [[1, 5, 9], [1, 6, 8], [2, 4, 9], [2, 5, 8], [2, 6, 7], [3, 4, 8], [3, 5, 7], [4, 5, 6]] - input: { k: 5, n: 15 } expected: [[1, 2, 3, 4, 5]] description: | Find all valid combinations of `k` numbers that sum up to `n` such that the following conditions are true: - Only numbers `1` through `9` are used. - Each number is used **at most once**. Return *a list of all possible valid combinations*. The list must not contain the same combination twice, and the combinations may be returned in any order. constraints: | - `2 <= k <= 9` - `1 <= n <= 60` examples: - input: "k = 3, n = 7" output: "[[1,2,4]]" explanation: "1 + 2 + 4 = 7. There are no other valid combinations." - input: "k = 3, n = 9" output: "[[1,2,6],[1,3,5],[2,3,4]]" explanation: "1 + 2 + 6 = 9, 1 + 3 + 5 = 9, 2 + 3 + 4 = 9. There are no other valid combinations." - input: "k = 4, n = 1" output: "[]" explanation: "There are no valid combinations. Using 4 different numbers in the range [1,9], the smallest sum we can get is 1+2+3+4 = 10, and since 10 > 1, there are no valid combinations." explanation: intuition: | Imagine you have a row of numbered boxes from 1 to 9, and you need to pick exactly `k` boxes such that their numbers add up to `n`. You can only pick each box once, and order doesn't matter — picking boxes 1, 2, 4 is the same as picking 4, 2, 1. This is a classic **combination problem** where you're exploring all possible subsets of a fixed set. The key insight is that you can build combinations incrementally: start with an empty selection, then for each number from 1 to 9, decide whether to include it or skip it. Think of it like walking through a decision tree. At each node, you choose to either: - **Include** the current number and move forward - **Skip** the current number and move forward When your selection reaches exactly `k` numbers and they sum to `n`, you've found a valid combination. If the sum exceeds `n` or you've used too many numbers, you backtrack and try a different path. The constraint that we only use numbers 1-9 keeps the search space small — at most 29 = 512 possible subsets — making this problem tractable with backtracking. approach: | We solve this using **Backtracking** to explore all valid combinations: **Step 1: Set up the recursive function** - Create a helper function `backtrack(start, remaining_sum, current_combination)` - `start`: The next number to consider (1 through 9) - `remaining_sum`: How much more we need to reach target `n` - `current_combination`: Numbers we've picked so far   **Step 2: Define the base cases** - If `current_combination` has exactly `k` numbers AND `remaining_sum == 0`, we found a valid combination — add a copy to results - If `current_combination` has `k` numbers but sum isn't `n`, or if we've exhausted all numbers (start > 9), backtrack   **Step 3: Explore choices with pruning** - For each number `i` from `start` to 9: - If `i > remaining_sum`, skip it and all larger numbers (pruning) - Otherwise, add `i` to `current_combination` - Recurse with `backtrack(i + 1, remaining_sum - i, current_combination)` - Remove `i` from `current_combination` (backtrack)   **Step 4: Start the recursion** - Call `backtrack(1, n, [])` and return the collected results   The key optimisation is **pruning**: if the current number already exceeds the remaining sum needed, we can skip all larger numbers since they would only increase the overshoot. common_pitfalls: - title: Generating Duplicate Combinations description: | Without careful ordering, you might generate the same combination multiple times. For example, [1,2,4] and [2,1,4] are the same combination. The fix is to always process numbers in increasing order by only considering numbers greater than or equal to `start`. This ensures each combination is generated exactly once in sorted order. wrong_approach: "Consider all numbers 1-9 at each step" correct_approach: "Only consider numbers from `start` to 9" - title: Forgetting to Backtrack description: | After exploring a path that includes a number, you must remove that number before exploring paths that exclude it. Forgetting this step corrupts the `current_combination` list. Always pair "add to combination" with "remove from combination" after the recursive call returns. wrong_approach: "Add number but never remove it" correct_approach: "Remove the number after recursive call (backtrack)" - title: Not Copying the Combination When Adding to Results description: | In Python, lists are mutable. If you append `current_combination` directly to results, all entries will reference the same list, which gets modified during backtracking. Always append a copy: `results.append(current_combination.copy())` or `results.append(list(current_combination))`. wrong_approach: "results.append(current_combination)" correct_approach: "results.append(current_combination.copy())" - title: Missing the Pruning Optimisation description: | Without pruning, you explore branches that can never lead to a valid solution. For instance, if you need `remaining_sum = 3` and you're at number 5, there's no point continuing since 5 > 3 and all subsequent numbers are even larger. Adding `if i > remaining_sum: break` significantly reduces unnecessary exploration. key_takeaways: - "**Backtracking template**: This problem follows the classic backtracking pattern — make a choice, recurse, undo the choice" - "**Avoid duplicates by ordering**: Processing elements in sorted order and only considering elements >= start prevents generating the same combination twice" - "**Prune aggressively**: Early termination when a branch cannot lead to a valid solution dramatically improves performance" - "**Foundation for harder problems**: This pattern extends to Combination Sum I, II, IV, and other subset/permutation problems" time_complexity: "O(C(9, k) * k). We explore at most C(9, k) combinations (9 choose k), and each valid combination takes O(k) time to copy. Since k <= 9 and C(9, k) <= 126, this is effectively constant for this problem." space_complexity: "O(k). The recursion depth is at most k, and we use O(k) space for the current combination. The output space for storing results is not counted." solutions: - approach_name: Backtracking with Pruning is_optimal: true code: | def combination_sum3(k: int, n: int) -> list[list[int]]: results = [] def backtrack(start: int, remaining: int, combination: list[int]) -> None: # Found a valid combination if len(combination) == k and remaining == 0: results.append(combination.copy()) return # Too many numbers or exhausted search space if len(combination) == k or start > 9: return # Try each number from start to 9 for num in range(start, 10): # Pruning: if current number exceeds remaining sum, skip rest if num > remaining: break # Include this number and recurse combination.append(num) backtrack(num + 1, remaining - num, combination) # Backtrack: remove the number we just added combination.pop() backtrack(1, n, []) return results explanation: | **Time Complexity:** O(C(9, k) * k) — We explore combinations of k numbers from 1-9, copying each valid one. **Space Complexity:** O(k) — Recursion depth and combination list are bounded by k. The backtracking explores the decision tree of including/excluding each number. Pruning when `num > remaining` cuts off entire subtrees, and processing numbers in order prevents duplicates. - approach_name: Iterative with Bitmask is_optimal: false code: | def combination_sum3(k: int, n: int) -> list[list[int]]: results = [] # Iterate through all 2^9 = 512 subsets for mask in range(1, 1 << 9): combination = [] total = 0 # Check which bits are set (which numbers to include) for i in range(9): if mask & (1 << i): combination.append(i + 1) # Numbers are 1-indexed total += i + 1 # Check if this subset matches our criteria if len(combination) == k and total == n: results.append(combination) return results explanation: | **Time Complexity:** O(2^9 * 9) = O(4608) — Check all 512 subsets, each taking O(9) to process. **Space Complexity:** O(k) — Each combination uses O(k) space. This approach treats each subset as a bitmask where bit i indicates whether number (i+1) is included. While less elegant than backtracking, it's simple and the small search space (512 subsets) makes it practical. No pruning is applied, so it explores all subsets regardless of validity.