Leetcode 39.组合总数


题目描述:

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

candidates 中的数字可以无限制重复被选取。

说明

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

1
2
3
4
5
6
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]

示例 2:

1
2
3
4
5
6
7
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

提示:

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

链接:

https://leetcode-cn.com/problems/combination-sum


题目分析

  题目要求所有的可行解,则依然可以使用回溯的方法来进行搜索遍历,也即对于每一个数 candidates[i],都可以选择是否将其加入结果中,然后再转化为搜索 target-candidates[i] 的问题。为了避免产生重复解,我们按照 candidates 的顺序添加,也即若当前选择不添加 candidates[i],则转化的子问题也不能再添加 candidates[i]。注意到题目中的数字可以无限制重复选取,则我们每次都需要保持从上一次添加的数字开始(也即上一次添加的数字仍然可以添加)。每当 target 为 0,意味着当前添加的数字的和为 target,也即找到了一组解,将其添加到结果列表中。
  而为了加快搜索速度,我们可以进行剪枝。比如我们可以先对 candidates 进行排序,当 target 已经小于当前想添加的数字时,也肯定小于后面所有的数字,则都不会是结果,则可以直接跳出循环返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
void dfs(vector<vector<int>>& result, vector<int>& now, vector<int>& candidates, int begin, int target){
if(target == 0){
result.push_back(now);
return;
}
for(int i = begin; i < candidates.size(); i++){
if(target < candidates[i]) break;
now.push_back(candidates[i]);
dfs(result, now, candidates, i, target-candidates[i]);
now.pop_back();
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> result;
vector<int> now;

sort(candidates.begin(), candidates.end());

dfs(result, now, candidates, 0, target);
return result;
}
};

  时间复杂度:$O(S)$,$S$ 是可行解的长度之和。我们遍历了所有的可行解得到结果,而对于不可行的情况马上剪枝,则时间复杂度是可行解的长度之和。因为这道题确实比较难给出一个较紧密的上界。对于每个数字选或者不选,一共拥有非常多的可能,这是指数级别的。但是我们进行的剪枝可以排除掉非常多的情况,因此实际的运行情况不好以target 的大小或者是 candidates 的大小界定。
  空间复杂度:$O(target)$。作为结果返回的数组不计入所需空间中,而所有的数字都是正整数,则最大的可行解长度也就是 target 的值(全由 1 构成可行解),这也是我们栈递归的最大深度,因此空间复杂度为 $O(target)$。