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

第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;
    }
};

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


总结 

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一文汇总VSCode多光标用法

光标的创建 按住alt&#xff0c;鼠标左键单击&#xff0c;在单击位置生成光标/删除光标 按住ctrlalt&#xff0c;单击↑/↓&#xff0c;在每行同一个位置&#xff08;若某一行较短&#xff0c;则在行尾&#xff09;生成光标&#xff0c;这个不会删除光标&#xff0c;只会在光标…

点击获取2024SIAL西雅国际食品展上海展后报告

随着2024年SIAL 西雅展&#xff08;上海&#xff09;的圆满落幕&#xff0c;我们不仅见证了一场食品与饮料行业的国际盛会&#xff0c;更是感受到了上海这座城市独有的魅力与活力。在这里&#xff0c;我们回顾了上海展的辉煌成就&#xff0c;同时&#xff0c;我们也满怀期待地展…

基于横纵向的混合联邦学习原理分析

近期陆续接触到关于混合联邦学习的概念&#xff0c;但基于横纵向的混合联邦实际的应用案例却几乎没有看到&#xff0c;普遍是一些实验性的课题&#xff0c;因此这一领域知识没有被很好普及。本篇文章的目的&#xff0c;主要是分析讨论关于横纵向混合联邦学习的业务场景、应用架…

Linux Redis 服务设置开机自启动

文章目录 前言一、准备工作二、操作步骤2.1 修改redis.conf文件2.2 创建启动脚本2.3 设置redis 脚本权限2.4 设置开机启动2.5 验证 总结 前言 请各大网友尊重本人原创知识分享&#xff0c;谨记本人博客&#xff1a;南国以南i、 提示&#xff1a;以下是本篇文章正文内容&#x…

【Electron】Electron入门实现

Electron 学习笔记 Electron 是一个开源框架&#xff0c;允许开发者使用网页技术&#xff08;HTML、CSS 和 JavaScript&#xff09;来构建跨平台的桌面应用程序。它由 GitHub 开发并维护&#xff0c;最初是为了支持开发 Atom 编辑器。Electron 结合了 Chromium&#xff08;用于…

海外仓一件代发业务优化指南:成本构成分析及优化策略

一件代发是大部分海外仓的核心业务&#xff0c;不过随着海外仓市场竞争的加剧&#xff0c;仓库经营成本上涨成了普遍现象。 今天我们会结合众多海外仓的实际情况&#xff0c;综合分析海外仓一件代发业务成本的构成&#xff0c;成本激增的原因以及对应的优化策略&#xff0c;希…

仓库选址问题【数学规划的应用(含代码)】阿里达院MindOpt

本文主要讲述使用MindOpt工具优化仓库选址的数学规划问题。 视频讲解&#x1f448;&#x1f448;&#x1f448;&#x1f448;&#x1f448;&#x1f448;&#x1f448;&#x1f448;&#x1f448; 一、案例场景 仓库选址问题在现代物流和供应链管理中具有重要的应用。因为仓库…

findfont: Generic family ‘sans-serif‘ not found because none of the ...: SimHei

警告过程 python代码在使用matplotlib画图时&#xff0c;如果在title&#xff0c;xlabel&#xff0c;ylabel中出现了中文&#xff0c;则会出现字体警告&#xff0c;中文字符显示为方框 例如代码&#xff1a; # matplotlib画图# 设置色带plt.imshow(data, cmapplt.cm.YlGn) #…

【AI大模型】应用开发基础,学到就是赚到!

前言 1、了解大模型能做什么 2、整体了解大模型应用开发技术栈 3、浅尝OpenAI API的调用 AI全栈工程师&#xff1a;懂AI、懂编程、懂业务的超级个体&#xff0c;会是AGI&#xff08;Artificial General Intelligence 通用人工智能&#xff09;时代最重要的人。 知识体系 AI学习…

【Mybatis 与 Spring】事务相关汇总

之前分享的几篇文章可以一起看&#xff0c;形成一个体系 【Mybatis】一级缓存与二级缓存源码分析与自定义二级缓存 【Spring】Spring事务相关源码分析 【Mybatis】Mybatis数据源与事务源码分析 Spring与Mybaitis融合 SpringManagedTransaction&#xff1a; org.mybatis.spri…

