刷题计划 day22回溯(一)【组合】【组合总和 III】【电话号码的字母组合】

⚡刷题计划day22 回溯(一)开始,此期开启回溯专题,敬请期待关注,可以点个免费的赞哦~

往期可看专栏,关注不迷路,

您的支持是我的最大动力🌹~

目录

回溯算法理论基础

回溯法解决的问题

如何理解回溯法

回溯法算法模板

题目一:77. 组合

回溯法三部曲

递归函数的返回值以及参数

回溯函数终止条件

单层逻辑

剪枝优化

题目二:216. 组合总和 III

初版

剪枝优化

优化1:

优化2:

优化后完整代码:

题目三:17. 电话号码的字母组合


参考代码随想录

回溯算法理论基础

回溯法也可以叫做回溯搜索法,它是一种搜索的方式。

回溯法的性能如何呢,这里要和大家说清楚了,虽然回溯法很难,很不好理解,但是回溯法并不是什么高效的算法

因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,如果想让回溯法高效一些,可以加一些剪枝的操作,但也改不了回溯法就是穷举的本质。

那么既然回溯法并不高效为什么还要用它呢?

因为没得选,一些问题能暴力搜出来就不错了,撑死了再剪枝一下,还没有更高效的解法。

此时大家应该好奇了,都什么问题,这么牛逼,只能暴力搜索。

回溯法解决的问题

回溯法,一般可以解决如下几种问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合

  • 切割问题:一个字符串按一定规则有几种切割方式

  • 子集问题:一个N个数的集合里有多少符合条件的子集

  • 排列问题:N个数按一定规则全排列,有几种排列方式

  • 棋盘问题:N皇后,解数独等等

相信大家看着这些之后会发现,每个问题,都不简单!

另外,会有一些同学可能分不清什么是组合,什么是排列?

组合是不强调元素顺序的,排列是强调元素顺序

例如:{1, 2} 和 {2, 1} 在组合上,就是一个集合,因为不强调顺序,而要是排列的话,{1, 2} 和 {2, 1} 就是两个集合了。

记住组合无序,排列有序,就可以了。

如何理解回溯法

回溯法解决的问题都可以抽象为树形结构,是的,我指的是所有回溯法的问题都可以抽象为树形结构!

因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度

递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。

这块可能初学者还不太理解,后面的回溯算法解决的所有题目中,我都会强调这一点并画图举相应的例子,现在有一个印象就行。

回溯法算法模板

void backtracking(参数) {if (终止条件) {存放结果;return;}
​for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯,撤销处理结果}
}

具体可结合例题进行理解

题目一:77. 组合

  1. 组合

