|
| 1 | +--- |
| 2 | +id: combination-sum |
| 3 | +title: Combination Sum(LeetCode) |
| 4 | +sidebar_label: 0039-Combination Sum |
| 5 | +tags: |
| 6 | + - Array |
| 7 | + - Backtracking |
| 8 | +description: Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target. |
| 9 | +sidebar_position: 39 |
| 10 | +--- |
| 11 | + |
| 12 | +## Problem Statement |
| 13 | + |
| 14 | +Given an array of distinct integers `candidates` and a target integer `target`, return a list of all unique combinations of `candidates` where the chosen numbers |
| 15 | +sum to `target`. You may return the combinations in any order. |
| 16 | + |
| 17 | +The same number may be chosen from `candidates` an unlimited number of times. Two combinations are unique if the |
| 18 | +frequency |
| 19 | + of at least one of the chosen numbers is different. |
| 20 | + |
| 21 | +The test cases are generated such that the number of unique combinations that sum up to `target` is less than `150` combinations for the given input. |
| 22 | + |
| 23 | +### Examples |
| 24 | + |
| 25 | +**Example 1:** |
| 26 | + |
| 27 | +```plaintext |
| 28 | +Input: candidates = [2,3,6,7], target = 7 |
| 29 | +Output: [[2,2,3],[7]] |
| 30 | +Explanation: |
| 31 | +2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times. |
| 32 | +7 is a candidate, and 7 = 7. |
| 33 | +These are the only two combinations. |
| 34 | +``` |
| 35 | + |
| 36 | +**Example 2:** |
| 37 | + |
| 38 | +```plaintext |
| 39 | +Input: candidates = [2,3,5], target = 8 |
| 40 | +Output: [[2,2,2,2],[2,3,3],[3,5]] |
| 41 | +``` |
| 42 | + |
| 43 | +**Example 3:** |
| 44 | + |
| 45 | +```plaintext |
| 46 | +Input: candidates = [2], target = 1 |
| 47 | +Output: [] |
| 48 | +``` |
| 49 | + |
| 50 | +### Constraints |
| 51 | + |
| 52 | +- `1 <= candidates.length <= 30` |
| 53 | +- `2 <= candidates[i] <= 40` |
| 54 | +- All elements of `candidates` are distinct. |
| 55 | +- `1 <= target <= 40` |
| 56 | + |
| 57 | +## Solution |
| 58 | + |
| 59 | +The Combination Sum problem involves finding all unique combinations in a list of candidates where the candidate numbers sum to a given target. Each number in the list may be used an |
| 60 | +unlimited number of times. Below, we discuss three approaches to solve this problem: DFS (Backtracking), Dynamic Programming (Slow), and Dynamic Programming (Fast). |
| 61 | + |
| 62 | +### Approach 1: DFS (Backtracking) |
| 63 | + |
| 64 | +#### Explanation: |
| 65 | + |
| 66 | +1. Use a recursive function to explore all possible combinations. |
| 67 | +2. Track the current combination (`cur`) and its sum (`cur_sum`). |
| 68 | +3. If `cur_sum` exceeds the target, backtrack. |
| 69 | +4. If `cur_sum` equals the target, add the current combination to the result list. |
| 70 | + |
| 71 | +#### Algorithm |
| 72 | + |
| 73 | +1. Initialize an empty list `ans` to store the results. |
| 74 | +2. Define a recursive function `dfs(cur, cur_sum, idx)` to explore combinations. |
| 75 | +3. In `dfs`, if `cur_sum` exceeds the target, return. |
| 76 | +4. If `cur_sum` equals the target, add `cur` to `ans` and return. |
| 77 | +5. Loop through candidates starting from `idx`, and recursively call `dfs` with updated `cur` and `cur_sum`. |
| 78 | +6. Start the DFS with an empty combination, a sum of 0, and starting index 0. |
| 79 | +7. Return the list `ans`. |
| 80 | + |
| 81 | +#### Implementation |
| 82 | + |
| 83 | +```python |
| 84 | +class Solution: |
| 85 | + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: |
| 86 | + ans = [] |
| 87 | + n = len(candidates) |
| 88 | + def dfs(cur, cur_sum, idx): |
| 89 | + if cur_sum > target: return |
| 90 | + if cur_sum == target: |
| 91 | + ans.append(cur) |
| 92 | + return |
| 93 | + for i in range(idx, n): |
| 94 | + dfs(cur + [candidates[i]], cur_sum + candidates[i], i) |
| 95 | + dfs([], 0, 0) |
| 96 | + return ans |
| 97 | +``` |
| 98 | + |
| 99 | +### Complexity Analysis |
| 100 | + |
| 101 | +- **Time complexity**: O(N^(M/min_cand + 1)), where N is the number of candidates, M is the target, and min_cand is the |
| 102 | + smallest candidate. |
| 103 | +- **Space complexity**: O(M/min_cand), as the recursion depth can go up to the target divided by the smallest |
| 104 | + candidate. |
| 105 | + |
| 106 | +### Approach 2: Dynamic Programming (Slow) |
| 107 | + |
| 108 | +#### Explanation: |
| 109 | + |
| 110 | +1. Use a list `dp` where `dp[i]` contains combinations that sum up to `i`. |
| 111 | +2. Build the `dp` list by iterating through all possible sums up to the target. |
| 112 | +3. For each sum, update the combinations by considering each candidate. |
| 113 | + |
| 114 | +#### Algorithm |
| 115 | + |
| 116 | +1. Create a dictionary `idx_d` to map each candidate to its index. |
| 117 | +2. Initialize a list `dp` with empty lists for all sums from 0 to the target. |
| 118 | +3. For each sum `i` from 1 to the target: |
| 119 | +* Iterate through all previous sums `j` from 0 to `i-1`. |
| 120 | +* For each combination in `dp[j]`, update combinations in `dp[i]`. |
| 121 | +* Add combinations directly from candidates if they equal `i`. |
| 122 | +4. Return the list `dp[-1]`. |
| 123 | + |
| 124 | +#### Implementation |
| 125 | + |
| 126 | +```python |
| 127 | +class Solution: |
| 128 | + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: |
| 129 | + idx_d = {val: idx for idx, val in enumerate(candidates)} |
| 130 | + n = len(candidates) |
| 131 | + dp = [[] for _ in range(target + 1)] |
| 132 | + for i in range(1, target + 1): |
| 133 | + for j in range(i): |
| 134 | + for comb in dp[j]: |
| 135 | + start_idx = idx_d[comb[-1]] |
| 136 | + for val in candidates[start_idx:]: |
| 137 | + if val + j == i: |
| 138 | + dp[i].append(comb + [val]) |
| 139 | + for candidate in candidates: |
| 140 | + if candidate == i: |
| 141 | + dp[i].append([candidate]) |
| 142 | + return dp[-1] |
| 143 | +``` |
| 144 | + |
| 145 | +### Complexity Analysis |
| 146 | + |
| 147 | +- **Time complexity**: O(MMM*N), where N is the number of candidates and M is the target. |
| 148 | +- **Space complexity**: O(M*M), due to the storage of combinations for each sum up to the target. |
| 149 | + |
| 150 | +### Approach 3: Dynamic Programming (Fast) |
| 151 | + |
| 152 | +#### Explanation: |
| 153 | + |
| 154 | +1. Use a list `dp` where `dp[i]` contains combinations that sum up to `i`. |
| 155 | +2. For each candidate, update the combinations for all possible sums up to the target. |
| 156 | + |
| 157 | +#### Algorithm |
| 158 | + |
| 159 | +1. Initialize a list `dp` with empty lists for all sums from 0 to the target. |
| 160 | +2. For each candidate `c`: |
| 161 | +3. For each sum `i` from `c` to the target: |
| 162 | +* If `i` equals `c`, add `[c]` to `dp[i]`. |
| 163 | +* For each combination in `dp[i - c]`, add `comb + [c]` to `dp[i]`. |
| 164 | +4. Return the list `dp[-1]`. |
| 165 | + |
| 166 | +#### Implementation |
| 167 | + |
| 168 | +```python |
| 169 | +class Solution: |
| 170 | + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: |
| 171 | + dp = [[] for _ in range(target + 1)] |
| 172 | + for c in candidates: |
| 173 | + for i in range(c, target + 1): |
| 174 | + if i == c: |
| 175 | + dp[i].append([c]) |
| 176 | + for comb in dp[i - c]: |
| 177 | + dp[i].append(comb + [c]) |
| 178 | + return dp[-1] |
| 179 | +``` |
| 180 | + |
| 181 | +### Complexity Analysis |
| 182 | + |
| 183 | +- **Time complexity**: O(MMN), where N is the number of candidates and M is the target. |
| 184 | +- **Space complexity**: O(M*M), due to the storage of combinations for each sum up to the target. |
| 185 | + |
| 186 | +### Conclusion |
| 187 | + |
| 188 | +In solving the Combination Sum problem: |
| 189 | + |
| 190 | +1. DFS (Backtracking) is simple and intuitive but can be slow for large inputs due to its exponential time complexity. |
| 191 | +2. Dynamic Programming (Slow) uses a structured approach but has higher time complexity (O(MMM*N)) and memory usage. |
| 192 | +3. Dynamic Programming (Fast) is more efficient (O(MMN)), making it suitable for larger inputs, though it still uses significant memory. |
| 193 | + |
| 194 | +Choose DFS (Backtracking) for smaller datasets, DP (Slow) for a structured approach within manageable limits, and DP (Fast) for larger datasets where efficiency is critical. |
0 commit comments