算法练习第21天|216.组合总和|||、17.电话号码的字母组合

216.组合总和 III

216. 组合总和 III - 力扣(LeetCode)icon-default.png?t=N7T8https://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个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。

思路分析:

这道题是在77.组合的基础上做了改进,相关回溯解法可以参考上一篇博文:

算法练习第20天|回溯算法 77.组合问题 257. 二叉树的所有路径-CSDN博客

和77组合题目一样,本题抽象成二叉树的逻辑如下(图片来自卡哥的代码随想录): 

  使用回溯的解法,我们依然要使用path和result两个vector,一个用于记录遍历路径,另一个用于记录满足条件的结果:

vector<vector<int>> result;  //满足条件的结果
vector<int> 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加以记录;然后就可以从startIndex+1的位置进行递归了,递归之后,该记录的结果会进行记录。然后要进行抽象成的二叉树遍历过程回溯过程,即当前节点退出sum和path,具体代码如下:

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(i);}

完整代码:

class Solution {
public:vector<vector<int>> result;  //满足条件的结果vector<int> 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; i<=9; i++){//处理节点sum += i;path.push_back(i);//递归backTracking(targetSum, k, sum, i+1);  //注意将i+1调整为startIndex//回溯sum -= i;path.pop_back(i);}}vector<vector<int>> combinationSum3(int k, int n) {backTracking(n,k,0,1);return result;}
};

进一步剪枝处理优化代码:

剪枝其实就是在二叉树遍历逻辑的基础上,去点一些明显没有必要的遍历路径,如下图所示:

当遍历元素的和大于题目要求的n时,其实已经没有意义了。 

那么剪枝的地方可以放在递归函数开始的地方,剪枝代码如下:

if (sum > targetSum) { // 剪枝操作return;
}

 除此之外,遍历过程也可以再剪枝:

接下来看一下优化过程如下:

  1. 已经选择的元素个数:path.size();

  2. 所需需要的元素个数为: k - path.size();

  3. 列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size())

  4. 在集合n中至多要从该起始位置 : i <= n - (k - path.size()) + 1,开始遍历,往后找还能找到k个数。

代码如下:

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;}
};

17.电话号码的字母组合

17. 电话号码的字母组合 - 力扣(LeetCode)icon-default.png?t=N7T8https://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"", //1"abc", //2"def", //3"ghi", //4"jkl", //5"mno", //6"pqrs", //7"tuv", //8"wxyz", //9};

回溯法来解决n个for循环的问题

例如:输入:"23",抽象为树形结构,如图所示:

17. 电话号码的字母组合

 这就和之前的77组合以及216组合III有了相似之处。然后只用回溯三部曲。

回溯三部曲:

  • 确定回溯函数参数

首先需要一个字符串s来收集叶子节点的结果,然后用一个字符串数组result保存起来,这两个变量我依然定义为全局。

再来看参数,参数指定是有题目中给的string digits,然后还要有一个参数就是int型的index。这个index用来表示遍历到digits中的第几个数字了。与之前的startIndex不一样。

    vector<string> 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中。添加完一个字母(对应模板中的处理节点),则 该去执行递归,index+1,然后回溯:

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();                       // 回溯
}

整体代码如下: 

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 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, index+1);  //找下一个数字对应的字母path.pop_back();  //回溯}}vector<string> letterCombinations(string digits) {if(digits.empty())return result;backTracking(digits, 0);return result;}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/837476.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

北亚MF2200手机取证平台介绍

一、产品介绍。 北亚MF2200手机取证平台是由北亚企安科技&#xff08;北京&#xff09;有限公司&#xff08;Frombyte&#xff09;自主研发的一款针对智能手机&#xff08;iPhone、Android&#xff09;及 iPad 取证分析的法证平台。本平台采集速度快&#xff0c;可通过自动提取…

【VUE】VUE3绘制箭头组件

效果预览&#xff1a; 长、宽、粗细等等根据情况合理调整即可。 组件&#xff1a; <template><div class"line" :style"props.arrowsColor"></div> </template><script setup> import { defineProps, ref, onMounted } fr…

为什么使用AI 在游戏中不犯法

使用AI在游戏中本身并不违法&#xff0c;甚至在很多情况下&#xff0c;游戏公司自己也会在游戏中集成AI来提高游戏体验&#xff0c;例如通过AI驱动的非玩家角色&#xff08;NPC&#xff09;来增加游戏的互动性和挑战性。然而&#xff0c;使用AI是否违法取决于AI的使用方式和目的…

36. 有效的数独 - 力扣(LeetCode)

基础知识要求&#xff1a; Java&#xff1a;方法、for循环、if判断、数组 Python&#xff1a; 方法、for循环、if判断、列表、集合 题目&#xff1a; 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一…

word2019 64位 NoteExpress突然无法使用解决方法

之前用的好好的&#xff0c;去除格式化运行过一次。 打开别的文档&#xff0c;突然发现红框中的图标全变灰了 根据教程添加 加载项&#xff0c;然后word以管理员身份重启&#xff0c;NE也以管理员身份运行&#xff0c;又可以了 然后突然又不行了&#xff0c;重启电脑后NE变成…

Android Studio开发之路(十二)image、byte[]、mat、Bitmap几种格式互转合集

