216. 组合总和III
题目描述: 216. 组合总和 III
原文链接: 216. 组合总和 III
视频链接: 216. 组合总和 III
树形结构
回溯三部曲:
① 确定回溯函数参数及返回值
和 77. 组合 一样,依然需要一维数组 path 来存放符合条件的结果,二维数组 res 来存放结果集。
这里依然定义 path 和 res 为全局变量。
vector<int> path; // 符合条件的结果
vector<vector<int>> res; // 存放结果集
接下来还需要如下参数:
targetSum(int)目标和,也就是题目中的 n。
sum(int)为已经收集的元素的总和,也就是 path 里元素的总和。
k(int)就是题目中要求 k 个数的集合。
startIndex(int)为下一层 for 循环搜索的起始位置。
vector<int> path;
vector<vector<int>> res;
void backtracking(int targetSum, int sum, int k, int startIndex) {
② 确定终止条件
所以如果 path.size() 和 k 相等了,就终止。
如果此时 path 里收集到的元素和(sum) 和 targetSum(就是题目描述的 n)相同了,就用 res 收集当前的结果。
if (path.size() == k) {
if (sum == targetSum) {
res.push_back(path);
}
return;
}
③ 确定单层递归逻辑
本题和 77. 组合 区别之一就是集合固定的就是 9 个数 [1,…,9],所以 for 循环固定 i <= 9(如图)
处理过程就是 path 收集每次选取的元素,相当于树形结构里的边,sum 来统计 path 里元素的总和。
处理过程 和 回溯过程 是一一对应的,处理有加,回溯就要有减!
for (int i = startIndex; i <= 9; i++) {
sum += i;
path.push_back(i);
backtracking(targetSum, sum, k, i + 1);
path.pop_back();
sum -= i;
}
代码如下:
class Solution {
private:
vector<int> path;
vector<vector<int>> res;
void backtracking(int targetSum, int sum, int k, int startIndex) {
if (path.size() == k) {
if (sum == targetSum) {
res.push_back(path);
}
return;
}
for (int i = startIndex; i <= 9; i++) {
sum += i;
path.push_back(i);
backtracking(targetSum, sum, k, i + 1);
path.pop_back();
sum -= i;
}
}
public:
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(n, 0, k, 1);
return res;
}
};
剪枝
① 已选元素总和 sum 如果已经大于 targetSum 了,那么往后遍历就没有意义了,直接剪掉。
那么剪枝的地方可以放在递归函数开始的地方,剪枝代码如下:
if (sum > targetSum) { // 剪枝操作
return;
}
② for 循环的范围也可以剪枝,i <= 9 - (k - path.size()) + 1;
剪枝后代码如下:
class Solution {
private:
vector<int> path;
vector<vector<int>> res;
void backtracking(int targetSum, int sum, int k, int startIndex) {
if (sum > targetSum) {
return;
}
if (path.size() == k) {
if (sum == targetSum) {
res.push_back(path);
}
return;
}
for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
sum += i;
path.push_back(i);
backtracking(targetSum, sum, k, i + 1);
path.pop_back();
sum -= i;
}
}
public:
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(n, 0, k, 1);
return res;
}
};
17. 电话号码的字母组合
题目链接:17. 电话号码的字母组合
原文链接:17. 电话号码的字母组合
视频链接:17. 电话号码的字母组合
数字和字母如何映射
可以使用 map 或者定义一个二维数组,例如:string lettersMap[10],来做映射:
const string lettersMap[11] = {
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz"
};
树形结构
图中可以看出遍历的深度,就是输入 “23” 的长度,而叶子节点就是我们要收集的结果,输出 [“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”]。
回溯三部曲
① 确定回溯函数参数及返回值
首先需要一个字符串 s 来收集叶子节点的结果,然后用一个字符串数组 res 保存起来,这两个变量依然定义为全局。
再来看参数,参数指定是有题目中给的 string digits,然后还要有一个参数就是 int 型的 index。
这个 index 是记录遍历第几个数字了,就是用来遍历 digits 的(题目中给出数字字符串),同时 index 也表示树的深度。
string s;
vector<string> res;
void backtracking(const string& digits, int index)
② 确定终止条件
例如输入用例 “23”,两个数字,那么根节点往下递归两层就可以了,叶子节点就是要收集的结果集。
那么终止条件就是如果 index 等于 输入的数字个数(digits.size)了(本来 index 就是用来遍历 digits 的)。
然后收集结果,结束本层递归。
if (index == digits.size()) {
res.push_back(s);
return;
}
③ 确定单层递归逻辑
首先要取 index 指向的数字,并找到对应的字符集(手机键盘的字符集)。
然后 for 循环来处理这个字符集,代码如下:
int digit = digits[index] - '0'; // 将index指向的数字转为int
string letters = lettersMap[digit]; // 取数字对应的字符集
for (int i = 0; i < letters.size(); i++) {
s.push_back(letters[i]); // 处理
backtracking(digits, index + 1); // 递归,注意index + 1,一下层要处理下一个数字了
s.pop_back(); // 回溯
}
注意这里 for 循环,要从 0 开始遍历。
因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而 77. 组合 和 216.组合总和III 都是求同一个集合中的组合。
代码如下:
class Solution {
private:
const string lettersMap[11] = {
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz"
};
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 = lettersMap[digit];
for (int i = 0; i < letters.size(); i++) {
s.push_back(letters[i]);
backtracking(digits, index + 1);
s.pop_back();
}
}
public:
vector<string> letterCombinations(string digits) {
if (digits.size() == 0) {
return res;
}
backtracking(digits, 0);
return res;
}
};