回溯热门问题

关卡名

回溯热门问题

我会了✔️

内容

1.组合总和问题

✔️

2.分割回文串问题

✔️

3.子集问题

✔️

4.排列问题

✔️

5.字母全排列问题

✔️

6.单词搜索

✔️

1. 组合总和问题 

LeetCode39题目要求:给你一个无重复元素的整数数组candidates和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有不同组合 ,并以列表形式返回。你可以按任意顺序返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 数组中的元素满足1 <= candidates[i] <= 200。例子:

输入:candidates = [2,3,6,7], target = 7

输出:[[2,2,3],[7]]

解释:

2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意2可以使用多次。

7 也是一个候选, 7 = 7 ,仅有这两种组合。

如果不考虑重复,本题与LeetCode113题就是一个题,如果可以重复,那是否会无限制取下去呢?也不会,因为题目给了说明,每个元素最小为1,因此最多也就target个1。
我们画图看该怎么做,对于序列{2,3,6,7},target=7。很显然我们可以先选择一个2,然后剩下的target就是7-2=5。再选一个2,剩余5-2=3。之后再选一个2,剩余3-2=1。已经小于2了,我们不能继续向下了,要返回一下。看看有没有3。OK,序列中有3,那么就得到了第一个结果{2,2,3}。
之后我们继续回退到只选了一个2的时候,这时候不能再取2了,而是从{3,6,7}中选择,如下图所示,没有符合要求的!
依次类推,后面尝试从3、6和7开始选择。
所以我们最终得到的结果就是{2,2,3}和{2,5}。为了方便,我们可以先对元素做个排序,然后将上面的过程画成这个一个树形图:

这个图横向是针对每个元素的暴力枚举,纵向是递归,也是一个纵横问题,实现代码也不复杂:

class CombinationSum {List<List<Integer>> res = new ArrayList<>(); //记录答案List<Integer> path = new ArrayList<>();  //记录当前正在访问的路径public List<List<Integer>> combinationSum(int[] candidates, int target) {dfs(candidates,0, target);return res;}public void dfs(int[] c, int u, int target) {if(target < 0){return ;} if(target == 0){res.add(new ArrayList(path));return ;}for(int i = u; i < c.length; i++){if( c[i] <= target)  {path.add(c[i]);//当前层将target减掉了一部分,也就是子结构只要找是否有满足(target -  c[i])就可以了dfs(c,i,target -  c[i]); // 因为可以重复使用,所以还是ipath.remove(path.size()-1); //回溯}}}
}

2.分割回文串 

分割问题也是回溯要解决的典型问题之一,常见的题目有分割回文串、分割IP地址,以及分割字符串等。
LeetCode131 分割回文串,给你一个字符串s,请你将s分割成一些子串,使每个子串都是回文串 ,返回s所有可能的分割方案。
回文串是正着读和反着读都一样的字符串。

示例1:

输入:s = "aab"

输出:[["a","a","b"],["aa","b"]]

字符串如何判断回文本身就是一个道算法题,本题在其之上还要再解决一个问题:如何切割?如果暴力切割,是非常困难的,如果从回溯的角度来思考就清晰很多:

 我们说回溯本身仍然会进行枚举,这里的也一样。切割线(就是图中的红线)切割到字符串的结尾位置,说明找到了一个切割方法。这里就是先试一试,第一次切'a',第二次切'aa',第三次切'aab'。这对应的就是回溯里的for循环,也就是横向方面。
我们还说回溯仍然会进行递归,这里也是一样的,第一次切了'a',剩下的就是'ab'。递归就是再将其再切一个回文下来,也就是第二个'a',剩下的'b'再交给递归进一步切割。这就是纵向方面要干的事情,其他以此类推。
至于回溯操作与前面是一样的道理,不再赘述。通过代码就可以发现,切割问题的回溯搜索的过程和组合问题的回溯搜索的过程是差不多的。

