代码随想录算法训练营第二十九天|39. 组合总和、40.组合总和II、131.分割回文串

39. 组合总和

文档讲解代码随想录

题目链接:. - 力扣(LeetCode)

这道题目的关键点: 

candidates :无重复元素的数组、candidates 中的数字可以无限制重复被选取

与之前做过的组合问题的区别:

组合问题是给一个集合,找出k个元素的组合,把这些组合都列出来,递归的层数(树的深度是确定的)

这里没有限制组合里面的数量,找到了和为target的就可以,通过和来限定树的深度

这道题目的树形结构:

 这道题目中说元素可以重复使用,这就告诉我们如果这次选了2,下次还可以选2,也就是剩余的集合里要包括本次选取的一个元素。如果选取了5,那么下次就从[5,3]中选取,为什么不从[2,5,3]选取,因为在选2的时候已经会从[2,5,3]中选了,会有重复的情况

回溯法

回溯函数参数返回值:

这里依然是定义两个全局变量,二维数组result存放结果集,数组path存放符合条件的结果。(这两个变量可以作为函数参数传入)

首先是题目中给出的参数,集合candidates, 和目标值target。

此外我还定义了int型的sum变量来统计单一结果path里的总和,其实这个sum也可以不用,用target做相应的减法就可以了,最后如何target==0就说明找到符合的结果了,但为了代码逻辑清晰,依然用了sum。

本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?

如果是一个集合来求组合的话,就需要startIndex,例如:77.组合 (opens new window),216.组合总和III (opens new window)。

如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:17.电话号码的字母组合(opens new window)

注意以上只是说求组合的情况,如果是排列问题,又是另一套分析的套路,后面在讲解排列的时候会重点介绍

回溯函数终止条件

终止只有两种情况,sum大于target和sum等于target,sum等于target的时候,需要收集结果

(递归的过程就是纵向遍历,在增加sum的过程)

回溯搜索的单层搜索逻辑

 单层for循环依然是从startIndex开始,搜索candidates集合。注意本题和77.组合 (opens new window)、216.组合总和III (opens new window)的一个区别是:本题元素为可重复选取的,关键点就在于递归调用函数时,传入的statindex和当前读取到的数一样

class Solution:def __init__(self):# 初始化结果列表,用于存储最终的组合结果self.result = []# 初始化路径列表,用于存储当前组合的路径self.path = []def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:# 定义回溯函数def backtracking(candidates, current_sum, start_index):# 如果当前和等于目标值,将当前路径添加到结果列表中if current_sum == target:self.result.append(copy.deepcopy(self.path))  # 深拷贝当前路径return# 如果当前和超过目标值,停止继续递归elif current_sum > target:return else:# 遍历候选数组,从start_index开始,避免重复组合for i in range(start_index, len(candidates)):# 添加当前候选数到路径中self.path.append(candidates[i])# 更新当前和current_sum += candidates[i]# 递归调用回溯函数,!!关键点:注意传入的起始索引是i,允许重复使用当前数backtracking(candidates, current_sum, i)# 回溯,移除路径中的最后一个数self.path.pop()# 恢复当前和current_sum -= candidates[i]return self.result# 调用回溯函数,初始和为0,起始索引为0return backtracking(candidates, 0, 0)

剪枝

在求和问题中,排序之后加剪枝是常见的套路!

具体的之后再看

40.组合总和II

文档讲解:代码随想录

题目链接:. - 力扣(LeetCode)

题目关键点:candidates 中包含重复的元素 、candidates 中的每个数字在每个组合中只能使用一次、解集不能包含重复的组合。

这个题目的难点:集合(数组candidates)有重复元素,但还不能有重复的组合。如果 candidates = [2,5,2,1,2]  target = 5,那么按照之前的做法就会出现重复的组合,比如取到第一个2时,后面可以再取2和1,在后面在取到第二个2时,还可以再取到2和1

去重逻辑

假设排序后的集合:[1,1,2,3]

①假设取第一个1,后面就会从[1,2,3]里面选

②假设取第二个1,后面就会从[2,3]里面选

第①次选的剩下的元素不仅包含了第②次选的剩下的所有元素,而且1也包含了,那么注定②以1为开头的元素一定和①中以1为开头的选的元素是重复的

排序就是为了让相邻的元素放在一起,那么①中取1之后,②中再以1开头的都会包含在①中了

因为元素已经排序过了,②中的1后面的元素一定在①中的1后面

所以说去重的关键在于树层去重

代码实现

回溯三部曲

  • 递归函数参数

与39.组合总和 (opens new window)套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。

这个集合去重的重任就是used来完成的。

  • 递归终止条件

