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

news2024/7/6 18:52:56

一、39. 组合总和

题目链接/文章讲解/视频讲解: https://programmercarl.com/0039.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.html
状态:已解决

1.思路 

        这道题跟216. 组合总和 III - 力扣(LeetCode)题思路差不多,区别在于216题要求每种组合的大小必须为k,并且一个数字最多只能使用一次。而这道题组合大小可以随意,同一个数字也可以用多次。因此,根据这两个条件我们只需要在256题的基础上去掉终止条件path.size()==k,且for循环的开始值可以从前一个递归的for循环开始值开始(不能从0到candidate.size()-1,否则会导致{2,3}和{3,2}这种看似序列不同但是是一个组合的情况)。

        明确大致做法后,套用公式:

(1)确定返回值和参数:每个组合和所有组合的集合定义在回溯函数的外部,故不做参数,同时此题也不需要根据子函数的结果更新父函数的值,故无返回值。参数需要原数组candidates、下一层函数for循环的开始位置、目前的总和sum、以及目标值target。

void backtracking(vector<int>& candidates,int startIndex,int sum,int target)

(2)确定终止条件:当sum>target时,再回溯下去sum的值也只会越来越大,故此时可以终止(剪枝)。还有一种终止情况就是sum==target,此时得到我们所需值,并且终止本轮回溯。

if(sum>target){
    return ;
}
if(sum == target){
    result.push_back(path);
    return ;
}

(3)回溯遍历过程:进行for循环,内部做回溯(显式path的回溯和隐式sum的回溯)

for(int i=startIndex;i<candidates.size();i++){
    path.push_back(candidates[i]);
    backtracking(candidates,i,sum+candidates[i],target);
    path.pop_back();
}

2.完整代码

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    void backtracking(vector<int>& candidates,int startIndex,int sum,int target){
        if(sum>target){
            return ;
        }
        if(sum == target){
            result.push_back(path);
            return ;
        }
        for(int i=startIndex;i<candidates.size();i++){
            path.push_back(candidates[i]);
            backtracking(candidates,i,sum+candidates[i],target);
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        path.clear();
        result.clear();
        backtracking(candidates,0,0,target);
        return result;
    }
};

二、40.组合总和II

题目链接/文章讲解/视频讲解: https://programmercarl.com/0040.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8CII.html
状态:已解决

1.思路 

        这道题很多人一开始的想法估计都和我一样,都是先按常规写法求出结果集,然后做一个去重操作,但是大家发现这样特别麻烦!!!

        这道题比39题难的点在于候选集有重复元素但是结果集中不同有重复的组合。以前我们实现去重是根据下一层递归for循环起始值大于上一层递归for循环的起始值实现的,但是由于有相同值的元素,这样的方法就不行了,如:{1,1,5,1,1,3},target=7,对于前三个元素,可以构成一个组合,对于第三到第五个元素,也可以构成一个组合,但二者实际是一样的组合,即使采用之前的方法也不管用。

        卡哥在这道题引入了一个新的去重思路:自创了树层去重和树枝去重的概念。

(1)数层去重:引起重复的根本原因是因为相同值的元素出现在多个位置,而实际上如果出现在后面的重复值,其得到的有效组合必定跟曾出现在前面位置的相同元素的有效组合一致。假设我们设前面某个元素为x,后面某个元素为y,其中,x=y且y出现在x的后方。那么,如果存在{y,m,n}满足和等于target,则{x,m,n}也必定和等于target。反之,不一定成立(由下一层递归for循环起始值大于上一层递归for循环的起始值决定)。因此,其实我们只需要统计等于某个值的所有元素中,最靠前的元素的有效组合即可,其余重复值不再对其进行操作。

        如何实现这个操作:先对candidates进行排序,在for循环中,如果某层循环满足candidates[i]==candidates[i-1]时,跳过,不做后面的回溯操作即可。