class Partition {List<List<String>> lists = new ArrayList<>();Deque<String> deque = new LinkedList<>();public List<List<String>> partition(String s) {backTracking(s,0);return lists;}private void backTracking(String s, int startIndex) {//如果起始位置大于s的大小,说明找到了一组分割方案if(startIndex>=s.length){lists.add(new ArrayList(deque));return;}for(int i=startIndex;i<s.length;i++){//如果是回文子串,则记录if(isPalindrome(s,startIndex,i)){String str = s.substring(startIndex, i + 1);deque.addLast(str);} else {continue;}//起始位置后移,保证不重复backTracking(s,i+1);deque.removeLast();}}//判断是否是回文串private boolean isPalindrome(String s, int startIndex, int end) {for(int i=startIndex,j=end;i<j;i++,j--){if(s.charAt(i)!=s.charAt(j)){return false;}}return true;}
}

3 子集问题

子集问题也是回溯的经典使用场景。回溯可以画成一种树状结构,子集、组合、分割问题都可以抽象为一棵树,但是子集问题与其他的类型有个明显的区别,组合问题一般找到满足要求的结果即可,而集合则要找出所有的情况。
LeetCode78,给你一个整数数组nums,数组中的元素互不相同。返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。你可以按任意顺序返回解集。

示例1:

输入:nums = [1,2,3]

输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

看上面的例子nums = [1,2,3],将子集抽象为树型结构如下:

 从图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合。
这里什么时候要停下来呢?其实可以不加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了。
而且求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树。这样实现起来也比较容易。

class Subsets {// 存放符合条件结果的集合List<List<Integer>> result = new ArrayList<>();// 用来存放符合条件结果LinkedList<Integer> path = new LinkedList<>();public List<List<Integer>> subsets(int[] nums) {//空集合也是一个子集if(nums.length==0){result.add(new ArrayList<>());return result;}subsetsHelper(nums,0);return result;}private void subsetsHelper(int[] nums, int startIndex){//「遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合」。result.add(new ArrayList<>(path));if(startIndex>=nums.length){return;}for(int i=startIndex;i<nums.length;i++){path.add(nums[i]);subsetsHelper(nums,i+1);path.removeLast();}}
}

上面代码里定义了全局变量result和path,这样整体比较简洁。这里可以转换成方法参数的形式,只是看起来比较复杂一些。

4 排列问题

LeetCode46.给定一个没有重复数字的序列,返回其所有可能的全排列。例如:

输入: [1,2,3]

输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

排列问题是典型的小学生都会,但是难道众人的问题。这个问题与前面组合等问题的一个区别是使用过的后面还要再用,例如1,在开始使用了,但是到了2 和3的时候仍然要再使用一次。这本质上是因为 [1,2] 和 [2,1] 从集合的角度看是一个,但从排列的角度看是两个。
元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次,所以就不能使用startIndex了,为此可以使用一个used数组来标记已经选择的元素,完整过程如图所示:

 

这里的终止条件怎么判断呢?从上图可以看出叶子节点就是结果。那什么时候是到达叶子节点呢?
当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。

class Permute {List<List<Integer>> result = new ArrayList<>();LinkedList<Integer> path = new LinkedList<>();boolean[] used;public List<List<Integer>> permute(int[] nums) {if(nums.length==0){return result;}used = new boolean(nums.length);permuteHelper(nums);return result;}private void permuteHelper(int[] nums){if(path.size()==nums.length){result.add(new ArrayList<>(path));return;}for (int i = 0; i < nums.length; i++){if (used[i]){continue;}used[i] = true;path.add(nums[i]);permuteHelper(nums);path.removeLast();used[i] = false;}}
}

5 字母大小写全排列 

LeetCode784. 字母大小写全排列:给定一个字符串 s ,通过将字符串 s 中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。以任意顺序返回输出。

示例1:输入:s = "a1b2"

输出:["a1b2", "a1B2", "A1b2", "A1B2"]

如果本题去掉数字,只告诉你两个字母ab,让你每个字母变化大小写,那就是ab、Ab、aB、AB四种情况,题目比2.6电话号码问题还简单,这里的数字就是干扰项,我们需要做的是过滤掉数字, 只处理字母。另外还要添加个大小写转换的问题。如下图所示:

class LetterCasePermutation {public List<String> letterCasePermutation(String s) {List<String> ans = new ArrayList<String>();dfs(s.toCharArray(), 0, ans);return ans;}public void dfs(char[] arr, int pos, List<String> res) {while (pos < arr.length && Character.isDigit(arr[pos])) {pos++;}if (pos == arr.length) {res.add(new String(arr));return;}arr[pos] ^= 32;dfs(arr, pos + 1, res);arr[pos] ^= 32;dfs(arr, pos + 1, res);}
}

6 单词搜索 

LeetCode79.给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例1:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"

输出:true

思路:从上到下,左到右遍历网格,每个坐标递归调用dfs方法,方法里的i,j表示网格坐标,k表示word的第k个字符。
回溯最重要的是两个工作,一个是确定递归方法的终止条件,一个是确定递推公式,这两个是相辅相成的。
递归停止的场景我们来明确一下:
如果要返回 false ,则满足下面任何一种情况就可以,

