文章目录
216. 组合总和 III
分析
本题相当于是在
77. 组合多了一个判断找出的集合之和等于n的判断
因为数只能取从1 ~ 9
思路
回溯三部曲
- 第一曲:确定递归函数参数
依然定义path 和 result为全局变量
vector<vector<int>> result; // 存放结果集
vector<int> path; // 符合条件的结果
- targetSum(int)目标和,也就是题目中的n。
- k(int)就是题目中要求k个数的集合。
- sum(int)为已经收集的元素的总和,也就是path里元素的总和。
- startIndex(int)为下一层for循环搜索的起始位置。
vector<vector<int>> result;
vector<int> path;
void backtracking(int targetSum, int k, int sum, int startIndex)
补充一下: 回溯算法递归的参数很难一次确定,需要用到什么参数,填什么参数
- 第二曲:确定终止条件
跟组合一样,遍历到第k层其实就已经将树的深度遍历完了
if (path.size() == k) {
if (sum == targetSum) result.push_back(path);
return; // 如果path.size() == k 但sum != targetSum 直接返回
}
- 第三曲: 单层搜索
每一层都是遍历的数都是上一层的数-1
,因为集合中不能出现重复的数
for (int i = startIndex; i <= 9; i++) {
sum += i;
path.push_back(i);
backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndex
sum -= i; // 回溯
path.pop_back(); // 回溯
}
思考这段代码的时候,暂时不要去考虑backtracking里面的过程,直接到sum -= i
和pop.pop_back
,然后进行下一层for循环,这两个相当于是清理战场了,然后换一个i
再打一遍
Code
C++
class Solution {
public:
vector<int> path;
vector<vector<int>>res;
void backtracking(int targetSum, int k, int sum, int startIndex){
// 终止条件
if(path.size() == k){
if(targetSum == sum) {
res.push_back(path);
return ;
}
}
//单层搜索
for(int i = startIndex; i <= 9 ; i++){
path.push_back(i);
sum += i;
backtracking(targetSum,k,sum,i+1);
sum -= i;
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(n, k, 0, 1);
return res;
}
};
剪枝优化
如果已经选的元素 已经大于targetSum
了,那么也没有必要继续往下遍历了
// 剪枝操作
if(sum) > targetSum{
return;
}
最终代码
class Solution {
public:
vector<int> path;
vector<vector<int>>res;
void backtracking(int targetSum, int k, int sum, int startIndex){
if(sum > targetSum) return;
// 终止条件
if(path.size() == k){
if(targetSum == sum) {
res.push_back(path);
return ;
}
}
//单层搜索
for(int i = startIndex; i <= 9 ; i++){
path.push_back(i);
sum += i;
backtracking(targetSum,k,sum,i+1);
sum -= i;
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(n, k, 0, 1);
return res;
}
};
17. 电话号码的字母组合
分析
本题一眼看可以暴力,因为如果digit = 2
, 那么两层for魂环酒后了. 但如果digit=10086
层呢,for循环是并不可能的. 这是和77. 组合在刚开始分析时一样的问题
理解本题后,要解决如下三个问题:
- 数字和字母如何映射
- 两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来
- 输入1 * #按键等等异常情况
思路
数字和字母如何映射
可以用map
, 或者定义一个二维数组,
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
回溯法来解决n个for循环的问题
依然是: 深度就是字母集合的长度
回溯三部曲
- 第一曲: 确定回溯函数参数
这个在实际做题中不可能一下子想出来,用到什么写什么
vector<string> result;//字符串数组result保存起来
string s;//收集叶子节点的结果
void backtracking(const string& digits, int index)//记录遍历第几个数字
const string& digit相当于取了一个别名,引用digit但不改变其字符串内容
- 确定终止条件
if(intdex == digits.size()){//遍历到树底了
result.push_back(s);
return;
}
- 确定单层遍历逻辑
int digit = digits[index] - '0'; // 将index指向的数字转为int
string letters = letterMap[digit]; // 取数字对应的字符集
for (int i = 0; i < letters.size(); i++) {
s.push_back(letters[i]); // 处理
backtracking(digits, index + 1); // 递归,注意index+1,一下层要处理下一个数字了
s.pop_back(); // 回溯
}
这里的for循环并不是从startIndex开始遍历的 因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而77. 组合 (opens new window)和216.组合总和III (opens new window)都是求同一个集合中的组合!
注意:输入1 * #按键等等异常情况
Code
class Solution {
public:
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
string s;
vector<string>res;
void backtracking(const string& digits, int index){
if(index == digits.size()){
res.push_back(s);
return;
}
int digit = digits[index] - '0';
string letters = letterMap[digit];
for(int i = 0; i < letters.size(); i++){
s.push_back(letters[i]);
backtracking(digits, index + 1);
s.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if(digits.size() == 0) return res;
backtracking(digits,0) ;
return res;
}
};