(2)树枝去重:为什么要树枝去重?因为假如candidates={1,1,2,5},target=7,现在以第一个1为开头做回溯,由于要进行树层去重,需要判断candidates[i]是否等于candidates[i-1],而第一个1和第二个1相等,故明明可以选择第二个1的时候把第二个1跳过了,导致丧失了{1,1,5}这个有效组合,因此,单单一个candidates信息是不够的,还需要其他信息。

        这里卡哥采取的方法是新增一个数组used,记录哪些位置的元素被用过了。0代表未用,1代表已经使用了。如果candidates[i]已经等于candidates[i-1]了,但used[i-1]==0,则说明上一个节点未被使用,此时代表上一个元素与现在这个元素相同,但从现在这个元素开始取值,也就是说,此时是在树层位置,树层位置只对相同值中的第一个元素进行处理,故此时跳过第二个1。而假如used[i-1]==1,代表此时是以第一个1为开始元素进行回溯操作的,故代表程序进行到树枝位置,树枝位置遇到后面的重复元素是要取值的。

        根据题意可总结得到关键部分代码:

if(i>0 && candidates[i]==candidates[i-1] && used[i-1]==0) continue;

题目难度变大了,但是一行多余操作就能解决去重问题,甚妙!

2.完整代码

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates,int target,int startIndex,int sum,vector<int>& used){
        if(sum > target){
            return ;
        }
        if(sum == target){
            result.push_back(path);
            return ;
        }
        for(int i=startIndex;i<candidates.size();i++){
            if(i>0 && candidates[i]==candidates[i-1] && used[i-1]==0) continue;
            path.push_back(candidates[i]);
            used[i] = 1;
            backtracking(candidates,target,i+1,sum+candidates[i],used);
            used[i] = 0;
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        vector<int> used(candidates.size(),0);
        backtracking(candidates,target,0,0,used);
        return result;
    }
};

三、131.分割回文串

题目链接/文章讲解/视频讲解: https://programmercarl.com/0131.%E5%88%86%E5%89%B2%E5%9B%9E%E6%96%87%E4%B8%B2.html
状态:已解决

1.思路         

        我觉得这道题的关键在于如何把题意转化到回溯法上面去。其实把切割操作和组合问题的操作列出来对比一下就知道怎么转换了:

例如对于字符串abcdef:

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

实质还是一个组合问题,也就是一个回溯问题,可以画树状图来思考过程。

       递归用于纵向遍历,而for循环用于横向遍历,那究竟如何进行切割的呢?利用startIndex,每层递归函数,在for循环中,[startIndex,i]构成的字符串就是这次操作被切割的子串,将这个子串后面的子串送入下一层递归(即startIndex=i+1)。 

        回溯三部曲:

(1)参数和返回值类型:返回值类型依旧为void,参数需要原字符串s,开始切割位置startIndex。

void backtracking(const string& s,int startIndex)

(2)终止条件:当切割线达到字符串末尾时,切割完毕,即startIndex>=s.size() 。

if(startIndex >= s.size()){
    result.push_back(path);
    return;
}

(3)每层递归逻辑:

        for循环i从startIndex开始,向后移动,移动到一个位置, 就计算startIndex和i之间的字符串是不是回文串,是的话就将其放入path中,作为一个子串,并开始回溯,继续切割该子串后面的子串;否则,跳过该位置。

for(int i=startIndex;i<s.size();i++){
    if(isPalindrome(s,startIndex,i)){
        string substrs = s.substr(startIndex,i-startIndex+1);
        path.push_back(substrs);
    }else{
        continue;
    }
     backtracking(s,i+1);
     path.pop_back();
}

2.完整代码 

        完整代码只需要添加一个判断回文子串的函数(用双指针法)。