  1. 行或列索引越界
  2. 当前矩阵元素与目标字符不同
  3. 当前矩阵元素已访问过。

如果要返回true,则应该k = len(word) - 1 ,即字符串 word 已全部匹配。
递推公式分析:

  1. 标记当前矩阵元素: 将 board[i][j] 修改为 空字符 '' ,代表此元素已访问过,防止之后搜索时重复访问。
  2. 搜索下一单元格: 朝当前元素的 上、下、左、右 四个方向开启下层递归,使用或连接 (代表只需找到一条可行路径就直接返回,不再做后续 DFS ),并记录结果至 res 。
  3. 还原当前矩阵元素: 将 board[i][j] 元素还原至初始值,即 word[k] 。

返回值: 返回布尔量 res ,代表是否搜索到目标字符串。
这样,看似高大上,实际并不复杂的实现就出来了:

class Exist {public boolean exist(char[][] board, String word) {char[] words = word.toCharArray();for(int i = 0; i < board.length; i++) {for(int j = 0; j < board[0].length; j++) {if (dfs(board, words, i, j, 0)) return true;}}return false;}boolean dfs(char[][] board, char[] word, int i, int j, int k) {if (i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;if (k == word.length - 1) return true;board[i][j] = '\0';boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);board[i][j] = word[k];return res;}
}

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

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

相关文章

leetcode做题笔记2132. 用邮票贴满网格图

给你一个 m x n 的二进制矩阵 grid &#xff0c;每个格子要么为 0 &#xff08;空&#xff09;要么为 1 &#xff08;被占据&#xff09;。 给你邮票的尺寸为 stampHeight x stampWidth 。我们想将邮票贴进二进制矩阵中&#xff0c;且满足以下 限制 和 要求 &#xff1a; 覆盖…

最快的排序算法TimSort还能更快吗

关于TimSort排序算法&#xff0c;请看这篇&#xff1a;另一位博主的博客 本文主要讨论让TimSort更快的方法。 已经产生了许多run&#xff0c;它们的长度是&#xff1a;4 6 2 5 7 用类似于霍夫曼编码的方法&#xff0c;找出最小的两项&#xff0c;相加。这里是4 2&#xff0c…

MATLAB 最小二乘直线拟合方法二 (36)

MATLAB 最小二乘直线拟合方法二 (36) 一、算法介绍二、算法实现1.代码2.结果一、算法介绍 这里介绍另一种拟合直线点云的方法,更为简单方便,结果与前者一致,主要内容直接复制代码使用即可,原理简单看代码即可,下面是具体的实现和拟合结果展示 二、算法实现 1.代码 代…

对Spring源码的学习:基于XML文件配置的开发流程

目录 BeanFactory开发流程 ApplicationContext BeanFactory与ApplicationContext对比 基于XML方式的Bean的配置 自动装配 BeanFactory开发流程 这里的第三方指的是Spring提供的BeanFactory&#xff0c;Spring启动时会初始化BeanFactory&#xff0c;然后读取配置清单&#…

2021实战面试

1、Rem , em , px , % , vw 之间的区别 PX: px像素&#xff08;Pixel&#xff09;。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。 em: 1,子元素字体大小的em是相对于父元素字体大小 2,元素的width/height/padding/margin用em的话是相对于该元素的font-size rem:1rem是…

智能优化算法应用:基于象群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于象群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于象群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.象群算法4.实验参数设定5.算法结果6.参考文献7.MA…

仿windows12网盘,私有云盘部署教程,支持多种网盘

仿windows12网盘,私有云盘部署教程&#xff0c;支持多种网盘 资源宝分享&#xff1a;www.httple.net 视频教程&#xff1a;https://www.bilibili.com/video/BV1m64y1G7Bq/ 宝塔部署方式&#xff1a; 1.验证是否安装jdk,没有安装请看安装教程 推荐安装jdk8&#xff08;注意您…

SAP ABAP 面试题交流

1.列举AT事件并说明其作用&#xff0c;AT事件中的工作区有何不同&#xff1f; AT FIRST 循环loop中执行第一条数据 AT LAST 循环loop中执行最后一条数据 AT NEW 循环loop中指定字段&#xff08;包含指定字段&#xff09;记录与上一条记录不一致数据执行 AT END OF 循环loo…

Visual Studio调试技巧合集

Visual Studio调试技巧合集 1 如何同一个项目运行不同main文件&#xff1f; 1 如何同一个项目运行不同main文件&#xff1f; &#xff08;1&#xff09;移动鼠标到需要关掉调试的文件&#xff0c;点击右键属性–常规–从生成中排除–是–确定&#xff0c;即显示“-”号排除&am…

Python自动化测试系列[v1.0.0][多种数据驱动实现附源码]

前情提要 请确保已经熟练掌握元素定位的常用方法及基本支持&#xff0c;请参考Python自动化测试系列[v1.0.0][元素定位] 数据驱动测试是自动化测试中一种重要的设计模式&#xff0c;这种设计模式可以将测试数据和测试代码分开&#xff0c;实现数据与代码解耦&#xff0c;与此同…

变量与方法面试题

char 型变量中能不能存储一个中文汉字&#xff0c;为什么&#xff1f; char 类型可以存储一个中文汉字&#xff0c;因为 Java 中使用的编码是 Unicode&#xff08;不选择任何特定的编码&#xff0c;直接使用字符在字符集中的编号&#xff0c;这是统一的唯一方法&#xff09;&a…

Python FuckIt模块:代码的“不死鸟”

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在编程世界中&#xff0c;每个开发者都曾遇到过代码中的错误&#xff0c;有时这些错误可能让人崩溃。但是&#xff0c;有一天&#xff0c;听说了一个叫做"FuckIt"的模块&#xff0c;它声称可以帮助摆脱…

【PIE-Engine 数据资源】全球海面温度产品

文章目录 一、 简介二、描述三、波段四、示例代码参考资料 一、 简介 数据名称全球海面温度产品时间范围2002年- 2018年空间范围全球数据来源毛克彪教授团队代码片段var images pie.lmageCollection(“CAAS/SSTG”) 二、描述 全球海面温度产品是 2002-2019 年的全球海面温度…

ES中根据主键_id查询记录

一、需求 es中_type&#xff1a;_doc&#xff0c;想要根据主键_id查询记录 二、实现 复合查询中使用语句查询http://192.168.1.1/_doc/1

Mybatis的foreach标签的使用以及参数的含义

Mybatis的foreach标签的使用以及参数的含义 语法格式&#xff1a; 属性说明&#xff1a; collection属性的注意点&#xff1a;

node js 递归生成vue文件目录

目录 什么是 fs 文件系统模块 fs.existsSync方法 方法说明&#xff1a; 语法&#xff1a; 向指定的文件中写入内容 writeFile fs.writeFile() 的语法格式 fs.writeFile() 的示例代码 判断文件是否写入成功 fs.mkdir 创建目录 目录已存在&#xff0c;重复创建 创建的目…

Python:Anaconda

简介 Anaconda是一个流行的Python发行版&#xff0c;专为科学计算和数据分析而设计。它包含了Python语言、Jupyter Notebook以及用于科学计算的众多包&#xff0c;如NumPy、Pandas、Matplotlib等。 Anaconda的特点是开箱即用&#xff0c;用户无需单独安装这些包&#xff0c;极…

Leetcode—459.重复的子字符串【简单】

2023每日刷题&#xff08;五十九&#xff09; Leetcode—459.重复的子字符串 算法思想 巧解的算法思想 实现代码 从第一个位置开始到s.size()之前&#xff0c;看s字符串是否是ss的子串 class Solution { public:bool repeatedSubstringPattern(string s) {return (s s).fin…

2023全球开发者生态调研:84%的开发者表示他们在工作中正积极使用生成式AI工具

今年JetBrains首次在一年一度的开发者生态调研中&#xff0c;增加了人工智能方向的问题。在全球26348名开发者参与的调研中&#xff0c;总体对人工智能的发展持乐观态度。特别是生成式AI在软件开发和编程环节中的应用&#xff0c;84%的开发者表示他们在工作中正在积极使用生成式…

占位图片(Placeholder Image)

一、引言 在网页设计和开发中&#xff0c;占位图片&#xff08;Placeholder Image&#xff09;是一种常见的技术手段&#xff0c;用于在用户上传图片之前或者图片加载失败时&#xff0c;展示一个临时替代的图片&#xff0c;以提高用户体验。本文将详细介绍占位图片的实现原理和…