与39.组合总和 (opens new window)相同,终止条件为 sum > target 和 sum == target

  • 单层搜索的逻辑

这里与39.组合总和 (opens new window)最大的不同就是要去重了。

前面我们提到:要去重的是“同一树层上的使用过”,如何判断同一树层上元素(相同的元素)是否使用过了呢。

如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]

此时for循环里就应该做continue的操作。

这块比较抽象,如图:

使用 used
class Solution:def __init__(self):self.path = []self.result = []def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:def backtracking(candidates,target,start_index,sum,used):if sum == target:self.result.append(copy.deepcopy(self.path))returnelif sum > target:returnelse:for i in range(start_index,len(candidates)):if i > 0:#可以这样写# if i > start_index:#也可以这样写if (candidates[i] == candidates [i-1] and  used[i-1] != True):#保证是树层去重continueself.path.append(candidates[i])sum += candidates[i]used[i] = Truebacktracking(candidates,target,i+1,sum,used)used[i] = Falseself.path.pop()sum -= candidates[i]return self.resultused =  [False] * len(candidates)candidates.sort()print(candidates)return backtracking(candidates,target,0,0,used)
不使用used
class Solution:def __init__(self):self.path = []self.result = []def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:def backtracking(candidates,target,start_index,sum):if sum == target:self.result.append(copy.deepcopy(self.path))returnelif sum > target:returnelse:for i in range(start_index,len(candidates)):# if i > 0:#不可以这样写,i不一定是从0开始的if i > start_index:if (candidates[i] == candidates [i-1]):continueself.path.append(candidates[i])sum += candidates[i]backtracking(candidates,target,i+1,sum)self.path.pop()sum -= candidates[i]return self.resultcandidates.sort()print(candidates)return backtracking(candidates,target,0,0)

这两种结果为什么是一样的?,后面再看,目前还没有太懂

131.分割回文串 

文档讲解:代码随想录

题目链接:. - 力扣(LeetCode)

切割问题和组合问题是差不多

例如对于字符串abcdef:

  • 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个.....。
  • 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段.....。

所以切割问题,也可以抽象为一棵树形结构,如图:

递归用来纵向遍历,for循环用来横向遍历,切割线(就是图中的红线)切割到字符串的结尾位置,说明找到了一个切割方法。

回溯三部曲

  • 递归函数参数

全局变量数组path存放切割后回文的子串,二维数组result存放结果集。 (这两个参数可以放到函数参数里)

本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的。

  • 递归函数终止条件

从树形结构的图中可以看出:切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止条件

那么在代码里什么是切割线呢?

在处理组合问题的时候,递归参数需要传入startIndex,表示下一轮递归遍历的起始位置,这个startIndex就是切割线。

  • 单层搜索的逻辑

来看看在递归循环中如何截取子串呢?

for (int i = startIndex; i < s.size(); i++)循环中,我们 定义了起始位置startIndex,那么 [startIndex, i] 就是要截取的子串。

首先判断这个子串是不是回文,如果是回文,就加入在path中,path用来记录切割过的回文子串。

from typing import List
import copyclass Solution:def __init__(self):self.path = []  # 收集单一结果的路径self.result = []  # 存储符合条件的最终结果def partition(self, s: str) -> List[List[str]]:# 定义一个递归的回溯函数def backtracking(s, start_index):# 终止条件:如果起始索引等于字符串长度,说明已经切割到最后if start_index == len(s):self.result.append(copy.deepcopy(self.path))  # 将当前路径的深拷贝加入结果中return# 单层搜索逻辑:从起始索引开始遍历字符串for i in range(start_index, len(s)):# 如果从start_index到i的子串是回文串if self.isPalindrome(s, start_index, i):# 将这个回文子串加入路径self.path.append(s[start_index:i + 1])  # 切片的末尾是i+1# 递归调用回溯函数,处理剩余的子串backtracking(s, i + 1)# 回溯,移除最后一个加入路径的子串self.path.pop()return self.result  # 返回结果return backtracking(s, 0)  # 从索引0开始调用回溯函数def isPalindrome(self, s, start_index, end_index):# 判断一个子串是否是回文串while start_index < end_index:  # 当起始索引小于结束索引时进行判断if s[start_index] != s[end_index]:  # 如果头尾字符不相等,返回Falsereturn Falsestart_index += 1  # 移动起始索引向右end_index -= 1  # 移动结束索引向左return True  # 如果所有字符都匹配,返回True

这道题目有下几个难点:

  • 切割问题可以抽象为组合问题
  • 如何模拟那些切割线
  • 切割问题中递归如何终止
  • 在递归循环中如何截取子串
  • 如何判断回文

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

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

相关文章

全面了解 LLM 微调——根据应用场景独特需求定制大型语言模型

