39. Combination Sum(组合总和)
1. 题目描述
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
- 所有数字(包括 target)都是正整数。
- 解集不能包含重复的组合。
示例 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]
]
2. 回溯法(Backtracking)
2.1 解题思路
首先这题为什么会用到回溯法呢?我们可以顺着一个例子的解题思路来看看如何想到使用回溯法的:
[2,3,5], target = 8
如果先选择2,需要考察一下只有2能不能揍出8呢?所以我们连续-2,直到差值为0;这时只用2就能得到8,有了第一个答案:
8 -2 - 2 -2 -2 = 0
然后我们会想,2能不能和3揍出8呢?刚才我们用了4个2揍出了8,所以自然而然地,我们把最后一个2替换成3:
8 -2 - 2 -2 -3 = -1
-1小于0了,所以3个2和一个3行不通了,结果太小了,所以3后面更大的5肯定也不行,我们需要回到第三个2上,把它替换成3:
8 -2 - 2 -3 = 1
结果大于0,但是小于所有candidate三的数,所以这种组合也太大了,也不行。之后我们回到第二个2上,替换成3:
8 -2 -3 = 5
如果再-2就和上面答案重复了,所以不能吃回头草,后面继续减-3:
8 -2 -3 -3 = 0
结果又为0了,又得到了一个答案。到此,我们观察一下,这种从后往前,一直往前走,遇到不行的情况,再退回先前某一步的方法,是不是非常像回溯法呢?所以我们可以依据上述的思路进行回溯归纳:
我们规定一个函数f(nums, i, target),nums表示当前candidates的数组,i表示当前candidates的序号,target表示当前需要揍出的数,所以:
- 当target - nums[i] < 0 或 i越界,return;
- 当target - nums[i] > 0,继续减去nums[i],即f(nums, i, target - nums[i]);
- 当target - nums[i],得到一组答案,return;
- 考察完2中的nums[i],继续考察下一个数nums[i + 1],即f(nums, i + 1, target)
注意candidates需要先进行升序排序,上述过程才能成立,否则会跳过一些可能的答案组合。如果有童鞋对上述回溯法的流程不是很能理解,可以看看下面的流程图加深理解(图中函数省略了nums参数):
2.2 实例代码
class Solution {
vector<vector<int>> ans;
vector<int> temp;
void backTracking(vector<int>& nums, int idx, int target) {
if (idx >= nums.size() || target - nums[idx] < 0) return;
temp.push_back(nums[idx]);
if (target - nums[idx] == 0) { ans.push_back(temp); temp.pop_back(); return; }
backTracking(nums, idx, target - nums[idx]);
temp.pop_back();
backTracking(nums, idx + 1, target);
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
backTracking(candidates, 0, target);
return this->ans;
}
};