Studying-代码随想录训练营day23| 39.组合总和、40.组合总和II、131.分割回文串

news2024/10/7 12:27:01

第23天,回溯part02,回溯两个题型组合,切割(ง •_•)ง💪

目录

39.组合总和

40.组合总和II

131.分割回文串 

总结 


39.组合总和

文档讲解:代码随想录组合总和

视频讲解:手撕组合总和

题目:

学习:

本题是在一个数组中抽取数加起来的和为target,与昨天的k个数的题目不同,昨天的题目中数的个数k限制了递归的层数,本题则是target限制了递归的层数。

同时要注意本题允许同一个数字无限制重复取用,因此每一层循环的范围要包含该节点,将本题的回溯逻辑转化为一张树形图为:

代码:确定回溯三部曲

//时间复杂度O(n*2^n)
//空间复杂度O(target)
class Solution {
public:
    vector<vector<int>> result; //答案数组
    vector<int> path; //保存路径
    //确定返回值和参数,本题有保存路径和答案数组因此不需要返回值,参数需要原数组,目标值target,一个集合范围下标startindex,一个求和sum
    void backtracking(vector<int>& candidates, int target, int startindex, int sum) {
        //确定终止条件
        if(sum > target) return;
        if(sum == target) {
            result.push_back(path);
            return;
        }
        //确定单层递归逻辑
        for(int i = startindex; i < candidates.size(); i++) {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, i, sum);
            //回溯
            sum -= candidates[i];
            path.pop_back();
        }
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates, target, 0, 0);
        return result;
    }

剪枝优化:本题显然能够针对sum进行优化,但优化前还需要注意要先对数组candidates进行排序,便于进行剪枝。

class Solution {
public:
    vector<vector<int>> result; //答案数组
    vector<int> path; //保存路径
    //确定返回值和参数,本题有保存路径和答案数组因此不需要返回值,参数需要原数组,目标值target,一个集合范围下标startindex,一个求和sum
    void backtracking(vector<int>& candidates, int target, int startindex, int sum) {
        //确定终止条件
        if(sum == target) {
            result.push_back(path);
            return;
        }
        //确定单层递归逻辑
        //剪枝处理,缩小for循环范围
        for(int i = startindex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, i, sum);
            //回溯
            sum -= candidates[i];
            path.pop_back();
        }
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        //进行排序,进行剪枝
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0, 0);
        return result;
    }
};

40.组合总和II

文档讲解:代码随想录组合总和II

视频讲解:手撕组合总和II

题目:

学习:

本题实际上和我们之前所学的三数之和,四数之和有些相似,那两题能够通过有限的循环找到答案。本题没有确定数的个数,因此本题不能直接通过有限个循环求解,需要采用回溯的方法。

本题的难点在于遍历过程中我们还需要进行去重处理,但这可以参考我们在三数之和里面进行的去重。关键在于当前遍历的数和前一个数相等时,当前的遍历就可以跳过,因为从当前往后遍历的所有可能的情况,前一个数都已经遍历过了,为了避免重复就可以跳过当前的循环。(要注意去重之前还需要对数组进行排序!)

上述的去重方式可以理解为树层去重,同一层相同的数可以进行去重,而同一个树枝上的组合里的元素不需要去重,因为一个组合是允许有相同的树的。

本题的剪枝和上一题相同,本题采用对target作减法的方式找到答案组合,因此当target小于0时就可以进行返回了,缩减for循环范围:

代码:确定回溯三部曲