Ubuntu/Linux调试安装南京来可CAN卡

准备好USB rules文件和can driver文件备用! 必做&#xff1a;放置USB rules文件到对应位置处理权限问题 而后&#xff1a;安装内核driver并编译。需求众多依赖编译环境&#xff0c;视情况安装填补。如GCC,G,make等等 进入对应64bit文件夹中&#xff0c;添加权限&#xff0c;执…

爬虫:爬取知乎热榜一级评论及回答2024不包含翻页

一、先上结果&#xff08;注:本文仅为兴趣爱好探究&#xff0c;请勿进行商业利用或非法研究&#xff0c;负责后果自负&#xff0c;与作者无关&#xff09; 1、爬标题及其具体内容 2、抓标题下的对应回答 3、爬取对应一级评论 二、上流程 1、获取cookies&#xff08;相信哥哥姐姐…

Qt Creator创建一个用户登录界面

目录 1 界面设计 2 代码 2.1 登录界面 2.2 注册界面 2.3 登陆后的界面 3 完整资源 这里主要记录了如何使用Qt Creator创建一个用户登录界面&#xff0c;能够实现用户的注册和登录功能&#xff0c;注册的用户信息存储在了一个文件之中&#xff0c;在登录时可以比对登录信息…

大厂程序员上班猝死成常态?

大家好&#xff0c;我是瑶琴呀&#xff0c;拥有一头黑长直秀发的女程序员。 近日&#xff0c;连续看到大厂程序员猝死、低血糖晕倒的新闻&#xff0c;同为程序员感到很难受。互联网加班成常态这是既定事实&#xff0c;尤其在这个内卷严重、经济不景气的环境中&#xff0c;加班…

actual combat 31 —— 多级表头excel导出

设置模板占位符 &#xff08;模板占位符表头不带点&#xff0c;非表头数据行带点&#xff0c;举例{.ago}&#xff0c;{ago}&#xff09;引入easyExcel依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><v…

【深度学习】图形模型基础(1):使用潜在变量模型进行数据分析的box循环

1.绪论 探索数据背后的隐藏规律&#xff0c;这不仅是数据分析的艺术&#xff0c;更是概率模型展现其威力的舞台。在这一过程中&#xff0c;潜在变量模型尤为关键&#xff0c;它成为了数据驱动问题解决的核心引擎。潜在变量模型的基本理念在于&#xff0c;那些看似复杂、杂乱无…

uniapp加载打点点效果

uniapp加载打点点效果 背景实现思路代码实现尾巴 背景 为了增加系统的交互性&#xff0c;我们在加载数据时通常会增加一些loading动效&#xff0c;但是在某些场景下只需要一些简单文字提醒。比如说使用【加载中】或者【loading】等字段&#xff0c;但是写静态的字符又显得交互…

新手必备!短视频剪辑常用的18个技巧——剪映篇

导入素材&#xff1a;这里我们可以选择自己拍摄好的素材&#xff08;图片、视频或录制好的音频&#xff09;&#xff0c;按照顺序导入剪辑区剪辑。这一步是剪辑的基础&#xff0c;确定剪辑的大体思路与成片框架&#xff01;别忽略了&#xff0c;剪映官方素材库提供的素材&#…

Windows宝塔面板部署ThinkPHP8.0创建Vue项目案例

安装ThinkPHP8.0 登录宝塔面板&#xff0c;创建一个站点。 输入composer代码&#xff0c;执行完成后自动创建TP目录 composer create-project topthink/think tp 网站目录设置为tp&#xff0c;运行目录设置为public 设置PHP版本为8.0以上&#xff0c;不然会出现下面的报错代…

中科驭数第三代DPU芯片K2-Pro,专为数据中心打造的“六边形战士”

近日&#xff0c;中科驭数重磅发布第三代DPU芯片K2-Pro&#xff0c;是国内首颗面向量产的全功能芯片&#xff01; K2-Pro采用自主研发的Kernel Processing Unit架构&#xff0c;集网络、存储、安全及计算等多业务卸载功能于一体&#xff0c;包处理速率翻倍至80Mpps&#xff0c…