216. 组合总和 III
1.思路
对一个范围的数选k个数的组合,当组合sum为n时输出,组合问题用回溯
2.回溯法
class Solution {
public:
vector<int>path;
vector<vector<int>>result;
void backtracking(int k, int n, int startindex, int sum) {
if (path.size() == k) {
if (sum == n) result.push_back(path);
return;
}
for (int i = startindex; i <= 9; i++) {
sum += i;
path.push_back(i);
backtracking(k, n, i + 1, sum);
sum -= i;
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(k, n, 1, 0);
return result;
}
};
3.回溯三部曲
1.确定递归函数的参数:
除了题目给出的k,n,还需要startindex来记录当前位置来防止遍历到以前的元素,重复加入结果集中,还需要一个sum,每次加入结果集中,求一次总和。
为什么用sum而不去在path长度=k时候判断?
因为定义一个sum,边求和边遍历,可以减少vector求和参数,而且你这样不能够剪枝,用sum剪枝
2.确定终止条件
当结果集的长度==k,说明得到了一个结果就可以提前结束了。
3.确定单层搜索逻辑
for循环,每次取一个元素加入path数组中,当数组元素到达k时,返回到递归函数下一层执行回溯函数,就返回上一层没有加值的那个时候,这个时候for循环进行下一层再加入一个值
4.剪枝
class Solution {
public:
vector<int>path;
vector<vector<int>>result;
void backtracking(int k, int n, int startindex, int sum) {
if (sum > n) return;//剪枝
if (path.size() == k) {
if (sum == n) result.push_back(path);
return;
}
for (int i = startindex; i <= 9 - (k - path.size()) + 1; i++) {//剪枝
sum += i;
path.push_back(i);
backtracking(k, n, i + 1, sum);
sum -= i;
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(k, n, 1, 0);
return result;
}
};
5.两个剪枝技巧
1.和上一题一样,当全加入后面的元素还是不满足k个元素时就可以提前结束,所以只需要遍历到符合条件的点,对传入的startindex做手脚,对for循环减少遍历。
for (int i = startindex; i <= 9 - (k - path.size()) + 1; i++) {//剪枝
2.可以对另一个参数sum进行剪枝,return的时候可以提前结束循环,当sum大于目标值n的时候就可以不往后面遍历了,就可以提前结束了。
if (sum > n) return;//剪枝
题目链接:17. 电话号码的字母组合
1.思路
找到所选数字的所有组合。
每次在一个映射的字符串中加入一个字符串,直到到达加入的数量最大值,回溯到下一层循环,加入下一个。
问题1:为什么不用startindex而用index?
因为上一题是一个集合的选多少个,所以用startindex来避免重复,如果再从0开始就会遇到遍历过的,所以用startindex,
而这里再每个集合中各选择一个,只需要下标来记录遍历到了哪一个数组,当超出这个数组就会回溯到下一层循环加入新的数,每个数的不一样所以不会重复。
问题2:数字和字母如何映射?
可以定义一个字符串数组lettermap,每个下标储存2-9的字符串
const string letterMap[10] = { "", // 0 "", // 1 "abc", // 2 "def", // 3 "ghi", // 4 "jkl", // 5 "mno", // 6 "pqrs", // 7 "tuv", // 8 "wxyz", // 9 };
问题3:两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来
利用回溯法解决多个for循环问题,一个下标标记循环到哪里
2.回溯法
class Solution {
public:
const string lettermap[10] = {
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz"
};
string path;
vector<string> result;
void backtracking(string& digits, int index) {
if (index == digits.size()) {
result.push_back(path);
return;
}
int digit = digits[index] - '0';
string st = lettermap[digit];
for (int i = 0; i < st.size(); i++) {
path.push_back(st[i]);
backtracking(digits, index + 1);
path.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if (digits.size() == 0) {
return result;
}
backtracking(digits, 0);
return result;
}
};
3.递归三部曲
1.确定回溯函数的参数
除了题目提供的digits,还要一个index记录现在遍历到了哪个哪个lettermap下标选择哪个字符集中的元素。这个index就是遍历到第几个数组了
2.确定终止条件
index因为是从0开始,当下标等于path的元素个数时,说明结束,就可以加入到result中。
3.确定单层遍历的逻辑
观察二叉树,每次加入一个数在这个index中,再循环遍历到下一层,再加入这一层的字符,当加入满后就加入到result中到回溯函数中,取出最后一层的值返回遍历for循环的下一个,当遍历完这层循环后退回到倒数第二层循环再加入一个新的再到最后一层重复进行。
注意:当为空时需要判断