//时间复杂度O(n*2^n)
//空间复杂度O(n)
class Solution {
public:
    //本题和三数之和十分相似,但三数之和规定了三个数,本题没有规定数的个数限制,无法确定循环层数,因此需要使用回溯
    vector<vector<int>> result; //答案数组
    vector<int> path; //保存路径
    //确定返回值和参数,本题直接在答案数组中进行操作,因此不需要返回值,参数需要给的数组,目标值,和一个指示范围的值
    //这里我们采用使用target作减法的方式,来进行和的求解
    void backtracking(vector<int>& candidates, int target, int startindex) {
        //确定终止条件
        if(target == 0) {
            result.push_back(path);
        }
        //确定单层递归逻辑
        //对范围进行剪枝
        for(int i = startindex; i < candidates.size() && target - candidates[i] >= 0; i++){
            //对结果去重,如果后面遍历的数有和前面的相同,则跳过它,因为前面相同的数已经把后面的结果遍历完了
            if(i > startindex && candidates[i] == candidates[i - 1]) continue;
            path.push_back(candidates[i]);
            target -= candidates[i];
            backtracking(candidates, target, i + 1);
            path.pop_back();
            target += candidates[i];
        }
    }
    
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        //排序便于进行去重
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0);
        return result;
    }
};

131.分割回文串 

文档讲解:代码随想录分割回文串

视频讲解:手撕分割回文串

题目:

学习:

本题是回溯算法中切割的第一道题,题干虽然很短,但实际难度很大。我们可以将问题分为几个步骤:1.如何切割字符串;2.如何遍历不同的切割方式;3.如何判断字符串是否回文。

针对以上三个问题,前两个实际上可以看作是一个组合的问题,即如何分开不同的数和如何不重复的遍历所有的情况。而第三个问题则是在循环过程中需要进行判断的,因此本题的回溯逻辑用树形结构可以写为:

可以看出切割的回溯搜索的过程实际上和组合问题的回溯搜索过程是差不多的。本题重点需要关注的是如何确定切割线以及如何截取子串。

  • 如何确定切割线:由图中我们可以看出,实际上每一层的切割线都是由startindex确定的,例如循环的第一层startindex=0,切割线排在最前面,因此每次都至少包含第一个字母a。而第二层对于最左边的节点来说startindex=1,切割线在第二个字母,前面的字符串已经确定了,之后的每次切割都包含第二个字母(由于ab不是回文所以不进行循环)。
  • 如何截取子串:确定了切割线的位置,子串的终点实际就是我们遍历过程中的i,i的变化决定了子串的长度,例如第一层来说,i从0到3,切割出来的子串就是a,aa,aab。子串就可以表示为字符串s中[startindex, i]区间包含的元素。

最后判断回文串,可以通过一个函数进行, 使用双指针法,一个指针从前向后,一个指针从后向前,如果前后指针所指向的元素是相等的,就是回文字符串了。最后的代码如下:

代码:确定回溯三部曲

//时间复杂度O(n*n^2)
//空间复杂度O(n^2)
class Solution {
public:
    vector<vector<string>> result; //返回数组
    vector<string> path; //保存路径
    //确定返回值和参数,本题同样不需要返回值,参数中除了字符串s以外,还需要一个起始下标startindex
    //这里十分要注意startindex就是切割线
    void backtracking(string s, int startindex) {
        //确定终止条件,当切割线等于字符串长度,意味着最后也切割完成了
        if(startindex == s.size()) {
            result.push_back(path);
            return;
        }
        //确定单层递归逻辑
        for(int i = startindex; i < s.size(); i++) {
            //以startindex为切割线一直到i为字符串长度
            //如果是回文串的话,假如path当中
            if(Palindrome(s, startindex, i)) {
                //获取[startindex,i]在s中的子串,第一个参数为下标位置,第二个参数为长度
                string str = s.substr(startindex, i - startindex + 1);
                path.push_back(str);
            }
            else {
                continue;
            }
            backtracking(s, i + 1); //切割下一个字符串
            path.pop_back(); //回溯
        }
    }
    //判断是否是回文字符串
    bool Palindrome(string s, int start, int end) {
        for(int i = start, j = end; i < j; i++, j--) {
            if(s[i] != s[j]) return false;
        }
        return true;
    }
    vector<vector<string>> partition(string s) {
        backtracking(s, 0);
        return result;
    }
};

总结:本题的难点主要有:切割问题可以抽象为组合问题、如何模拟那些切割线、切割问题中递归如何终止、在递归循环中如何截取子串、如何判断回文。理解这几点本题也就能够解出来了。


总结 

回溯算法是一个暴力搜索的方法,因此我们重点要理解每一道题暴力搜索的逻辑过程,才能够写出正确的回溯算法代码,多加练习💪。

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

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

