佛山企业网站建设多少钱,wordpress默认邮件在哪里设置,安宁网站建设与制作,esuwiki wordpress算法训练DAY25|回溯2
216.组合总和III
力扣题目链接
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数#xff0c;并且每种组合中不存在重复的数字。
说明#xff1a; 所有数字都是正整数。 解集不能包含重复的组合。
示例 1: 输入: k 3, n …算法训练DAY25|回溯2
216.组合总和III
力扣题目链接
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数并且每种组合中不存在重复的数字。
说明 所有数字都是正整数。 解集不能包含重复的组合。
示例 1: 输入: k 3, n 7 输出: [[1,2,4]]
示例 2: 输入: k 3, n 9 输出: [[1,2,6], [1,3,5], [2,3,4]]
#思路
本题就是在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合。
相对于77. 组合 无非就是多了一个限制本题是要找到和为n的k个数的组合而整个集合已经是固定的了[1,...,9]。
想到这一点了做过77. 组合 之后本题是简单一些了。
本题k相当于树的深度9因为整个集合就是9个数就是树的宽度。
例如 k 2n 4的话就是在集合[1,2,3,4,5,6,7,8,9]中求 k个数 2, n和 4的组合。
选取过程如图 图中可以看出只有最后取到集合13和为4 符合条件。
#回溯三部曲 确定递归函数参数
和77. 组合 一样依然需要一维数组path来存放符合条件的结果二维数组result来存放结果集。
这里我依然定义path 和 result为全局变量。
至于为什么取名为path从上面树形结构中可以看出结果其实就是一条根节点到叶子节点的路径。
vectorvectorint result; // 存放结果集
vectorint path; // 符合条件的结果
接下来还需要如下参数 targetSumint目标和也就是题目中的n。 kint就是题目中要求k个数的集合。 sumint为已经收集的元素的总和也就是path里元素的总和。 startIndexint为下一层for循环搜索的起始位置。
所以代码如下
vectorvectorint result;
vectorint path;
void backtracking(int targetSum, int k, int sum, int startIndex)
其实这里sum这个参数也可以省略每次targetSum减去选取的元素数值然后判断如果targetSum为0了说明收集到符合条件的结果了我这里为了直观便于理解还是加一个sum参数。
还要强调一下回溯法中递归函数参数很难一次性确定下来一般先写逻辑需要啥参数了填什么参数。 确定终止条件
什么时候终止呢
在上面已经说了k其实就已经限制树的深度因为就取k个元素树再往下深了没有意义。
所以如果path.size() 和 k相等了就终止。
如果此时path里收集到的元素和sum 和targetSum就是题目描述的n相同了就用result收集当前的结果。
所以 终止代码如下
if (path.size() k) {if (sum targetSum) result.push_back(path);return; // 如果path.size() k 但sum ! targetSum 直接返回
} 单层搜索过程
本题和77. 组合 区别之一就是集合固定的就是9个数[1,...,9]所以for循环固定i9
如图 处理过程就是 path收集每次选取的元素相当于树型结构里的边sum来统计path里元素的总和。
代码如下
for (int i startIndex; i 9; i) {sum i;path.push_back(i);backtracking(targetSum, k, sum, i 1); // 注意i1调整startIndexsum - i; // 回溯path.pop_back(); // 回溯
}
别忘了处理过程 和 回溯过程是一一对应的处理有加回溯就要有减
参照关于回溯算法你该了解这些 中的模板不难写出如下C代码
class Solution {
private:vectorvectorint result; // 存放结果集vectorint path; // 符合条件的结果// targetSum目标和也就是题目中的n。// k题目中要求k个数的集合。// sum已经收集的元素的总和也就是path里元素的总和。// startIndex下一层for循环搜索的起始位置。void backtracking(int targetSum, int k, int sum, int startIndex) {if (path.size() k) {if (sum targetSum) result.push_back(path);return; // 如果path.size() k 但sum ! targetSum 直接返回}for (int i startIndex; i 9; i) {sum i; // 处理path.push_back(i); // 处理backtracking(targetSum, k, sum, i 1); // 注意i1调整startIndexsum - i; // 回溯path.pop_back(); // 回溯}}
public:vectorvectorint combinationSum3(int k, int n) {result.clear(); // 可以不加path.clear(); // 可以不加backtracking(n, k, 0, 1);return result;}
};
#剪枝
这道题目剪枝操作其实是很容易想到了想必大家看上面的树形图的时候已经想到了。
如图 已选元素总和如果已经大于n图中数值为4了那么往后遍历就没有意义了直接剪掉。
那么剪枝的地方可以放在递归函数开始的地方剪枝代码如下
if (sum targetSum) { // 剪枝操作return;
}
当然这个剪枝也可以放在 调用递归之前即放在这里只不过要记得 要回溯操作给做了。
for (int i startIndex; i 9 - (k - path.size()) 1; i) { // 剪枝sum i; // 处理path.push_back(i); // 处理if (sum targetSum) { // 剪枝操作sum - i; // 剪枝之前先把回溯做了path.pop_back(); // 剪枝之前先把回溯做了return;}backtracking(targetSum, k, sum, i 1); // 注意i1调整startIndexsum - i; // 回溯path.pop_back(); // 回溯
}
和回溯算法组合问题再剪剪枝 一样for循环的范围也可以剪枝i 9 - (k - path.size()) 1就可以了。
最后C代码如下
class Solution {
private:vectorvectorint result; // 存放结果集vectorint path; // 符合条件的结果void backtracking(int targetSum, int k, int sum, int startIndex) {if (sum targetSum) { // 剪枝操作return; }if (path.size() k) {if (sum targetSum) result.push_back(path);return; // 如果path.size() k 但sum ! targetSum 直接返回}for (int i startIndex; i 9 - (k - path.size()) 1; i) { // 剪枝sum i; // 处理path.push_back(i); // 处理backtracking(targetSum, k, sum, i 1); // 注意i1调整startIndexsum - i; // 回溯path.pop_back(); // 回溯}}
public:vectorvectorint combinationSum3(int k, int n) {result.clear(); // 可以不加path.clear(); // 可以不加backtracking(n, k, 0, 1);return result;}
}; 时间复杂度: O(n * 2^n) 空间复杂度: O(n)
17.电话号码的字母组合
力扣题目链接
给定一个仅包含数字 2-9 的字符串返回所有它能表示的字母组合。
给出数字到字母的映射如下与电话按键相同。注意 1 不对应任何字母。 示例: 输入23 输出[ad, ae, af, bd, be, bf, cd, ce, cf].
说明尽管上面的答案是按字典序排列的但是你可以任意选择答案输出的顺序。
#思路
从示例上来说输入23最直接的想法就是两层for循环遍历了吧正好把组合的情况都输出了。
如果输入233呢那么就三层for循环如果2333呢就四层for循环.......
大家应该感觉出和77.组合 遇到的一样的问题就是这for循环的层数如何写出来此时又是回溯法登场的时候了。
理解本题后要解决如下三个问题 数字和字母如何映射 两个字母就两个for循环三个字符我就三个for循环以此类推然后发现代码根本写不出来 输入1 * #按键等等异常情况
#数字和字母如何映射
可以使用map或者定义一个二维数组例如string letterMap[10]来做映射我这里定义一个二维数组代码如下
const string letterMap[10] {, // 0, // 1abc, // 2def, // 3ghi, // 4jkl, // 5mno, // 6pqrs, // 7tuv, // 8wxyz, // 9
};
#回溯法来解决n个for循环的问题
例如输入23抽象为树形结构如图所示 图中可以看出遍历的深度就是输入23的长度而叶子节点就是我们要收集的结果输出[ad, ae, af, bd, be, bf, cd, ce, cf]。
回溯三部曲 确定回溯函数参数
首先需要一个字符串s来收集叶子节点的结果然后用一个字符串数组result保存起来这两个变量我依然定义为全局。
这个index是记录遍历第几个数字了就是用来遍历digits的题目中给出数字字符串同时index也表示树的深度。
代码如下
vectorstring result;
string s;
void backtracking(const string digits, int index) 确定终止条件
例如输入用例23两个数字那么根节点往下递归两层就可以了叶子节点就是要收集的结果集。
那么终止条件就是如果index 等于 输入的数字个数digits.size了本来index就是用来遍历digits的。
然后收集结果结束本层递归。
代码如下
if (index digits.size()) {result.push_back(s);return;
} 确定单层遍历逻辑
首先要取index指向的数字并找到对应的字符集手机键盘的字符集。
然后for循环来处理这个字符集代码如下
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); // 递归注意index1一下层要处理下一个数字了s.pop_back(); // 回溯
}
注意输入1 * #按键等等异常情况
代码中最好考虑这些异常情况但题目的测试数据中应该没有异常情况的数据所以我就没有加了。
但是要知道会有这些异常如果是现场面试中一定要考虑到
// 版本一
class Solution {
private:const string letterMap[10] {, // 0, // 1abc, // 2def, // 3ghi, // 4jkl, // 5mno, // 6pqrs, // 7tuv, // 8wxyz, // 9};
public:vectorstring result;string s;void backtracking(const string digits, int index) {if (index digits.size()) {result.push_back(s);return;}int digit digits[index] - 0; // 将index指向的数字转为intstring letters letterMap[digit]; // 取数字对应的字符集for (int i 0; i letters.size(); i) {s.push_back(letters[i]); // 处理backtracking(digits, index 1); // 递归注意index1一下层要处理下一个数字了s.pop_back(); // 回溯}}vectorstring letterCombinations(string digits) {s.clear();result.clear();if (digits.size() 0) {return result;}backtracking(digits, 0);return result;}
}; 时间复杂度: O(3^m * 4^n)其中 m 是对应四个字母的数字个数n 是对应三个字母的数字个数 空间复杂度: O(3^m * 4^n)
一些写法是把回溯的过程放在递归函数里了例如如下代码我可以写成这样注意注释中不一样的地方
// 版本二
class Solution {
private:const string letterMap[10] {, // 0, // 1abc, // 2def, // 3ghi, // 4jkl, // 5mno, // 6pqrs, // 7tuv, // 8wxyz, // 9};
public:vectorstring result;void getCombinations(const string digits, int index, const string s) { // 注意参数的不同if (index digits.size()) {result.push_back(s);return;}int digit digits[index] - 0;string letters letterMap[digit];for (int i 0; i letters.size(); i) {getCombinations(digits, index 1, s letters[i]); // 注意这里的不同}}vectorstring letterCombinations(string digits) {result.clear();if (digits.size() 0) {return result;}getCombinations(digits, 0, );return result;
}
};
我不建议把回溯藏在递归的参数里这种写法很不直观我在二叉树以为使用了递归其实还隐藏着回溯 这篇文章中也深度分析了回溯隐藏在了哪里。
所以大家可以按照版本一来写就可以了。