当前位置: 首页 > ds >正文

回溯实战篇2

文章目录

  • 前言
  • 分割
    • 分割回文串
    • 复原IP地址
  • 子集
    • 子集
    • 子集II

前言

今天继续带大家进行回溯的实战篇2,去学习如何用回溯的方法去解决分割和子集的问题,最重要的就是学会回溯三部曲的构建,一文带大家弄懂。本文用于记录自己的学习过程,同时向大家进行分享相关的内容。本文内容参考于代码随想录同时包含了自己的许多学习思考过程,如果有错误的地方欢迎批评指正!

image-20250509235248135

分割

分割回文串

131. 分割回文串 - 力扣(LeetCode)

image-20250509235347673

相关技巧:其实这种分割的问题和组合问题是类似的,我们来看如果是组合问题:那是不是就是选取一个a之后,在bcdef中再去选取第二个,然后选取b之后在cdef中再选取第三个。切割问题问题也是同样的道理:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段。我们再来看这道分割的题目,其实就是两个步骤,我们先把分割的结果找出来,然后再判断其每段分割是不是回文串。这里有个小技巧,就是我们可以再进入回溯前进行判断分割的该段是不是回文,是回文那就进入回溯去继续分割,不是回文就返回。

image-20250510084430773

我们来通过一个成功的结果来思考我们该如何去通过回溯方法来写代码。首先切割的范围是[a,a,b],切割出第一个a之后,我们判断[a]是不是回文串,一个字母当然是了,所以继续切割a,就得到了[a],[a],第二个a同样的也是回文串,继续切割,得到[b],同样的也是回文串,这时候切割到最后了,都是回文串那么就保存路径结果退出单层回溯。然后这时候就回溯到了[a]和[a,b]的时候,这时候我们再[a,b]后进行切割,判断[a,b]不属于回文串,直接退出单层回溯。所以这样再来看我们的逻辑就跟清晰了,回溯三部曲就能够很好的写出来了。

  • 确定回溯的参数和返回值:字符串s,我们用来记录切割到哪里的startindex参数,路径参数path,用来保存路径进行回溯操作的,最后就是保存结果的result参数了。返回值不需要了,有result来保存结果了。
  • 确定回溯的终止条件:当Startindex的大小等于字符串的长度的时候,说明切割到最后了,直接加入结果退出回溯。
  • 确定单层回溯的逻辑:首先判断是否切割到最后,没有就继续当前的回溯过程,从当前的位置开始往后切割,切割完之后,判读当前的子串是否是回文串,回文串就进入回溯继续切割,不是回文串就往后再切割直到是回文串,或者切割到最后为止。
class Solution:def partition(self, s: str) -> List[List[str]]:'''递归用于纵向遍历for循环用于横向遍历当切割线迭代至字符串末尾,说明找到一种方法类似组合问题,为了不重复切割同一位置,需要start_index来做标记下一轮递归的起始位置(切割线)'''result = []self.backtracking(s, 0, [], result)return resultdef backtracking(self, s, start_index, path, result ):# Base Caseif start_index == len(s):result.append(path[:])return# 单层递归逻辑for i in range(start_index, len(s)):# 此次比其他组合题目多了一步判断:# 判断被截取的这一段子串([start_index, i])是否为回文串if self.is_palindrome(s, start_index, i):path.append(s[start_index:i+1])self.backtracking(s, i+1, path, result)   # 递归纵向遍历:从下一处进行切割,判断其余是否仍为回文串path.pop()             # 回溯def is_palindrome(self, s: str, start: int, end: int) -> bool:i: int = start        j: int = endwhile i < j:if s[i] != s[j]:return Falsei += 1j -= 1return True

复原IP地址

93. 复原 IP 地址 - 力扣(LeetCode)

image-20250509235502535

相关技巧:这道题看着好像比较复杂,但是其逻辑跟分割回文串是一样的道理了,我们分割回文串的时候,判断分割的串是不是回文串,那么这里呢,我们分割完之后,就是判断当前的分割区间是否合法。只不过把回文串的判断换成了是否合法的判断了。那么不合法的情况有哪些呢?就是**0开头的数字不合法、遇到非数字字符不合法、如果大于255了不合法。**并且其终止条件也不像分割回文串分割到最后了,而是分割了三次就是结束了。