相关文章

启智畅想:AI集装箱箱号识别系统,解决方案提供商

AI集装箱箱号识别系统 当前,智能卡口管理行业正处于快速发展的阶段。随着物联网、大数据、人工智能等技术的不断进步,智能卡口管理系统已经能够实现对集装箱运输的全程跟踪、监控和管理,大大提高了管理效率和安全性。然而,市场上现有的智能卡口管理系统仍然存在一些痛点问题,如…

【文档智能】DLAFormer:端到端的解决版式分析、阅读顺序方法

前言 前面文章介绍到&#xff0c;文档智能中版式分析(DLA)&#xff08;《【文档智能 & RAG】RAG增强之路&#xff1a;增强PDF解析并结构化技术路线方案及思路》&#xff09;、阅读顺序&#xff08;《【文档智能】符合人类阅读顺序的文档模型-LayoutReader及非官方权重开源…

Windows怎么实现虚拟IP

在做高可用架构时&#xff0c;往往需要用到虚拟IP&#xff0c;在linux上面有keepalived来实现虚拟ip的设置。在windows上面该怎么弄&#xff0c;keepalived好像也没有windows版本&#xff0c;我推荐一款浮动IP软件PanguVip&#xff0c;它可以实现windows上面虚拟ip的漂移。设置…

Feign 配置全局日志存入mongo