(https://leetcode.cn/problems/combinations/description/)

本题是回溯法的经典题目。

直接的解法当然是使用for循环,例如示例中k为2,很容易想到 用两个for循环,这样就可以输出 和示例中一样的结果。

但如果n为100,k为50呢,那就50层for循环,是不是开始窒息

此时就会发现虽然想暴力搜索,但是用for循环嵌套连暴力都写不出来!

咋整?

回溯搜索法来了,虽然回溯法也是暴力,但至少能写出来,不像for循环嵌套k层让人绝望。

那么回溯法怎么暴力搜呢?

上面我们说了要解决 n为100,k为50的情况,暴力写法需要嵌套50层for循环,那么回溯法就用递归来解决嵌套层数的问题

递归来做层叠嵌套(可以理解是开k层for循环),每一次的递归中嵌套一个for循环,那么递归就可以用于解决多层嵌套循环的问题了

如果脑洞模拟回溯搜索的过程,绝对可以让人窒息,所以需要抽象图形结构来进一步理解。

那么我把组合问题抽象为如下树形结构:

图中可以发现n相当于树的宽度,k相当于树的深度

那么如何在这个树上遍历,然后收集到我们要的结果集呢?

图中每次搜索到了叶子节点,我们就找到了一个结果

相当于只需要把达到叶子节点的结果收集起来,就可以求得 n个数中k个数的组合集合。

回溯法三部曲

递归函数的返回值以及参数

首先我们需要定义两个全局变量,一个记录当前符合条件的路径,一个记录符合条件路径的集合。

LinkedList<Integer> path = new LinkedList<>();
List<List<Integer>> result = new ArrayList<>();

函数里至少还需要题目所给的两个参数,n,k。

然后还需要一个参数startIndex,这个参数用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。

为什么要有这个startIndex呢?

从下图中红线部分可以看出,在集合[1,2,3,4]取1之后,下一层递归,就要在[2,3,4]中取数了,那么下一层递归如何知道从[2,3,4]中取数呢,靠的就是startIndex。

所以需要startIndex来记录下一层递归,搜索的起始位置。

void backtracking(int n,int k,int startIndex)

回溯函数终止条件

到达叶子节点及到终点,

path这个数组的大小如果达到k,说明我们找到了一个子集大小为k的组合了,在图中path存的就是根节点到叶子节点的路径,可以再结合上图理解。

if(path.size()==k){result.add(new ArrayList<>(path));return;
}

单层逻辑

回溯法的搜索过程就是一个树型结构的遍历过程,在如下图中,可以看出for循环用来横向遍历,递归的过程是纵向遍历。

如此我们才遍历完图中的这棵树。

for循环每次从startIndex开始遍历,然后用path保存取到的节点i。

代码如下:

for(int i=startIndex;i<=n;i++){path.add(i);backtracking(n,k,i+1);path.removeLast();
}

完整代码:

class Solution {
​LinkedList<Integer> path = new LinkedList<>();List<List<Integer>> result = new ArrayList<>();public List<List<Integer>> combine(int n, int k) {backtracking(n,k,1);return result;}public void backtracking(int n,int k,int startIndex){if(path.size()==k){result.add(new ArrayList<>(path));return;}
​for(int i=startIndex;i<=n;i++){path.add(i);backtracking(n,k,i+1);path.removeLast();}}
}

可以再对比一下前面给出的模板,是不是还挺像的。

剪枝优化

回溯法虽然是暴力搜索,但也有时候可以有点剪枝优化一下的。

我们遍历时候的范围i<=n是可以剪枝优化的,怎么优化呢?

举个例子,比如n=4,k=4,组合就只有一个[1,2,3,4],这时候再遍历,元素数量也没有四个,再循环就没有意义,如图理解:

所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置

如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了

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

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

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

  3. 在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历

为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。

举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。

从2开始搜索都是合理的,可以是组合[2, 3, 4]。

这里大家想不懂的话,建议也举一个例子,就知道是不是要+1了。

所以优化之后的for循环是:

for(int i=startIndex;i<=n-(k-path.size())+1;i++)

优化后整体代码如下:

class Solution {
​LinkedList<Integer> path = new LinkedList<>();List<List<Integer>> result = new ArrayList<>();public List<List<Integer>> combine(int n, int k) {backtracking(n,k,1);return result;}public void backtracking(int n,int k,int startIndex){if(path.size()==k){result.add(new ArrayList<>(path));return;}
​for(int i=startIndex;i<=n-(k-path.size())+1;i++){path.add(i);//处理节点backtracking(n,k,i+1);path.removeLast();//回溯,撤销处理的节点}}
}

题目二:216. 组合总和 III

[216. 组合总和 III

(https://leetcode.cn/problems/combination-sum-iii/description/)

此题与上一题思路很像,多了一个限制,本题是要找到和为n的k个数的组合,而整个集合已经是固定的了[1,...,9]。

本题k相当于树的深度,9(因为整个集合就是9个数)就是树的宽度。

直接看AC代码即可:

初版

class Solution {LinkedList<Integer> path = new LinkedList<>();List<List<Integer>> result = new ArrayList<>();int sum =0;public List<List<Integer>> combinationSum3(int k, int n) {backtracking(k,n,1);return result;}public void backtracking(int k,int n,int startIndex){if(path.size()==k){if(sum==n){result.add(new ArrayList<>(path));}return;}
​for(int i=startIndex;i<=9;i++){path.add(i);sum+=i;backtracking(k,n,i+1);path.removeLast();sum-=i;}}
}

剪枝优化

优化1:

和上题一样,for循环的范围可以剪枝,i <= 9 - (k - path.size()) + 1就可以了。

优化2:

如图可以发现,当我们sum已经大于n的时候,再往后遍历也就没意义了,直接剪掉。

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

if(sum>n){return;
}

优化后完整代码:

class Solution {LinkedList<Integer> path = new LinkedList<>();List<List<Integer>> result = new ArrayList<>();int sum =0;public List<List<Integer>> combinationSum3(int k, int n) {backtracking(k,n,1);return result;
​}public void backtracking(int k,int n,int startIndex){if(sum>n){return;}if(path.size()==k){if(sum==n){result.add(new ArrayList<>(path));}return;}for(int i=startIndex;i<=9-(k-path.size())+1;i++){path.add(i);sum+=i;backtracking(k,n,i+1);path.removeLast();sum-=i;}}
}

题目三:17. 电话号码的字母组合

  1. 电话号码的字母组合

(https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/)

从示例上来说,输入"23",最直接的想法就是两层for循环遍历了吧,正好把组合的情况都输出了。

如果输入"233"呢,那么就三层for循环,如果"2333"呢,就四层for循环.......

大家应该感觉出和第一题遇到的一样的问题,就是这for循环的层数如何写出来,此时又是回溯法登场的时候了。

首先定义一个String进行映射:

//初始对应所有的数字,为了直接对应2-9,新增了两个无效的字符串""
String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

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

详细见代码即可:

class Solution {List<String> result = new ArrayList<>();StringBuilder path = new StringBuilder();public List<String> letterCombinations(String digits) {if(digits==null || digits.length()==0) {return result;}int len = digits.length();//初始对应所有的数字,为了直接对应2-9,新增了两个无效的字符串""String[] numString = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};backTracking(digits,numString,0);return result;}public void backTracking(String digits,String[] numString,int numIndex){if(numIndex==digits.length()){result.add(path.toString());return;}String str = numString[digits.charAt(numIndex)-'0'];// 将numIndex指向的数字转为intfor(int i=0;i<str.length();i++){path.append(str.charAt(i));backTracking(digits,numString,numIndex+1);// 递归,注意numIndex+1,一下层要处理下一个数字了path.deleteCharAt(path.length()-1);}}}

点个免费的赞赞吧~

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

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

相关文章

访问限定符

文章目录 一、访问限定符 一、访问限定符 C⼀种实现封装的⽅式&#xff0c;用类将对象的属性与方法结合在⼀块&#xff0c;让对象更加完善&#xff0c;通过访问权限选择性的将其接口提供给外部的用户使用。 public修饰的成员在类外可以直接被访问&#xff1b;protected和priva…

【论文阅读】WGSR

0. 摘要 0.1. 问题提出 1.超分辨率(SR)是一个不适定逆问题&#xff0c;可行解众多。 2.超分辨率(SR)算法在可行解中寻找一个在保真度和感知质量之间取得平衡的“良好”解。 3.现有的方法重建高频细节时会产生伪影和幻觉&#xff0c;模型区分图像细节与伪影仍是难题。 0.2. …

CSP/信奥赛C++语法基础刷题训练(23):洛谷P1217:[USACO1.5] 回文质数 Prime Palindromes

CSP/信奥赛C语法基础刷题训练&#xff08;23&#xff09;&#xff1a;洛谷P1217&#xff1a;[USACO1.5] 回文质数 Prime Palindromes 题目描述 因为 151 151 151 既是一个质数又是一个回文数&#xff08;从左到右和从右到左是看一样的&#xff09;&#xff0c;所以 151 151 …

【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录

背景 Jetbrain IDE 支持生成 Test 类&#xff0c;其中选择JUnit5 和 JUnit&#xff0c;但是感觉这不是标准的单元测试&#xff0c;因为接口命名吧。 差异对比 两者生成的单测API名称同原API&#xff0c;没加test前缀的。使用差异主要表现在&#xff1a; setUp &#xff06; …

Kylin Server V10 下基于Sentinel(哨兵)实现Redis高可用集群

一、什么是哨兵模式 Redis Sentinel 是一个分布式系统,为 Redis 提供高可用性解决方案。可以在一个架构中运行多个 Sentinel 进程(progress)这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线信息,并使用投票协议(agreement protocols)来决定是否执行…

扩散模型从原理到实战 入门

diffusion-models-class-CN/unit1/README_CN.md at main darcula1993/diffusion-models-class-CN GitHub 你可以使用命令行来通过此令牌登录 (huggingface-cli login) 或者运行以下单元来登录&#xff1a; from huggingface_hub import notebook_loginnotebook_login() http…

阅读《先进引信技术的发展与展望》识别和控制部分_笔记

基本信息 题名&#xff1a;先进引信技术的发展与展望 作者&#xff1a; 张合;戴可人 发表时间&#xff1a;2023-07-20 可装定、可探测、可处理、可控制是灵巧引信设计的四项基本能力。与之对应&#xff0c;先进引信的基础研究涵盖了信息交联技术、末端探测技术、目标识别技术…

07-Making a Bar Chart with D3.js and SVG

课程链接 Curran的课程&#xff0c;通过 D3.js 的 scaleLinear, max, scaleBand, axisLeft, axisBottom&#xff0c;根据 .csv 文件生成一个横向柱状图。 【注】如果想造csv数据&#xff0c;可以使用通义千问&#xff0c;关于LinearScale与BandScale不懂的地方也可以在通义千…

Fakelocation Server服务器/专业版 ubuntu

前言:需要Ubuntu系统 Fakelocation开源文件系统需求 Ubuntu | Fakelocation | 任务一 任务一 更新Ubuntu&#xff08;安装下载不再赘述&#xff09; sudo -i # 提权 sudo apt update # 更新软件包列表 sudo apt upgrade # 升级已安装的软…

从搭建uni-app+vue3工程开始

技术栈 uni-app、vue3、typescript、vite、sass、uview-plus、pinia 一、项目搭建 1、创建以 typescript 开发的工程 npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project2、安装sass npm install -D sass// 安装sass-loader&#xff0c;注意需要版本10&#xff0c;…

SMMU软件指南操作之流(stream)安全性和流标识

安全之安全(security)博客目录导读 目录 1、流安全性 2、流标识 2.1 什么是 StreamID? 2.2 SubstreamID 的作用 1、流安全性 SMMUv3 架构在没有实现 RME 设备分配的情况下,支持两种可选的安全状态,这由 SMMU_S_IDR1.SECURE_IMPL 报告。如果实现了 RME 设备分配,则通过…

Android仿前端分页组件pagination

仿前端pagination Android仿前端分页组件pagination 最近Android原生有个需求就是做个分页组件&#xff0c;不用上拉加载&#xff0c;因为数据量太大用户喜欢前端的方式&#xff0c;UI主要是拼凑比较简单&#xff0c;主要补充了一些判断越界和数据不全的细节&#xff0c;记录方…

贴代码框架PasteForm特性介绍之query,linkquery

简介 PasteForm是贴代码推出的 “新一代CRUD” &#xff0c;基于ABPvNext&#xff0c;目的是通过对Dto的特性的标注&#xff0c;从而实现管理端的统一UI&#xff0c;借助于配套的PasteBuilder代码生成器&#xff0c;你可以快速的为自己的项目构建后台管理端&#xff01;目前管…

深入理解下oracle 11g block组成

深层次说&#xff0c;oracle数据库的最少组成单位应该是块&#xff0c;一般默认情况下&#xff0c;oracle数据库的块大小是8kb&#xff0c;其中存储着我们平常所需的数据。我们在使用过程中&#xff0c;难免会疑问道&#xff1a;“oracle数据块中到底是怎样组成的&#xff0c;平…

万有引力定律和库仑定律:自然的对称诗篇

万有引力定律和库仑定律&#xff1a;自然的对称诗篇 在宇宙深邃的知识长河中&#xff0c;万有引力定律和库仑定律恰似两颗璀璨的明珠&#xff0c;闪耀着人类智慧与自然奥秘的光辉。 十七世纪&#xff0c;牛顿在对天体运行的深邃思索中&#xff0c;拨开重重迷雾&#xff0c;发现…

win10局域网加密共享设置

1、创建共享账户 我的电脑右键选择管理 选择本地用户和组 -> 用户 双击用户 在空白区域右键,新建用户 然后创建用户 点击创建后 2、设置网络 右下角网络右键

.NET6 WebApi第1讲:VSCode开发.NET项目、区别.NET5框架【两个框架启动流程详解】

一、使用VSCode开发.NET项目 1、创建文件夹&#xff0c;使用VSCode打开 2、安装扩展工具 1>C# 2>安装NuGet包管理工具&#xff0c;外部dll包依靠它来加载 法1》&#xff1a;NuGet Gallery&#xff0c;注意要启动科学的工具 法2》NuGet Package Manager GUl&#xff0c…

准备阶段 Profiler性能分析工具的使用(一)

Unity 性能分析器 (Unity Profiler) 性能分析器记录应用程序性能的多个方面并显示相关信息。使用此信息可以做出有关应用程序中可能需要优化的事项的明智决策&#xff0c;并确认所做的优化是否产生预期结果。 默认情况下&#xff0c;性能分析器记录并保留游戏的最后 300 帧&a…

【强化学习的数学原理】第03课-贝尔曼最优公式-笔记

学习资料&#xff1a;bilibili 西湖大学赵世钰老师的【强化学习的数学原理】课程。链接&#xff1a;强化学习的数学原理 西湖大学 赵世钰 文章目录 一、例子&#xff1a;如何改进策略&#xff1f;二、最优策略和公式推导三、公式求解以及最优性四、最优策略的有趣性质五、本节课…

24小时自动监控,自动录制直播蓝光视频!支持抖音等热门直播软件

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 工具特点📒📝 使用🎈 获取方式 🎈⚓️ 相关链接 ⚓️📖 介绍 📖 对于许多直播爱好者和内容创作者而言,错过心爱的直播或难以搜集视频素材始终是一个难题。今天,给大家分享的这款工具可以轻松解决这个问题,它拥有…