南海建设网站,h5 移动 网站 开发,平台推广计划,dw网页制作破解版216.组合总和 III
216. 组合总和 III - 力扣#xff08;LeetCode#xff09;https://leetcode.cn/problems/combination-sum-iii/
题目描述#xff1a;
找出所有相加之和为 n 的 k 个数的组合#xff0c;且满足下列条件#xff1a;
只使用数字1到9每个数字 最多使用一…216.组合总和 III
216. 组合总和 III - 力扣LeetCodehttps://leetcode.cn/problems/combination-sum-iii/
题目描述
找出所有相加之和为 n 的 k 个数的组合且满足下列条件
只使用数字1到9每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次组合可以以任何顺序返回。 示例 1:
输入: k 3, n 7
输出: [[1,2,4]]
解释:
1 2 4 7
没有其他符合的组合了。
示例 2:
输入: k 3, n 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 2 6 9
1 3 5 9
2 3 4 9
没有其他符合的组合了。
示例 3:
输入: k 4, n 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字我们可以得到的最小和是1234 10因为10 1没有有效的组合。
思路分析
这道题是在77.组合的基础上做了改进相关回溯解法可以参考上一篇博文
算法练习第20天|回溯算法 77.组合问题 257. 二叉树的所有路径-CSDN博客
和77组合题目一样本题抽象成二叉树的逻辑如下图片来自卡哥的代码随想录 使用回溯的解法我们依然要使用path和result两个vector一个用于记录遍历路径另一个用于记录满足条件的结果
vectorvectorint result; //满足条件的结果
vectorint path; //当前路径
下面我们回顾一下上一篇博文中所讲的回溯三部曲
第一步确认回溯函数的参数和返回值。
void backtracking(参数)
{
}
一般来说回溯函数的返回值类型为void至于参数为了表达方便我们定义了目标和targetSum即题目中的n、k、遍历到当前路径的和sum、以及每一层回溯的开始索引startIndex。具体代码如下
// int targetSum; //目标和
// int k; //k个元素
// int sum; //当前path记录的元素的和
// int startIndex; //开始的索引
//回溯第一步确认回溯函数的参数以及返回值
void backTracking(int targetSum, int k, int sum, int startIndex)
{
}
第二步确认回溯的终止条件。
if (终止条件) {存放结果;return;
}
什么时候本次回溯终止那就是我们成功的找到了一组数据里面有k个元素且它们的和为n。那么终止条件就是 path.size() k sum targetSum。找到这一组数据则需要将其记录在结果result中然后return。具体代码如下
if(path.size() k sum targetSum)
{result.push_back(path);return;
}
但其实更严谨的逻辑应该是 先检查是否遍历了k个元素即path的size是否为k。如果遍历了k个元素则判断它们的和sum是否等于targetSum如果相等则记录该组数据不相等则不记录。但是不论是否相等此时已经是遍历了k个元素已经不能再继续遍历了所以直接return结束掉本次的回溯。代码如下
if(path.size() k )
{if(sum targetSum)result.push_back(path);return;
}
如果不满足上述的第一个条件则说明还没有遍历到k个元素应该执行单层回溯的具体逻辑。
第三步确认单层回溯的遍历过程
for (选择本层集合中元素树中节点孩子的数量就是集合的大小) {处理节点;backtracking(路径选择列表); // 递归回溯撤销处理结果
}
这一过程要做哪些事情
首先从startIndex开始遍历元素将当前元素加到sum中并用path加以记录然后就可以从startIndex1的位置进行递归了递归之后该记录的结果会进行记录。然后要进行抽象成的二叉树遍历过程回溯过程即当前节点退出sum和path具体代码如下
for(int i startIndex; i9; i){//处理节点sum i;path.push_back(i);//递归backTracking(targetSum, k, sum, i1); //注意将i1调整为startIndex//回溯sum - i;path.pop_back(i);}
完整代码
class Solution {
public:vectorvectorint result; //满足条件的结果vectorint path; //当前路径// int targetSum; //目标和// int k; //k个元素// int sum; //当前path记录的元素的和// int startIndex; //开始的索引//回溯第一步确认回溯函数的参数以及返回值void backTracking(int targetSum, int k, int sum, int startIndex){//回溯第二步确认回溯函数的终止条件//什么时候终止此次回溯那就是找到了一组数k个数且它们的和为nif(path.size() k ){if(sum targetSum)result.push_back(path);return;}//回溯第三步确认单层回溯的遍历过程//单层回溯要做那些事情遍历从startIndex开始遍历for(int i startIndex; i9; i){//处理节点sum i;path.push_back(i);//递归backTracking(targetSum, k, sum, i1); //注意将i1调整为startIndex//回溯sum - i;path.pop_back(i);}}vectorvectorint combinationSum3(int k, int n) {backTracking(n,k,0,1);return result;}
};
进一步剪枝处理优化代码
剪枝其实就是在二叉树遍历逻辑的基础上去点一些明显没有必要的遍历路径如下图所示 当遍历元素的和大于题目要求的n时其实已经没有意义了。
那么剪枝的地方可以放在递归函数开始的地方剪枝代码如下
if (sum targetSum) { // 剪枝操作return;
} 除此之外遍历过程也可以再剪枝
接下来看一下优化过程如下 已经选择的元素个数path.size(); 所需需要的元素个数为: k - path.size(); 列表中剩余元素n-i 所需需要的元素个数k - path.size() 在集合n中至多要从该起始位置 : i n - (k - path.size()) 1开始遍历往后找还能找到k个数。
代码如下
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;}
};
17.电话号码的字母组合
17. 电话号码的字母组合 - 力扣LeetCodehttps://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/题目描述
给定一个仅包含数字 2-9 的字符串返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下与电话按键相同。注意 1 不对应任何字母。 示例 1
输入digits 23
输出[ad,ae,af,bd,be,bf,cd,ce,cf]示例 2
输入digits
输出[]示例 3
输入digits 2
输出[a,b,c] 数字和字母如何映射
可以使用map或者定义一个二维数组例如string letterMap[10]来做映射我这里定义一个二维数组代码如下 const string letterMap[10] {, //0, //1abc, //2def, //3ghi, //4jkl, //5mno, //6pqrs, //7tuv, //8wxyz, //9};
回溯法来解决n个for循环的问题
例如输入23抽象为树形结构如图所示 这就和之前的77组合以及216组合III有了相似之处。然后只用回溯三部曲。
回溯三部曲
确定回溯函数参数
首先需要一个字符串s来收集叶子节点的结果然后用一个字符串数组result保存起来这两个变量我依然定义为全局。
再来看参数参数指定是有题目中给的string digits然后还要有一个参数就是int型的index。这个index用来表示遍历到digits中的第几个数字了。与之前的startIndex不一样。 vectorstring result;string path;//回溯第一步void backTracking(const string digits, int index){} 确认回溯的终止条件
index表示遍历到了digits的第几个数字其初始值为0遍历过一个数字后就会1那么当index等于digits.size时表明遍历完毕。代码如下
if (index digits.size()) {result.push_back(s);return;
}
确认单层回溯逻辑
首先要根据index把数字对应的字符串取出来然后遍历该字符串将字母加入到路径path中。添加完一个字母对应模板中的处理节点则 该去执行递归index1然后回溯
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(); // 回溯
}
整体代码如下
class Solution {
private:const string letterMap[10] {, //0, //1abc, //2def, //3ghi, //4jkl, //5mno, //6pqrs, //7tuv, //8wxyz, //9};public:vectorstring result;string path;//回溯第一步void backTracking(const string digits, int index){//回溯第二步确认回溯终止条件if(index digits.size()){result.push_back(path);return ;}//回溯第三步确认单层回溯逻辑操作int num digits[index] - 0; //获取对应的数字string letters letterMap[num]; //获取该数字对应的字母 for(int i 0 ; i letters.size(); i){path.push_back(letters[i]); //加入一个字母backTracking(digits, index1); //找下一个数字对应的字母path.pop_back(); //回溯}}vectorstring letterCombinations(string digits) {if(digits.empty())return result;backTracking(digits, 0);return result;}
};