题目链接

LeetCode

题目描述

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。

candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。

对于给定的输入,保证和为 target 的唯一组合数少于 150 个。

示例 1:

输入: candidates = [2,3,6,7], target = 7
输出: [[7],[2,2,3]]

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

示例 4:

输入: candidates = [1], target = 1
输出: [[1]]

示例 5:

输入: candidates = [1], target = 2
输出: [[1,1]]

提示:

  • 1 <= candidates.length <= 30
  • 1 <= candidates[i] <= 200
  • candidate 中的每个元素都是独一无二的。
  • 1 <= target <= 500

    解题思路

    方法一:回溯

    回到本题,我们定义递归函数 dfs(target, combine, idx) 表示当前在 candidates 数组的第 idx 位,还剩 target 要组合,已经组合的列表为 combine。递归的终止条件为 target <= 0 或者 candidates 数组被全部用完。那么在当前的函数中,每次我们可以选择跳过不用第 idx 个数,即执行 dfs(target, combine, idx + 1)。也可以选择使用第 idx 个数,即执行 dfs(target - candidates[idx], combine, idx),注意到每个数字可以被无限制重复选取,因此搜索的下标仍为 idx。

更形象化地说,如果我们将整个搜索过程用一个树来表达,即如下图呈现,每次的搜索都会延伸出两个分叉,直到递归的终止条件,这样我们就能不重复且不遗漏地找到所有可行解:
39. 组合总和 - 图1

  1. class Solution {
  2. public:
  3. vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
  4. len = candidates.size();
  5. dfs(candidates,0,target);
  6. return res;
  7. }
  8. private:
  9. vector<vector<int>> res;
  10. vector<int> path;
  11. int len = 0;
  12. void dfs(vector<int>& candidates,int idx,int target){
  13. if(idx==len||target<0){
  14. return;
  15. }
  16. if(target==0){
  17. res.push_back(path);
  18. return;
  19. }
  20. dfs(candidates,idx+1,target);
  21. if(target-candidates[idx]>=0){
  22. path.push_back(candidates[idx]);
  23. dfs(candidates,idx,target - candidates[idx]);
  24. path.pop_back();
  25. }
  26. }
  27. };
class Solution {
    List<List<Integer>> res = new LinkedList<List<Integer>>();
    List<Integer> path = new LinkedList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        recur(candidates, target, 0);
        return res;
    }
    private void recur(int[] candidates, int target, int pos){
        if(pos == candidates.length){
            return;
        }
        if(target == 0){
            res.add(new LinkedList<Integer>(path));
            return;
        }
        // 跳过当前元素
        recur(candidates, target, pos + 1);
        // 选择当前元素  因为下次还在在当前位置开始,所以可以选择0-n次
        if(target - candidates[pos] >= 0){
            path.add(candidates[pos]);
            recur(candidates, target - candidates[pos], pos);
            path.remove(path.size() - 1);
        }
    }
}
class Solution {
    List<List<Integer>> res;
    List<Integer> path;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        res = new LinkedList<List<Integer>>();
        path = new LinkedList<Integer>();
        backstrack(candidates, 0, target);
        return res;
    }
    private void backstrack(int[] candidates, int pos, int target){
        if(target < 0 || pos >= candidates.length){
            return;
        }
        if(target == 0){
            res.add(new LinkedList<Integer>(path));
            return;
        }
        // 选择当前元素  因为下次还在在当前位置开始,所以可以选择0-n次
        path.add(candidates[pos]);
        backstrack(candidates, pos, target - candidates[pos]);
        // 跳过当前元素
        path.remove(path.size() - 1);
        backstrack(candidates, pos + 1, target);
    }
}
  • 时间复杂度 O(S) :其中 S 为所有可行解的长度之和
  • 空间复杂度 O(target)