算法练习第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变成…

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

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

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…

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;笔者准备再找一款…

企业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;通俗易懂&…

(接上一篇linux rocky 搭建DNS高阶版)实现不同网段访问解析不同的服务器并加域

上一篇链接&#xff1a;linux rocky 搭建DNS服务和禁止AD域控DNS&#xff0c;做到独立DNS并加域-CSDN博客文章浏览阅读417次&#xff0c;点赞13次&#xff0c;收藏7次。使用linux rocky 搭建DNS服务&#xff0c;用于独立AD域控DNS存在&#xff0c;并且实现加域。https://blog.c…

数字集成电路物理设计[陈春章]——知识总结与精炼01

第一章 集成电路物理设计方法 1.1 数字集成电路设计挑战 1.2 数字集成电路设计流程 前两节内容讲述的是数字集成电路发展与流程&#xff0c;知识体系比较宏观和简单&#xff0c;请读者自行了解即可。 1.3 数字集成电路设计收敛 实现设计收敛任务&#xff1a;①数据系统;②优…

Flume 的安装和使用方法(Spark-2.1.0)

一、Flume的安装 1.下载压缩包 https://www.apache.org/dyn/closer.lua/flume/1.7.0/apache-flume-1.7.0-bin.tar.gz 2.上传到linux中 3.解压安装包 cd #进入加载压缩包目录sudo tar -zxvf apache-flume-1.7.0-bin.tar.gz -C /usr/local # 将 apache-flume-1.7.0-bin.tar.g…

有哪些值得买的开放式耳机推荐?2024年开放式运动耳机选购指南

开放式耳机因其独特设计&#xff0c;能在一定程度上保护听力。相较于传统封闭式耳机&#xff0c;开放式设计允许周围环境声音自然流入耳内&#xff0c;降低了耳内共振和声压&#xff0c;减少了耳道的不适感&#xff0c;从而减轻了对听力的潜在损害。对于追求音质与听力保护并重…

国外新闻媒体推广:多元化媒体分发投放-大舍传媒

前言 &#xff1a;随着全球化的进程&#xff0c;国外新闻市场呈现出快速发展的趋势。在这个趋势下&#xff0c;国外新闻媒体推广成为了各行业企业宣传业务的重要一环。本文将重点介绍大舍传媒的多元化媒体分发投放服务&#xff0c;以及对国外新闻媒体推广的意义。 1. 多元化媒…

阿赵UE引擎C++编程学习笔记——解决中文乱码问题

大家好&#xff0c;我是阿赵。   在UE编写C的时候&#xff0c;可能有些朋友发现&#xff0c;在C里面如果打印输出或者赋值一些中文的字符串的时候&#xff0c;会出现各种的报错&#xff0c;要么乱码&#xff0c;要么直接编译不过。   这个问题&#xff0c;其实和UE本身没什…

OSEK应用模式

1 前言 应用模式&#xff08;Application modes)用于区分不同的场景&#xff0c;以便在系统运行时&#xff0c;组织各自相互独立的OS相关的资源集合&#xff0c;是一种分而治之的思想体现。不同的应用模式是互斥的&#xff0c;即系统当前必须在一种应用模式&#xff08;且只能在…

Java面试八股之反射慢在哪里

Java反射慢在哪里 动态类型检查&#xff1a; 在反射过程中&#xff0c;Java需要在运行时确定类、方法、字段等的类型信息。这与编译时已经确定类型信息的常规对象访问不同&#xff0c;反射需要额外的类型查询和验证&#xff0c;增加了性能开销。 安全检查&#xff1a; 反射…