image-20250510090158161

其实整个过程跟分割回文串差不多,相信大家看图就能够很好的理解了。

  • 确定回溯的参数和返回值:其实与分割回文串的参数差不多,字符串s,我们用来记录切割到哪里的startindex参数,路径参数current,用来保存路径进行回溯操作的,还有个pointnum用来记录我们分割了几次,最后就是保存结果的result参数了。返回值不需要了,有result来保存结果了。
  • 确定回溯的终止条件:当pointnum的大小等于3的时候,说明切割成四段了,再判断最后的部分是否合法,合法就直接加入结果退出回溯。
  • 确定单层回溯的逻辑:首先判断是否切割三次了,没有就继续当前的回溯过程,从当前的位置开始往后切割,切割完之后,判读当前的子串是否是合法,合法就进入回溯继续切割,不合法就往后再切割直到合法为止,或者切割到最后为止。
class Solution:def restoreIpAddresses(self, s: str) -> List[str]:result = []self.backtracking(s, 0, 0, "", result)return resultdef backtracking(self, s, start_index, point_num, current, result):if point_num == 3:  # 逗点数量为3时,分隔结束if self.is_valid(s, start_index, len(s) - 1):  # 判断第四段子字符串是否合法current += s[start_index:]  # 添加最后一段子字符串result.append(current)returnfor i in range(start_index, len(s)):if self.is_valid(s, start_index, i):  # 判断 [start_index, i] 这个区间的子串是否合法sub = s[start_index:i + 1]self.backtracking(s, i + 1, point_num + 1, current + sub + '.', result)else:breakdef is_valid(self, s, start, end):if start > end:return Falseif s[start] == '0' and start != end:  # 0开头的数字不合法return Falsenum = 0for i in range(start, end + 1):if not s[i].isdigit():  # 遇到非数字字符不合法return Falsenum = num * 10 + int(s[i])if num > 255:  # 如果大于255了不合法return Falsereturn True

子集

子集

78. 子集 - 力扣(LeetCode)

image-20250509235535318

相关技巧:来看子集问题,如果将组合,分割,子集这些问题都当作树结构来看,组合和分割的最终结果是不是就是找到叶子节点,但是子集问题不同,子集问题是为了找到树种所有的节点。其实也还是跟组合问题一样的,组合问题到最终的叶子节点就保存结果到结果集,而子集问题就是再每个单层回溯的时候都保留当前的结果到结果集。

image-20250510094511373

实现路径跟组合一样,唯一不同的就是需要保存每个中间结果。

  • 确定回溯的参数和返回值:我们来看回溯参数,和组合问题是一样的道理。nums肯定是需要的,然后我们还需要个srartindex用来记录当前从哪里开始,并且在每下个回溯的时候,需要在当前的基础上加1,这样我们就避免了重复的可能。还有我们需要有一个参数path来存储我们的路径,用来加入新的和弹出操作,最后当结果符合,我们肯定需要一个result来保存我们的结果。这就是我们该问题中所需要的参数了。至于返回值就不需要了,因为我们有result参数来保存我们的结果了。
  • 确定回溯的终止条件:就是当startindex大于当前的nums长度就终止,超过长度就没意义了。
  • 确定单层回溯的逻辑:首先第一步就是将当前的结果加入我们的结果集,因为我们是找所有的节点,也就是所有的结果包括中间结果,然后就是判断是否超出长度了,然后就是正常回溯的操作了(跟组合的一样)。
class Solution:def subsets(self, nums):result = []path = []self.backtracking(nums, 0, path, result)return resultdef backtracking(self, nums, startIndex, path, result):result.append(path[:])  # 收集子集,要放在终止添加的上面,否则会漏掉自己if startIndex >= len(nums):  returnfor i in range(startIndex, len(nums)):path.append(nums[i])self.backtracking(nums, i + 1, path, result)path.pop()

子集II

90. 子集 II - 力扣(LeetCode)

image-20250509235611787