1.概述 截至2023年&#xff0c;大型语言模型&#xff08;LLM&#xff09;的发展确实在不断进步&#xff0c;涌现出了多种新的模型&#xff0c;如ChatGLM、Alpaca、Falcon以及Llama 2&#xff0c;还有GPT-4等。这些模型在自然语言处理领域展现出了强大的潜力&#xff0c;它们能…

【全开源】国际版JAVA游戏陪玩系统源码陪练APP源码H5源码电竞系统源码支持Android+IOS+H5

国际版游戏陪玩系统&#xff1a;连接全球玩家的桥梁 在数字化时代&#xff0c;游戏已成为全球范围内跨越文化和地域的桥梁。随着游戏产业的蓬勃发展&#xff0c;玩家们对于游戏体验的需求也日益多样化。为了满足这一市场需求&#xff0c;我们隆重推出“国际版游戏陪玩系统”&a…

视频监控技术助力智慧校园建设:系统设计与实施策略

一、引言 随着信息技术的迅猛发展&#xff0c;智慧校园作为教育信息化建设的重要组成部分&#xff0c;对于提升校园安全、教学效率和管理水平具有重要意义。本文旨在介绍智慧校园视频管理系统的架构设计&#xff0c;为构建高效、智能的校园视频监控系统提供参考。 二、系统整…

基于若依的ruoyi-vue3.8.7的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…

Java面试八股之WeakHashMap的工作原理

简述WeakHashMap的工作原理 弱键&#xff08;Weak Keys&#xff09;&#xff1a; WeakHashMap 的键&#xff08;keys&#xff09;是通过 WeakReference 弱引用进行封装的。弱引用是一种特殊的引用类型&#xff0c;它不会阻止所引用的对象被垃圾收集器回收。这意味着&#xff…

【计算机毕业设计】基于SSM+Vue的健身房管理系统【源码+lw+部署文档+讲解】

目录 1 绪论 1.1 研究背景 1.2 目的和意义 1.3 论文结构安排 2 相关技术 2.1 SSM框架介绍 2.2 B/S结构介绍 2.3 Mysql数据库介绍 3 系统分析 3.1 系统可行性分析 3.1.1 技术可行性分析 3.1.2 经济可行性分析 3.1.3 运行可行性分析 3.2 系统性能分析 3.2.1 易用性指标 3.2.2 可…

spring-boot-starter-mail 定义邮件工具类EmailHelper

注意 想把这个工具类定义成工具类&#xff0c;所以所有的方法都是静态方法&#xff0c;使用的变量处理参数理所当然都是静态变量期初使用的是Autowired 和 Value进行注解&#xff0c;但是这两个注解是依赖于实例&#xff0c;静态方法是不依赖实例的&#xff0c;所以 from 在发送…

如何在Spring启动的时候执行一些操作

如何在Spring启动的时候执行一些操作 在Spring启动的时候执行一些操作有多种方式。你可以通过实现ApplicationRunner或者CommandLineRunner接口&#xff0c;在Spring Boot应用程序启动后执行特定操作。另外&#xff0c;你也可以使用PostConstruct注解&#xff0c;在Spring Bea…

Dubbo配置上的一些概念

对于dubbo在spring中我们可能看到有如下配置&#xff08;可参考Schema 配置参考手册 | Apache Dubbo&#xff09;&#xff1a; dubbo:application:id: dubbo-account-examplename: dubbo-account-example# 是否启用 Dubbo 的 QoS&#xff08;Quality of Service&#xff09;服…

【Fastadmin】自定义404页面

1.修改config.php // 文件路径&#xff1a;application/config.php// 自定义错误码模板http_exception_template > [// 定义404错误的模板渲染404 > APP_PATH . common/view/404/404.html,], 2.需要把debug关闭才能生效 在.env文件中把debug true&#xff0c;改为…

在springboot项目中自定义404页面

今天点击菜单的时候不小心点开了一个不存在的页面&#xff0c;然后看到浏览器给的一个默认的404页面 后端的程序员都觉得这页面太丑了&#xff0c;那么怎么能自定义404页面呢&#xff1f; 很简单&#xff0c;在我们的springboot的静态资源目录下创建一个error包&#xff0c;然…

UL认证储能电表工商业储能智能计量电表ADL3000-E-B/KC

◉概述 ADL3000-E-B 导轨式多功能电能表&#xff0c;是主要针对电力系统&#xff0c;工矿企业&#xff0c;公用设施的电能统计、管理需求而设计的一款智能仪表&#xff0c;产品具有精度高、体积小、安装方便等优点。集成常见电力参数测量及电能计量及考核管理&#xff0c;提供…