Welcome to Subscribe On Youtube
39. Combination Sum
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
. You may return the combinations in any order.
The same number may be chosen from candidates
an unlimited number of times. Two combinations are unique if the frequency of at least one of the chosen numbers is different.
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.
Example 1:
Input: candidates = [2,3,6,7], target = 7 Output: [[2,2,3],[7]] Explanation: 2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times. 7 is a candidate, and 7 = 7. These are the only two combinations.
Example 2:
Input: candidates = [2,3,5], target = 8 Output: [[2,2,2,2],[2,3,3],[3,5]]
Example 3:
Input: candidates = [2], target = 1 Output: []
Constraints:
1 <= candidates.length <= 30
2 <= candidates[i] <= 40
- All elements of
candidates
are distinct. 1 <= target <= 40
Solutions
Solution 1: Sorting + Pruning + Backtracking (Two Implementations)
We can first sort the array to facilitate pruning.
Next, we design a function $dfs(i, s)$, which means starting the search from index $i$ with a remaining target value of $s$. Here, $i$ and $s$ are both non-negative integers, the current search path is $t$, and the answer is $ans$.
In the function $dfs(i, s)$, we first check whether $s$ is $0$. If it is, we add the current search path $t$ to the answer $ans$, and then return. If $s \lt candidates[i]$, it means that the elements of the current index and the following indices are all greater than the remaining target value $s$, and the path is invalid, so we return directly. Otherwise, we start the search from index $i$, and the search index range is $j \in [i, n)$, where $n$ is the length of the array $candidates$. During the search, we add the element of the current index to the search path $t$, recursively call the function $dfs(j, s - candidates[j])$, and after the recursion ends, we remove the element of the current index from the search path $t$.
We can also change the implementation logic of the function $dfs(i, s)$ to another form. In the function $dfs(i, s)$, we first check whether $s$ is $0$. If it is, we add the current search path $t$ to the answer $ans$, and then return. If $i \geq n$ or $s \lt candidates[i]$, the path is invalid, so we return directly. Otherwise, we consider two situations, one is not selecting the element of the current index, that is, recursively calling the function $dfs(i + 1, s)$, and the other is selecting the element of the current index, that is, recursively calling the function $dfs(i, s - candidates[i])$.
In the main function, we just need to call the function $dfs(0, target)$ to get the answer.
The time complexity is $O(2^n \times n)$, and the space complexity is $O(n)$. Here, $n$ is the length of the array $candidates$. Due to pruning, the actual time complexity is much less than $O(2^n \times n)$.
Solution 2: DP
We can also use iterative solutions to create a three-dimensional
array dp
, where dp[i]
represents the set of all solutions whose target number is i+1
.
Here i traverse from 1 to target. For each i, create a two-dimensional array cur, and then traverse the candidates array.
- If the number traversed is greater than i, it means that the current and subsequent numbers cannot form i, directly break off.
- Otherwise, if they are equal, the current numbers form an array by themselves and added to cur.
- Otherwise, traverse all the arrays in
dp[i-candidates[j]-1]
. If the current number is greater than the first element of the array, skip it, because the result is required to be in order. Otherwise, add the current number to the beginning of the array, and put the array into cur.
-
class Solution { private List<List<Integer>> ans = new ArrayList<>(); private List<Integer> t = new ArrayList<>(); private int[] candidates; public List<List<Integer>> combinationSum(int[] candidates, int target) { Arrays.sort(candidates); this.candidates = candidates; dfs(0, target); return ans; } private void dfs(int i, int s) { if (s == 0) { ans.add(new ArrayList(t)); return; } if (i >= candidates.length || s < candidates[i]) { return; } dfs(i + 1, s); t.add(candidates[i]); dfs(i, s - candidates[i]); t.remove(t.size() - 1); } } ////// class Solution_dp { public List<List<Integer>> combinationSum(int[] candidates, int target) { // for each-target (from 1 to target), its dp[i][j] => so 3-D array dp[][][] List<List<List<Integer>>> dp = new ArrayList<>(); Arrays.sort(candidates); for (int i = 1; i <= target; ++i) { List<List<Integer>> cur = new ArrayList<>(); for (int j = 0; j < candidates.length; ++j) { if (candidates[j] > i) break; if (candidates[j] == i) { ArrayList<Integer> one = new ArrayList<Integer>(); one.add(candidates[j]); cur.add(one); // @note: one with proper <Integer>, or else unsupoorted operation error break; } for (List<Integer> a : dp.get(i - candidates[j] - 1)) { if (candidates[j] > a.get(0)) { continue; } ArrayList<Integer> deepCopied = new ArrayList<>(a); // @note: must have deepCopied.add(0, candidates[j]); // @note: largest at index=0 for the array cur.add(deepCopied); } } dp.add(cur); } return dp.get(dp.size() - 1); } }
-
class Solution { public: vector<vector<int>> combinationSum(vector<int>& candidates, int target) { sort(candidates.begin(), candidates.end()); vector<vector<int>> ans; vector<int> t; function<void(int, int)> dfs = [&](int i, int s) { if (s == 0) { ans.emplace_back(t); return; } if (i >= candidates.size() || s < candidates[i]) { return; } dfs(i + 1, s); t.push_back(candidates[i]); dfs(i, s - candidates[i]); t.pop_back(); }; dfs(0, target); return ans; } };
-
class Solution: def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: # i: start index for this recursion # s: sum def dfs(i, s): if s == target: ans.append(t.copy()) return if s > target: return for j in range(i, len(candidates)): c = candidates[j] t.append(c) dfs(j, s + c) t.pop() ans = [] t = [] # candidates.sort() # diff from combinationSum-II, no need sorting it dfs(0, 0) return ans ############ # dp version class Solution_dp: def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: # for each-target (from 1 to target), its dp[i][j] # => so 3-D array dp[][][] dp = [] candidates.sort() for i in range(1, target+1): cur = [] for j in range(len(candidates)): if candidates[j] > i: break if candidates[j] == i: one = [candidates[j]] cur.append(one) break for a in dp[i - candidates[j] - 1]: if candidates[j] > a[0]: continue deepCopied = a.copy() deepCopied.insert(0, candidates[j]) cur.append(deepCopied) dp.append(cur) return dp[-1] ############ class Solution(object): def combinationSum(self, candidates, target): """ :type candidates: List[int] :type target: int :rtype: List[List[int]] """ def dfs(candidates, start, target, path, res): if target == 0: return res.append(path + []) for i in range(start, len(candidates)): if target - candidates[i] >= 0: path.append(candidates[i]) dfs(candidates, i, target - candidates[i], path, res) path.pop() res = [] dfs(candidates, 0, target, [], res) return res ######### class Solution: def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: def dfs(i: int, s: int): if s == 0: ans.append(t[:]) return if i >= len(candidates) or s < candidates[i]: return dfs(i + 1, s) t.append(candidates[i]) dfs(i, s - candidates[i]) t.pop() candidates.sort() t = [] ans = [] dfs(0, target) return ans
-
func combinationSum(candidates []int, target int) (ans [][]int) { sort.Ints(candidates) t := []int{} var dfs func(i, s int) dfs = func(i, s int) { if s == 0 { ans = append(ans, slices.Clone(t)) return } if i >= len(candidates) || s < candidates[i] { return } dfs(i+1, s) t = append(t, candidates[i]) dfs(i, s-candidates[i]) t = t[:len(t)-1] } dfs(0, target) return }
-
function combinationSum(candidates: number[], target: number): number[][] { candidates.sort((a, b) => a - b); const ans: number[][] = []; const t: number[] = []; const dfs = (i: number, s: number) => { if (s === 0) { ans.push(t.slice()); return; } if (i >= candidates.length || s < candidates[i]) { return; } dfs(i + 1, s); t.push(candidates[i]); dfs(i, s - candidates[i]); t.pop(); }; dfs(0, target); return ans; }
-
public class Solution { private List<IList<int>> ans = new List<IList<int>>(); private List<int> t = new List<int>(); private int[] candidates; public IList<IList<int>> CombinationSum(int[] candidates, int target) { Array.Sort(candidates); this.candidates = candidates; dfs(0, target); return ans; } private void dfs(int i, int s) { if (s == 0) { ans.Add(new List<int>(t)); return; } if (i >= candidates.Length || s < candidates[i]) { return; } dfs(i + 1, s); t.Add(candidates[i]); dfs(i, s - candidates[i]); t.RemoveAt(t.Count - 1); } }
-
impl Solution { fn dfs(i: usize, s: i32, candidates: &Vec<i32>, t: &mut Vec<i32>, ans: &mut Vec<Vec<i32>>) { if s == 0 { ans.push(t.clone()); return; } if i >= candidates.len() || s < candidates[i] { return; } Self::dfs(i + 1, s, candidates, t, ans); t.push(candidates[i]); Self::dfs(i, s - candidates[i], candidates, t, ans); t.pop(); } pub fn combination_sum(mut candidates: Vec<i32>, target: i32) -> Vec<Vec<i32>> { candidates.sort(); let mut ans = Vec::new(); Self::dfs(0, target, &candidates, &mut vec![], &mut ans); ans } }
-
class Solution { /** * @param integer[] $candidates * @param integer $target * @return integer[][] */ function combinationSum($candidates, $target) { $result = []; $currentCombination = []; $startIndex = 0; sort($candidates); $this->findCombinations($candidates, $target, $startIndex, $currentCombination, $result); return $result; } function findCombinations($candidates, $target, $startIndex, $currentCombination, &$result) { if ($target === 0) { $result[] = $currentCombination; return; } for ($i = $startIndex; $i < count($candidates); $i++) { $num = $candidates[$i]; if ($num > $target) { break; } $currentCombination[] = $num; $this->findCombinations($candidates, $target - $num, $i, $currentCombination, $result); array_pop($currentCombination); } } }