回溯
17 电话号码的组合 medium
其实这道题并不复杂,和组合总和类似,给出数字的长度就是遍历的深度,而每次递归时要遍历的字母次数就是此次递归时,对应数字对应的字符串的长度。
无非是有点儿近似真实的场景。
给出本题的代码:
const string numToLetter[8] = {
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz"};
string path = "";
vector<string> res;
void reback(int digitsLength, string digits, int digitsStartIndex) {
if (path.size() == digitsLength) {
res.push_back(path);
return;
}
int num = digits[digitsStartIndex] - '2';
for (int i = 0; i < numToLetter[num].size(); i++) {
path.push_back(numToLetter[num][i]);
reback(digitsLength, digits, digitsStartIndex + 1);
path.pop_back();
}
return;
}
vector<string> letterCombinations(string digits) {
if (digits == "") return {
};
int digitsLength = digits.size();
reback(digitsLength, digits, 0);
return res;
}
39 组合总和 medium
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。‘
这道题目比此前的组合总和IIII要复杂一点,难度在于候选序列中的数字可以无限制的选取。
递归的深度和广度都取决于目标和的大小与所选数字,
关键在于何时可以终止递归,我的想法是当当前路径和大于目标和的时候就可以退出(先对候选数组进行排序,这样是不是就可以剪枝了?)
因此,除了递归时需要多一个判断条件,递归处理时不需要每次都把起始索引值+1以外,这道题与组合总和IIII差别就不大了,代码如下:
vector<int> path;
vector<vector<int>> res;
void reback(vector<int>& candidates, int target, int sum, int startIndex) {
if (sum > target) return;
if (sum == target) {
res.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size(); i++) {
path.push_back(candidates[i]);
reback(candidates, target, sum + candidates[i], i);
path.pop_back();
}
return;
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
reback(candidates, target, 0, 0);
return res;
}
这道题目中有个好玩儿点儿是,最早的时候我确实将候选数组进行排序了,但是发现及时没有如此处理,结果也是一样的)
其实本道题还是可以剪枝的,就按照我们说的来,先对候选数组进行排序,在进入递归前就判断总和是否大于目标值就好了,取消重复的终止条件,代码如下:
vector<int> path;
vector<vector<int>> res;
void reback(vector<int>& candidates, int target, int sum, int startIndex) {
if (sum == target) {
res.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
path.push_back(candidates[i]);
reback(candidates, target, sum + candidates[i], i);
path.pop_back();
}
return;
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
reback(candidates, target, 0, 0);
return res;
}
40 组合总和II medium
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
看上去这道题貌似更简单了些,实则不然,这道题的难度在于候选数组中有重复的数字,但是题目要求相同的数字组合出的答案只能存在一组,例如[1,1,2],目标和为3,那么第一个1和2以及第二个1和2都符合条件,但是仅只能有一组答案。
所以当2已经用过一次后,就不能再用了。
首先这道题目如何想要方便处理,也是要对候选数组进行排序的。
去重的方式有两种,
一个是构造一个记录数组中的数是否用过的标志位数组,当两个数相同时,如果上个数已经被用过,那么当前的这个数就不再用了,代码如下:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
if (sum == target) {
result.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
used[i] = true;
backtracking(candidates, target, sum, i + 1, used);
used[i] = false;
sum -= candidates[i];
path.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<bool> used(candidates.size(), false);
path.clear();
result.clear();
// 首先把给candidates排序,让其相同的元素都挨在一起。
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0, used);
return result;
}
第二种方法是排序后,直接把那些相同的数字去除即可,代码如下:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
if (sum == target) {
result.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
if (i > startIndex && candidates[i] == candidates[i - 1]) {
continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i + 1);
sum -= candidates[i];
path.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
path.clear();
result.clear();
// 首先把给candidates排序,让其相同的元素都挨在一起。
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0);
return result;
}