class Solution {
public:
    vector<string> path;
    vector<vector<string>> result;
    bool isPalindrome(const string& s,int startIndex,int i){
        for(int left = startIndex,right = i;left < right;left++,right--){
            if(s[left] != s[right]) return false;
        }
        return true;
    }
    void backtracking(const string& s,int startIndex){
        if(startIndex >= s.size()){
            result.push_back(path);
            return;
        }
        for(int i=startIndex;i<s.size();i++){
            if(isPalindrome(s,startIndex,i)){
                string substrs = s.substr(startIndex,i-startIndex+1);
                path.push_back(substrs);
            }else{
                continue;
            }
            backtracking(s,i+1);
            path.pop_back();
        }
    }
    vector<vector<string>> partition(string s) {
        path.clear();
        result.clear();
        backtracking(s,0);
        return result;
    }
};

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

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

相关文章

为什么感觉张宇 25 版没 24版讲得好?

很多同学反映&#xff1a;25版&#xff0c;讲得太散了, 知识点太多&#xff0c;脱离了基础班。 三个原因&#xff1a; 1. 25版改动很大&#xff0c;课程没有经过打磨&#xff1b; 2. 因为24考试难度增加&#xff0c;所以改动的总体思路是“拓宽基础”&#xff1a;即把部分强…

redis中bitmap的使用及场景,如何操作

一、概念 在Redis数据库中&#xff0c;Bitmap&#xff08;位图&#xff09;是一种特殊的数据结构&#xff0c;它不是一个独立的数据类型&#xff0c;而是基于String类型实现的。Bitmap主要用于存储大量二进制位&#xff08;0或1&#xff09;的数据&#xff0c;这些位可以代表不…

支付接口和数据库断言及封装

支付下单接口 请求方法&#xff1a; post 请求地址&#xff1a;http://shop.lemonban.com:8107/p/order/pay 请求参数&#xff1a;{“payType”:3,“orderNumbers”:“1733308182027309056”} 请求头部&#xff1a; {“Content-Type”:“application/json”,“Authorization…

HDMI 2.1b 规范解读

HDMI 规范 HDMI 2.1b 是最新版 HDMI 规范&#xff0c;支持一系列更高的视频分辨率和刷新频率&#xff0c;包括 8K60 和 4K120 以及高达 10K 的分辨率。同时支持动态 HDR 格式&#xff0c;带宽能力增加到 48Gbps HDMI。 新的超高速 HDMI 线缆支持 48Gbps 带宽。该线缆可确保提供…

在单通道彩图上踩的坑

使用labelme后&#xff0c;生成如图所示文件夹&#xff0c;其中JPEGImages是原图&#xff0c;SegmentationClassPNG是标签。 此时SegmentationClassPNG中的标签&#xff08;masks&#xff09;是只包含0和1的二进制文件&#xff0c;0表示背景,1表示要识别的物体类型。&#xff…

什么是ISP住宅IP?相比于普通IP它的优势是什么?

什么是ISP住宅IP&#xff1f; ISP住宅IP是指由互联网服务提供商&#xff08;ISP&#xff09;分配给住宅用户的IP地址。它是用户在家庭网络环境中连接互联网的标识符&#xff0c;通常用于上网浏览、数据传输等活动。ISP住宅IP可以是动态分配的&#xff0c;即每次连接时都可能会…

RabbitMQ高级-应用问题、集群搭建

1.消息补偿 消息可靠性保障&#xff1a;——消息补偿机制 需求&#xff1a;100%确保消息发送成功 2.幂等性保障 幂等性指一次和多次请求某一资源&#xff0c;对于资源本身应该具有同样的结果。也就是说&#xff0c;其任意多次执行对资源本身所产生的影响均与第一次执行的影响…

2024/3/31周报

文章目录 摘要Abstract文献阅读题目创新点实验数据研究区域数据和材料 方法XGBoost algorithmLong Short‑Term Memory AlgorithmEvaluation of the Model Accuracy 实验结果 深度学习XGBoost代码实现AdaBoostBoostingAdaBoost算法AdaBoost代码实现 总结 摘要 本周阅读了一篇基…

上海开放大学2024年春《过程控制技术》网上记分作业参考答案

答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 电大搜题 多的用不完的题库&#xff0c;支持文字、图片搜题&am…

SD-WAN组网面临的安全挑战?如何提供有效的安全措施