相关技巧:这道题,其实就是类似于子集和组合总和II问题的结合。我们不仅需要每个中间过程的值,并且其有重复的,我们还需要去重,还记得我们之前怎么去重的吗?是的这个时候used数组又需要登场了。

image-20250510101405711

具体的实现过程就是在子集的基础上加了个去重,也就是used数组的使用,这个就不在叙述了,可以去看看回溯实战1的组合总和II讲述怎么使用的。

  • 确定回溯的参数和返回值:我们来看回溯参数,和子集问题是一样的道理,不过就是多了个used数组用来保证我们的去重。其他的就是nums肯定是需要的,然后我们还需要个srartindex用来记录当前从哪里开始,并且在每下个回溯的时候,需要在当前的基础上加1,这样我们就避免了重复的可能。还有我们需要有一个参数path来存储我们的路径,用来加入新的和弹出操作,最后当结果符合,我们肯定需要一个result来保存我们的结果。这就是我们该问题中所需要的参数了。至于返回值就不需要了,因为我们有result参数来保存我们的结果了。
  • 确定回溯的终止条件:就是当startindex大于当前的nums长度就终止,超过长度就没意义了。
  • 确定单层回溯的逻辑:首先第一步就是将当前的结果加入我们的结果集,因为我们是找所有的节点,也就是所有的结果包括中间结果,然后就是判断是否超出长度了,然后就是循环中,我们需要有个判断,使用used数组保证同个树枝中可以使用相同的,同个数层中不能使用相同的数值。
class Solution:def subsetsWithDup(self, nums):result = []path = []used = [False] * len(nums)nums.sort()  # 去重需要排序self.backtracking(nums, 0, used, path, result)return resultdef backtracking(self, nums, startIndex, used, path, result):result.append(path[:])  # 收集子集for i in range(startIndex, len(nums)):# used[i - 1] == True,说明同一树枝 nums[i - 1] 使用过# used[i - 1] == False,说明同一树层 nums[i - 1] 使用过# 而我们要对同一树层使用过的元素进行跳过if i > 0 and nums[i] == nums[i - 1] and not used[i - 1]:continuepath.append(nums[i])used[i] = Trueself.backtracking(nums, i + 1, used, path, result)used[i] = Falsepath.pop()
http://www.xdnf.cn/news/6280.html

相关文章:

  • 今日行情明日机会——20250514
  • day25-异常处理
  • [Java实战]Spring Security 添加验证码(二十三)
  • android实现USB通讯
  • 基于 Kubernetes 部署容器平台kubesphere
  • CCF第七届AIOps国际挑战赛季军分享(RAG)
  • YOLO v2:目标检测领域的全面性进化
  • 记录 QT 在liunx 下 QFileDialog 类调用问题 ()Linux下QFileDialog没反应)
  • AI日报 · 2025年5月14日|Android 生态大型更新与多端 Gemini 集成
  • UPS是什么?UPS 不间断电源有哪些适配的升压芯片?
  • zabbix7.2最新版本 nginx自定义监控(三) 设置触发器
  • MySQL之基础索引
  • postman 用法 LTS
  • 互联网大厂Java求职面试:AI内容生成平台下的高并发架构设计与性能优化
  • CycleISP: Real Image Restoration via Improved Data Synthesis通过改进数据合成实现真实图像恢复
  • Linux grep -r 查找依赖包是否存在依赖类 Class
  • 【Pycharm】pycharm修改注释文字的颜色
  • HDD 安全擦除:何时以及如何在 Windows PC 上安全擦除硬盘
  • 【SSL证书系列】客户端如何检查中间CA签名是否由根CA签发
  • 应用示例1:交通灯
  • 怎么快速换电脑浏览器的ip:方法与注意事项
  • Java零基础学习Day13——面向对象进阶
  • ClickHouse详解
  • Android学习总结之Glide自定义三级缓存(实战篇)
  • Linux相关概念和易错知识点(39)(URL、HTTP)
  • PlantSimulation 隐藏 Frame节点(Structure)的操作方法
  • 怎么实现Redis的高可用?
  • k8s 中使用 Service 访问时NetworkPolicy不生效问题排查
  • 快消零售AI转型:R²AIN SUITE如何破解效率困局
  • 电脑内存智能监控清理,优化性能的实用软件