一、知识点 Camerax中的 imageCapture用例默认的image格式是JPEG, 而ImageAnalysis用例默认的image格式是YUV_420_888. 二、ImageAnalysis用例中ImageProxy转mat YUV转Mat 三、imageCapture中image专byte[] 如下边代码&#xff0c; //拍照&#xff0c;保存到内存 private…

普中STM32F103ZET6开发板让DS0和DS1两个LED同时亮

欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目录 一.前言 二.代码 三.运行效果 一.前言 在这套stm32教程中,只教学了如何亮DS0,而没有教学如何亮DS1。 二.代码 main.c #include "stm32f10x.h"void Syst

jQuery的选择器与自带函数详解

在前端开发中&#xff0c;jQuery是一个广泛使用的JavaScript库&#xff0c;它极大地简化了HTML文档遍历、事件处理、动画以及AJAX交互等操作。本文将通过一个示例页面&#xff0c;详细介绍jQuery的选择器和一些常用的自带函数。 示例代码优化 首先&#xff0c;我们来优化和完…

flowable多对并发网关跳转的分析

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; h…

webpack监听文件改变实时编译示例:热更新

watchpack是webpack内部使用的模块&#xff0c;用于监听文件系统。当使用webpack-dev-server或webpack的–watch选项时&#xff0c;webpack会利用watchpack监听文件系统的变化 webpack-dev-server可以用来实现热更新 npm i -D webpack-dev-serverpackage.json"scripts&quo…

SpringBoot整合JavaMail邮件

JavaMail邮件发送 文章目录 JavaMail邮件发送1.方式一&#xff1a;SpringBoot整合JavaMailSender2.方式二&#xff1a;java直接发送 1.方式一&#xff1a;SpringBoot整合JavaMailSender 1.yml配置提取 application: mail:smtp:#服务器主机名host: smtp.qq.com#服务器端口 por…

spring boot3多模块项目工程搭建-下(团队开发模板)

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 目录 写在前面 上文衔接 Common模块 DAO模块 Service模块 Web模块 API模块 写在最后 写在前面 本文介绍了springboot开发后端服务&#xff0c;多模块项目工程搭建&#xff0c;各模块的…

ZL-016D多通道小鼠主动跑轮系统主要研究动物生活节律

简单介绍&#xff1a; 多通道小鼠主动跑轮系统是由动物本身自发运动来推动跑轮转动。在这种构型中&#xff0c;笼内动物长期活动的信息&#xff0c;如跑轮转动方向、转数、累计总行程等&#xff0c;能够使用编码器进行长度计记录。此装置由转轮组件、笼体、以及转动方向速度传…

勒索软件漏洞?在不支付赎金的情况下解密文件

概述 在上一篇文章中&#xff0c;笔者对BianLian勒索软件进行了研究剖析&#xff0c;并且尝试模拟构建了一款针对BianLian勒索软件的解密工具&#xff0c;研究分析过程中&#xff0c;笔者感觉构建勒索软件的解密工具还挺有成就感&#xff0c;因此&#xff0c;笔者准备再找一款…

uniapp外部scss文件使用scss语法不生效,

项目场景&#xff1a; 页面的样式重复我想提取出来作为公共样式 新建scss文件&#xff0c;然后引入&#xff0c;结果样式不生效 问题描述&#xff1a; uniapp官网示例引入css的方法 /* 绝对路径 */ import url(/common/uni.css); import url(/common/uni.css); /* 相对路径 …

企业OA办公系统开发笔记:2、MyBatis-Plus

文章目录 企业办公系统&#xff1a;2、MyBatis-Plus一、MyBatis-Plus1、简介2、主要特点3、依赖 二、MyBatis-Plus入门1、配置文件2、启动类3、实体类4、添加Mapper类5、测试Mapper接口6、CRUD测试6.1、insert添加6.1.1、示例6.1.2、主键策略 6.2、更新6.3、删除6.3.1、根据id删…

第十一届蓝桥杯大赛软件类决赛 Java A 组

文章目录 发现宝藏【考生须知】试题 A: 合数个数试题 B : 含 2 天数试题 C: 本质上升序列试题 D: 迨尺天涯试题 E: 玩具蛇试题 F: 游园安排试题 G: 画廊试题 H: 奇偶覆盖试题 I: 补给试题 J: 蓝跳跳 发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&…

【Python】Python中的除法运算

在 Python 中&#xff0c;除法运算可以通过几种不同的运算符来执行&#xff0c;主要包括普通除法 (/) 和整除 (//)&#xff0c;还有取余运算 (%)&#xff0c;这些运算符有各自的特定用途和行为。 1. 普通除法 (/) 普通除法运算符 / 用于执行标准的除法运算&#xff0c;结果总…

【Linux】使用Valgrind定位内存增长问题

文章目录 1 内存问题2 Valgrind2.1 Valgrind介绍2.2 Valgrind中的Memcheck2.3 Valgrind中的Massif 3 总结 1 内存问题 内存问题是一类比较难以定位的问题&#xff0c;通常有两类场景&#xff1a; 程序在低负载情况下的内存使用量是否正常&#xff0c;低负载情况下不应该太高程…

Python基础语法【1】

做个简单的总结 1.输出 直接通过print函数进行输出 print("Hello") 2.变量 2.1命名规则 变量名只能包含字母、数字和下划线。变量名能以字母或下划线打头&#xff0c;但不能以数字打头。例如&#xff0c;可将变量命名为message_1&#xff0c;但不能将其命名为1…