SD-WAN&#xff08;软件定义广域网&#xff09;技术的广泛应用&#xff0c;企业面临着越来越多的网络安全挑战。尽管SD-WAN带来了灵活性和效率的提升&#xff0c;但其开放性和基于云的特性也带来了一系列安全威胁。本文将探讨SD-WAN组网面临的安全挑战&#xff0c;并提供一些有…

1236. 递增三元组:做题笔记

目录 暴力 代码 二分 代码 前缀和 代码 推荐视频讲解 暴力 这道题说的是有三个元素数量相同的数组&#xff0c;想知道有多少个三元组满足&#xff1a;三个数分别来自 A B C数组且呈现递增。 我想的是既然要求递增&#xff0c;那就先把数组数据都排一下序&#xff0c;…

鸿蒙:滑动条组件Slider

滑动条组件&#xff0c;通常用于快速调节设置值&#xff0c;如音量调节、亮度调节等应用场景。 说明 该组件从API Version 7开始支持。 子组件 无 接口 Slider(options?: {value?: number, min?: number, max?: number, step?: number, style?: SliderStyle, direc…

如何使用potplayer在公网环境访问内网群晖NAS中储存在webdav中的影视资源

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-D7WJh3JaNVrLcj2b {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

渐变颜色作图

clear clc close all % 生成 x 值 x linspace(0, 5, 1000); % 计算对应的 y 值&#xff08;二次函数分布&#xff09; y x .^ 2; % 添加一些随机噪声 y y randn(size(y)); clinspace(1,10,length(x)); arry1[x,y]; arry2sortrows(arry1,2,descend); arry3[arry2,c]…

LabVIEW双通道太阳射电频谱观测系统

LabVIEW双通道太阳射电频谱观测系统 开发了一个基于LabVIEW平台开发的双通道高速太阳射电频谱观测系统。该系统实时监测太阳射电爆发&#xff0c;具有随机性、持续时间短、变化快等特点。通过高速信号采集卡实现1.5 GS/s的信号采集&#xff0c;时间分辨率可达4ms&#xff0c;频…

跑腿小程序|基于微信小程序的跑腿平台小程序设计与实现(源码+数据库+文档)

跑腿平台小程序目录 目录 基于微信小程序的跑腿平台小程序设计与实现 一、前言 二、系统设计 三、系统功能设计 1、用户信息管理 2、跑腿任务管理 3、任务类型管理 4、公告信息管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、…

MySQL 优化及故障排查

目录 一、mysql 前置知识点 二、MySQL 单实例常见故障 故障一 故障二 故障三 故障四 故障五 故障六 故障七 故障八 三、MySQL 主从故障排查 故障一 故障二 故障三 四、MySQL 优化 1.硬件方面 &#xff08;1&#xff09;关于 CPU &#xff08;2&#xff09;关…

备战蓝桥杯---贪心刷题2

话不多说&#xff0c;直接看题&#xff1a; 首先我们大致分析一下&#xff0c;先排序一下&#xff0c;Kn&#xff0c;那就全部选。 当k<n时&#xff0c;k是偶数&#xff0c;那么结果一定非负&#xff0c;因为假如负数的个数有偶数个&#xff0c;那么我们成对选它&#xff0…

单链表的一些个人易错的问题(有理解不正确的地方请大佬指正)

请看下列代码 代码1&#xff1a;&#xff08;正确示范&#xff09; #include<stdio.h> #include<stdlib.h> struct node {int data;struct node *next;}; int main() {int n;int a;printf("请输入数字个数&#xff1a;");scanf("%d",&n)…

行车记录打不开?数据恢复有高招!

行车记录打不开&#xff0c;是许多车主在使用行车记录仪时常遇到的问题。当启动设备时&#xff0c;屏幕无反应或提示错误&#xff0c;让人不禁焦虑&#xff0c;担心重要的行车记录丢失。 行车记录打不开原因分析 行车记录打不开&#xff0c;其背后的原因可能复杂多样。硬件故障…