代码随想录算法训练DAY25|回溯2
算法训练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 = 2,n = 4的话,就是在集合[1,2,3,4,5,6,7,8,9]中求 k(个数) = 2, n(和) = 4的组合。
选取过程如图:
图中,可以看出,只有最后取到集合(1,3)和为4 符合条件。
#回溯三部曲
-
确定递归函数参数
和77. 组合 一样,依然需要一维数组path来存放符合条件的结果,二维数组result来存放结果集。
这里我依然定义path 和 result为全局变量。
至于为什么取名为path?从上面树形结构中,可以看出,结果其实就是一条根节点到叶子节点的路径。
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)
其实这里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循环固定i<=9
如图:
处理过程就是 path收集每次选取的元素,相当于树型结构里的边,sum来统计path里元素的总和。
代码如下:
for (int i = startIndex; i <= 9; i++) {sum += i;path.push_back(i);backtracking(targetSum, k, sum, i + 1); // 注意i+1调整startIndexsum -= i; // 回溯path.pop_back(); // 回溯 }
别忘了处理过程 和 回溯过程是一一对应的,处理有加,回溯就要有减!
参照关于回溯算法,你该了解这些! 中的模板,不难写出如下C++代码:
class Solution { private:vector<vector<int>> result; // 存放结果集vector<int> 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); // 注意i+1调整startIndexsum -= i; // 回溯path.pop_back(); // 回溯}} public:vector<vector<int>> 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); // 注意i+1调整startIndexsum -= i; // 回溯path.pop_back(); // 回溯 }
和回溯算法:组合问题再剪剪枝 一样,for循环的范围也可以剪枝,i <= 9 - (k - path.size()) + 1就可以了。
最后C++代码如下:
class Solution { private:vector<vector<int>> result; // 存放结果集vector<int> 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); // 注意i+1调整startIndexsum -= i; // 回溯path.pop_back(); // 回溯}} public:vector<vector<int>> 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"", // 1"abc", // 2"def", // 3"ghi", // 4"jkl", // 5"mno", // 6"pqrs", // 7"tuv", // 8"wxyz", // 9 };
#回溯法来解决n个for循环的问题
例如:输入:"23",抽象为树形结构,如图所示:
图中可以看出遍历的深度,就是输入"23"的长度,而叶子节点就是我们要收集的结果,输出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]。
回溯三部曲:
-
确定回溯函数参数
首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组result保存起来,这两个变量我依然定义为全局。
这个index是记录遍历第几个数字了,就是用来遍历digits的(题目中给出数字字符串),同时index也表示树的深度。
代码如下:
vector<string> 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); // 递归,注意index+1,一下层要处理下一个数字了s.pop_back(); // 回溯 }
注意:输入1 * #按键等等异常情况
代码中最好考虑这些异常情况,但题目的测试数据中应该没有异常情况的数据,所以我就没有加了。
但是要知道会有这些异常,如果是现场面试中,一定要考虑到!
// 版本一 class Solution { private:const string letterMap[10] = {"", // 0"", // 1"abc", // 2"def", // 3"ghi", // 4"jkl", // 5"mno", // 6"pqrs", // 7"tuv", // 8"wxyz", // 9}; public:vector<string> 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); // 递归,注意index+1,一下层要处理下一个数字了s.pop_back(); // 回溯}}vector<string> 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"", // 1"abc", // 2"def", // 3"ghi", // 4"jkl", // 5"mno", // 6"pqrs", // 7"tuv", // 8"wxyz", // 9}; public:vector<string> 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]); // 注意这里的不同}}vector<string> letterCombinations(string digits) {result.clear();if (digits.size() == 0) {return result;}getCombinations(digits, 0, "");return result; } };
我不建议把回溯藏在递归的参数里这种写法,很不直观,我在二叉树:以为使用了递归,其实还隐藏着回溯 这篇文章中也深度分析了,回溯隐藏在了哪里。
所以大家可以按照版本一来写就可以了。
相关文章:

代码随想录算法训练DAY25|回溯2
算法训练DAY25|回溯2 216.组合总和III 力扣题目链接 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。 说明: 所有数字都是正整数。 解集不能包含重复的组合。 示例 1: 输入: k 3, n …...

hanlp,pkuseg,jieba,cutword分词实践
总结:只有jieba,cutword,baidu lac成功将色盲色弱成功分对,这两个库字典应该是最全的 hanlp[持续更新中] https://github.com/hankcs/HanLP/blob/doc-zh/plugins/hanlp_demo/hanlp_demo/zh/tok_stl.ipynb import hanlp # hanlp.pretrained.tok.ALL # 语种见名称最…...

一个简单的Vue实例
Vue.js 是一个流行的 JavaScript 框架,用于构建交互式的网页应用。一个基本的 Vue 实例包含数据对象、模板、挂载点、方法和生命周期钩子等。 以下是一个简单的 Vue 实例示例: // 创建一个新的 Vue 实例 var app new Vue({el: #app, // 指定一个挂载点…...

【GoLang入门教程】Go语言几种标准库介绍(八)
ChatGPT 和文心一言哪个更好用? 文章目录 ChatGPT 和文心一言哪个更好用?强烈推荐前言几种库runtime库 ( 运行时接口)常用的函数:示例 sort库(排序接口)主要的函数和接口:示例 strings库(字符串转换、解析及…...

[系统安全] 五十四.恶意软件分析 (6)PE文件解析及利用Python获取样本时间戳
您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列。因此,我重新开设了这个专栏,准备系统整理和深入学习系统安全、逆向分析和恶意代码检测,“系统安全”系列文章会更加聚焦,更加系…...

kafka入门(九):副本
副本 副本(Replica),指的是分布式系统对数据和服务提供的一种冗余方式。 Kafka通过多副本机制实现故障自动转移,在Kafka集群中某个broker节点失效的情况下仍然保证服务可用。 kafka 副本之间是 一主多从的关系。 其中 leader 副…...

【5G 接口协议】N2接口协议NGAP(NG Application Protocol)介绍
博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持! 博主链接 本人就职于国际知名终端厂商,负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。 博客…...

2024年甘肃省职业院校技能大赛信息安全管理与评估 样题三 模块二
竞赛需要完成三个阶段的任务,分别完成三个模块,总分共计 1000分。三个模块内容和分值分别是: 1.第一阶段:模块一 网络平台搭建与设备安全防护(180 分钟,300 分)。 2.第二阶段:模块二…...

Python自动化我选DrissionPage,弃用Selenium
DrissionPage 是一个基于 python 的网页自动化工具。 它既能控制浏览器,也能收发数据包,还能把两者合而为一。 可兼顾浏览器自动化的便利性和 requests 的高效率。 它功能强大,内置无数人性化设计和便捷功能。 它的语法简洁而优雅&#x…...

MQ 消息丢失、重复、积压问题,如何解决?
面试官在面试候选人时,如果发现候选人的简历中写了在项目中使用了 MQ 技术(如 Kafka、RabbitMQ、RocketMQ),基本都会抛出一个问题:在使用 MQ 的时候,怎么确保消息 100% 不丢失? 这个问题在实际…...

【Linux】第三十三站:日志
文章目录 一、实现一个简单的日志1.简介2.可变参数3.错误等级4.时间5.打印每一条参数6.与前面的一些代码搭配使用 二、完整代码 一、实现一个简单的日志 1.简介 我们运行代码的时候,我们希望有各种各样的运行时候的一些信息。这也就是日志 它一半有日志时间&…...

MVC和MVVM区别和VUE关系
MVC(Model-View-Controller)和 MVVM(Model-View-ViewModel)是两种常见的前端架构模式,它们的主要区别在于处理业务逻辑和数据操作的方式。 MVC中,View(视图)可以直接访问Model&…...

vue3自定义按钮点击变颜色实现(多选功能)
实现效果图: 默认选中第一个按钮,未选中按钮为粉色,点击时颜色变为红色 利用动态类名,当定义isChange数值和下标index相同时,赋予act类名,实现变色效果 <template><div class"page"&…...

Redis的key过期策略是怎么实现的
这是一道经典的Redis面试题,一个Redis中可能存在很多很多的key,这些key中可能有很大一部分都有过期时间,此时Redis服务器咋知道哪些key已经过期,哪些还没过期呢? 如果直接遍历所有的key,这显然是行不通的&…...

vue+elenemt分页+springboot
目录 1、编写模板 2、发请求调接口 3、后端返回数据 1.编写实体类 2、UserController 3、Userservice接口 4、(mapper接口)UserMapper 5、xml 1、编写模板 <!-- 搜素框 --><el-input placeholder"请输入姓名" v-model"ke…...

C++ :命名空间域
目录 冲突与命名: 举个例子: 全局与局部: 域作用限定符: 命名空间域: 冲突与命名: 在C语言中,我们通常会使用stdlib.h 而stdlib.h 本质上是一个函数的库,在程序中使用的大多数…...

提升网站关键词排名的工具
随着互联网的蓬勃发展,网站的关键词排名成为衡量网站流量和曝光度的重要指标。在这个竞争激烈的数字时代,站在搜索引擎结果的前列变得至关重要。为了实现这一目标,合理利用关键词排名优化工具是必不可少的。本文将重点介绍147SEO软件…...

ICMP控制消息 汇总
控制消息由 类型 字段中的值标识。代码 字段给出了消息的附加上下文信息。自协议首次引入以来,一些控制消息已被弃用。 重要的ICMP Control Message控制信息 类型码状态描述0 –回声回复:140回声回复(用于ping)1和2未分配已预留3 …...

C#,入门教程(22)——函数的基础知识
上一篇: C#,入门教程(21)——命名空间(namespace)与程序结构的基础知识https://blog.csdn.net/beijinghorn/article/details/124140653 一、函数的基本概念 一个软件的结构大体如下: 大厦application: a plaza { --…...

已经30了,5年多,只会功能测试的怎么办?
🍅 视频学习:文末有免费的配套视频可观看 🍅 关注公众号【互联网杂货铺】,回复 1 ,免费获取软件测试全套资料,资料在手,涨薪更快 这两天一直在和网易的朋友聊软件测试的发展,这一行的…...

什么是UML?有什么用?
2、什么是UML?有什么用? UML 是 Unified Model Language的缩写,中文是统一建模语言,是由一整套图表组成的标准化建模语言。 UML 是一种统一建模语言,一种图标式语言(画图的) UML 不是只有 Java 中使用&…...

盘点好用内容合规监测工具
网页敏感内容监测 Web Purify 由 WebPurify 提供,这是一个专门从事内容审核和过滤服务的公司。 核心功能 ● 文本审核:加强脏话过滤,标记仇恨言论、偏执、性挑逗等 ● 图片审核:让个人资料照片、社交应用程序、产品定制远离令…...

CC工具箱使用指南:【查找锐角】
一、简介 在面要素中,尖锐角往往是有问题的地方。 在一系列空间分析后,通常会遗留下来部分尖锐角,需要手动处理。 但是人工去找出这些尖锐角又比较麻烦,这个工具的目的就是找出面要素边界的尖锐角。 二、工具参数介绍 右键点击…...

kafka消费相关问题(GPT回答版本)
kafka消费相关问题(GPT回答版本) 在Java中,要避免重复消费Kafka消息,可以使用以下方法 1. 使用消费者组: 在设置Kafka消费者时,可以指定一个消费者组。一个消费者组中可以有多个消费者实例,每…...

【C++】string的基本使用二
我们接着上一篇的迭代器说起,迭代器不只有正向的,还有反向的,就是我们下边的这两个 它的迭代器类型也是不同的 rbegin就是末尾,rend就是开头,这样我们想遍历一个string对象的话就可以这样做 int main() {string s1(…...

MATLAB解决考研数学一题型(上)
闲来无事,情感问题和考研结束后的戒断反应比较严重,最近没有什么写博文的动力,抽空来整理一下考研初试前一直想做的工作——整理一下MATLAB解决数学一各题型的命令~ 本贴的目录遵循同济版的高数目录~ 目录 一.函数与极限 1.计算双侧极限 2…...

Vue以弹窗形式实现导入功能
目录 前言正文 前言 由于个人工作原因,偏全栈,对于前端的总结还有些初出茅庐,后续会进行规整化的总结 对应的前端框架由:【vue】avue-crud表单属性配置(表格以及列) 最终实现的表单样式如下:…...

分布式锁原理及实现
目录 一、锁的使用场景 二、如何实现控制? 三、单台服务器使用锁的场景 四、分布式锁 五、Redis 实现分布式锁及存在问题 六、Redisson 实现分布式锁 七、定时任务+锁 一、锁的使用场景 1. 控制定时任务执行 定时任务多次执行浪费资源ÿ…...

蓝桥杯官网填空题(海盗与金币)
题目描述 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。 12名海盗在一个小岛上发现了大量的金币,后统计一共有将近5万枚。 登上小岛是在夜里,天气又不好。由于各种原因,有的海盗偷拿了很…...

JavaScript 中JSON 字符串和对象之间的转换。
JSON.stringify() 方法(对象转换为 JSON 字符串) 用于将 JavaScript 对象转换为 JSON 字符串。 它接受一个 JavaScript 对象作为参数,并返回对应的 JSON 字符串表示。例如: const obj { name: John, age: 25 }; const jsonStr…...