1、开启feign日志 在application.yml 添加配置 feign:client:config:default:loggerLevel: FULL2、日志实体类 Document(collection "feignLogs") Data public class FeignLog {Idprivate String id;private String method;private String url;private LocalDate…

智慧园区综合平台解决方案PPT(75页)

## 智慧园区的理解 ### 从园区1.0到园区4.0的演进 1. 园区1.0&#xff1a;以土地经营为主&#xff0c;成本驱动&#xff0c;提供基本服务。 2. 园区2.0&#xff1a;服务驱动&#xff0c;关注企业成长&#xff0c;提供增值服务。 3. 园区3.0&#xff1a;智慧型园区&#xff…

Spring Boot结合FFmpeg实现视频会议系统视频流处理与优化

在构建高效稳定的视频会议系统时,实时视频流的处理和优化是开发者面临的核心挑战之一。这不仅仅是简单的视频数据传输,更涉及到一系列复杂的技术问题,需要我们深入分析和有效解决。 高并发与实时性要求: 视频会议系统通常需要支持多人同时进行视频通话,这就意味着系统需要…

IP白名单及其作用解析

在网络安全领域&#xff0c;IP白名单是一项至关重要的策略&#xff0c;它允许特定的IP地址或地址范围访问网络资源&#xff0c;从而确保只有受信任的终端能够连接。下面&#xff0c;我们将深入探讨IP白名单的定义、作用以及实施时的关键考虑因素。 一、IP白名单的定义 IP白名单…

django admin添加自己的页面

建立模型 如果要单独建一个页面&#xff0c;用于展示model的数据&#xff0c;可以新建一个model&#xff0c;继承自要展示的那个类 class ViewsByDayModel(ViewsByDay): # 父类为要展示的model类class Meta:proxy True # 使用代理verbose_name 每日浏览次数统计verbose_nam…

当客户想要实现监控视频AI识别

客户说&#xff0c;“我想要实现视频AI识别&#xff0c;你给我出个方案” “好好好&#xff0c;没问题” “现在有安装的监控设备吗” “现在已经安装了多少监控设备&#xff1f;需要加装吗&#xff1f;” “已经安装的监控设备是什么配置&#xff0c;有AI算法吗&#xff1…

GOROOT GOPATH GOPROXY GO111MODULE

GOROOT GOROOT代表Go的安装目录。可执行程序go(或go.exe)和gofmt(或gofmt.exe)位于 GOROOT/bin目录中。 配置GOROOT环境变量&#xff0c;其值为Go的安装目录&#xff1b;然后在环境变量PATH中添加GOROOT/bin路径。 注意&#xff1a;GOROOT变量只是代表了安装目录&#xff0c;不…

微服务 | Springboot整合GateWay+Nacos实现动态路由

1、简介 路由转发 执行过滤器链。 ​ 网关&#xff0c;旨在为微服务架构提供一种简单有效的统一的API路由管理方式。同时&#xff0c;基于Filter链的方式提供了网关的基本功能&#xff0c;比如&#xff1a;鉴权、流量控制、熔断、路径重写、黑白名单、日志监控等。 基本功能…

Spring容器的启动过程及留给开发者的可拓展点

一、Spring容器启动经过了哪些过程&#xff1f; 1、首先需要加载读取到应用环境中的配置&#xff0c;比如要加载的bean的class的包路径等信息。 【读取配置】 2、再就需要找到哪些类是需要spring进行类实例创建并管理的&#xff0c;扫描到具体的Class及Class元信息上的一些注…

探索高效开发神器:Blackbox AI(免费编程助手)

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 &#x1f916; 想要代码生成&#xff1f;&#x1f44c; &#x1f4ac; 需要和AI聊天解决难题&#xff1f;&#…

信息化与农业生产的深度融合

信息化与农业生产的深度融合 信息化与农业生产的深度融合&#xff0c;是现代农业发展的一个重要趋势&#xff0c;它不仅促进了农业生产效率的显著提升&#xff0c;还为实现农业可持续发展和精准管理提供了强有力的支撑。这一过程不仅仅是技术的简单叠加&#xff0c;更是农业生…

TIA博途WinCC通过VB脚本从 Excel中读取数据的具体方法介绍

TIA博途WinCC通过VB脚本从 Excel中读取数据的具体方法介绍 添加 一个PLC,设置PLC的IP地址,如下图所示, 添加全局DB块,新建几个变量,如下图所示, 在数据块中添加了 tag1 …… tag6 ,共 6 个浮点数类型的变量,用来接收通过 WinCC 从 Excel 文件中读取的数据。 添加 HMI…

Mathematica训练课(44)-- 一些符号#,,//, /. 的整理

①“//”在后面写成你要执行的操作,即可执行。 注意:这一函数作用域标志的优先级是很靠后的,也就是说它会对一整行式子作用。 ②@的作用是在@后面的第一个元素进行操作 Sqrt @ a(*@作用在@后面、对离@最近的仅仅一个元素作用*) 例如,下面 若作用对象外面套着{},那么就要…

shopify入门教程-应用开发(二)

4.内网穿透 为什么要用这个&#xff0c;就是把电脑上的开发内容通过内网穿透显示到你的开发店铺上。这里的内网穿透我用了ngrok,花生壳&#xff0c;但都不如shopify官方推荐的cloudflare好用。所以这里我也推荐cloudflare。 运用内网穿透2个步骤 把app运行起来 ​​​​​​​…

EtherCAT笔记(五)—— 寻址方式与应用层协议

目录 1. EtherCAT 报文寻址 1.1 EtherCAT 网段寻址 1.1.1 直连模式 1.1.2 开放模式 1.2 段内寻址 —— 设备寻址 1.2.1 顺序寻址 1.2.2 设置寻址 1.3 逻辑寻址 1.4 关于WKC 2. 应用层协议 2.1 CoE &#xff1a; CANopen over EtherCAT 2.2 SoE (Servo Drive Profile …

SpringBoot学习04-[定制SpringMVC]

定制SpringMVC 定制SpringMvc的自动配置定制springmvc-configurePathMatch配置定制SpringMVC-拦截器Interceptor定制SpringMVC-CORS配置全局cors配置针对某个方法加跨域解决 WebMvcConfigurer原理定制SpringMVC-JSONJSON开发jackson的使用定制化json序列化和反序列化 JSON国际化…

解锁音乐潮流:使用TikTok API获取平台音乐信息

一、引言 TikTok&#xff0c;作为全球领先的短视频社交平台&#xff0c;不仅为用户提供了展示自我、分享生活的舞台&#xff0c;还为用户带来了丰富多样的音乐体验。在TikTok上&#xff0c;音乐与视频内容的结合&#xff0c;为用户带来了全新的视听盛宴。对于音乐制作人、品牌…