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

【蓝桥杯速成】| 10.回溯切割

前面两篇内容我们都是在做有关回溯问题的组合应用

今天的题目主题是:回溯法在切割问题的应用

题目一:分割回文串

问题描述

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

给你一个字符串 s,请你将 s 分割成一些 子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

示例 1:

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

示例 2:

输入:s = "a"
输出:[["a"]]

提示:

  • 1 <= s.length <= 16
  • s 仅由小写英文字母组成

解题步骤

在做这题之前我们需要明确回文串是什么东西👇

那么我们解决这个问题就可以分为分割字符串和判断回文串两个part

运用回溯法分割字符串其实和组合问题是类似的思路

我们需要按照字符串顺序,在所有可能位置砍上一刀,形成不同的字符串碎片

那么为了不错过任何一种可能,就需要按顺序遍历

下面就来尝试使用回溯三部曲!

1.确定函数返回值及参数

依旧使用backtracking这个名字,没有返回值的要求,参数呢传入我们需要使用的字符串s以及一个开始索引startindex即可

这个开始索引是不断变大的,意味着每次砍第一刀下去,第一个碎片的长度在慢慢变大,这也是我们有序分割的重要环节

void backtracking(string s,int startindex)

2.确定终止条件

按照最简单的思路,和之前的组合问题一样,我们会在叶子结点取到该过程的一个答案,再做个对应题目限制的判断,合适就加入result中, 

那么我们的分割也是这样,在叶子节点结束分割,把整个字符串剁碎了,

但需要注意的是,在叶子节点我们得到的不是碎片,而是虽然碎了但仍旧可以拼成完整s的组合体

就像你买了一只烤鸭,咔咔剁了几刀,你拿到手的是所有鸭子碎片而不是只有一个鸭屁股

那么在这种情况下,加入判断回文子串的代码好像有点不方便

所以我们改为在单层递归的时候做判断而不是在终止条件做判断

这样做同时也更省事了,如果你切了一刀发现这个不是回文串就没必要再切下去了

就好比切到鸭屁股的话扔掉就好了,没必要把鸭屁股也剁碎留着

还有一点需要明确的是分割完毕的表示方法,

在参数列表中我们不断传递的startindex实际上就对应我们砍下去的那一刀,

那么只要这个索引大于等于回文串长度就是切完了

if(startindex>=s.size()){

        result.push_back(path);

        return;

}

 3.确定单层遍历操作

还是同样的逻辑,最外层确定第一刀位置

切下来以后加入path中,再递归切第二刀...

递归结束后需要pop加入值,做一个回溯

除了以上切割操作,我们还需要加入判断回文串的代码

这一个部分为使效率最大化可以加在把碎片加入path的代码前

也就是说符合回文才加入path,否则直接return

判断回文串我们可以另设函数所以单层遍历操作应该如下:

for(int i=startindex;i<s.size();i++){

        if(isPalindrome(...){//这个函数等会再想!

                string str = s.substr(startindex,i-startindex+1);//当前碎片是【startindex,i】

                path.push_back(str);//是回文串!切下来放进path中!

        }else{

                continue;//不是就跳出!

        }

        backtracking(s,i+1);

        path.pop_back();

}       

补充:

s.substr(pos, len)

substr()是C++语言函数,主要功能是复制子字符串,要求从指定位置开始,并具有指定的长度。如果没有指定长度_Count或_Count+_Off超出了源字符串的长度,则子字符串将延续到源字符串的结尾。 

 ok那么回溯算法的步骤我们已经完成了,还剩下判断回文串的逻辑需要写

4.判断回文串

按照回文串的概念,我们只需要一个指针从头开始,一个指针从尾开始

不断同频比较两个字符是否相等就可以了

那么需要的参数就是这个字符串碎片

返回值设为bool类型

bool isPalindrome(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;

}

合并所有代码,并在主函数中传参调用即可!完整代码看下面👇! 

code

class Solution {
public:
    vector<string> path;
    vector<vector<string>> result;
    bool isPalindrome(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;
    }
    void backtracking(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 str=s.substr(startindex,i-startindex+1);
                path.push_back(str);
            }else{
                continue;
            }
            backtracking(s,i+1);
            path.pop_back();
        }
    }
    vector<vector<string>> partition(string s) {
        backtracking(s,0);
        return result;
    }
};

题目二:复原IP地址

问题描述

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

有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

  • 例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245""192.168.1.312" 和 "192.168@1.1" 是 无效 IP 地址。

给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

示例 1:

输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]

示例 2:

输入:s = "0000"
输出:["0.0.0.0"]

示例 3:

输入:s = "101023"
输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]

提示:

  • 1 <= s.length <= 20
  • s 仅由数字组成

解题步骤

参照上一题,我一开始写了以下代码,

主要思路就是无情分割,直到最后,

在每一步分割中使用函数判断是否符合要求

合理就加入path,逐渐组成IP地址

最后在叶子节点处加入正确的IP到result中

但是发现在单层递归中很难处理,

所以我们需要改变一下整个回溯的过程,按照这个题目的特点,给出量身定制的方案

class Solution {

public:

    string path;

    vector<string> result;

    bool isOK(string str){

        if(str.empty() || str.size()>3 || str[0]=='0'){

            return false;

        }

        int num=stoi(str);

        return num>=0&&num<=255;

    }

    void backtracking(string s,int startindex){

        if(startindex>=s.size()){

            result.push_back(path);

            return;

        }

        for(int i=startindex;i<s.size();i++){

            string str=s.substr(startindex,i-startindex+1);

            if(isOK(str)){

                path+=str;

               //@#@¥¥#%#!@@¥#@%!@?????

            }else{

                break;

            }

            backtracking(s,i+1);

            path.pop_back();

        }

    }

    vector<string> restoreIpAddresses(string s) {

        if(s.size()<4 || s.size()>12){

            return result;

        }

        backtracking(s,0);

        return result;

    }

};

 再审审题!

IP地址是只能有4段的,每一段不超过3个数字,

所以这里我们需要把段数作为切割完成的标志,不能再用索引指到最后,

这样就相当于做数学证明题有条件没用上,那一般来说都是证不出来的!!!

那么和段数有关的还有IP地址中的 '.' (点),

为了一举两得(实现判断段数和加点)我们可以用点数来代表段数,

点数为3意味着段数为4嘛!

重新走一边回溯三部曲

1.确定函数返回值及参数

按照上面的思路,我们得加入段数作为参数,搭配startindex共同表示分割情况

startindex关乎下刀点,不能丢掉,把刀丢了还怎么切嘛!

void backtracking(string s,int startindex,int pointnum)

2.确定终止条件

上面已经捋清楚了:点数为3意味着段数为4

但是加入第三个点后我们还没有判断剩下部分,也就是第四段是否符合要求

所以这个最后一部分的合法性要放在这个终止条件中进行判断

合法后再加入到result中

if(pointnum==3){

        if(isValid...){//判断合法性等会再说!

                result.push_back(s);

        }

        return;      

}

 3.确定单层遍历操作

在单层遍历中我们需要往s中加点(题目说了可以通过在 s 中插入 '.' 来形成),修改pointnum,递归,回溯

最外层遍历依旧代表第一刀(或者说上一刀)的位置,

这样才能确保第二🔪是从剩下的部分切的

切下来一段就需要判断是否合法

如果合法,在s的这个位置后面加上点

再让pointnum+1

递归backtracking函数

pointnum回溯,

s回溯

如果不合法,那么直接结束

for(int i=startindex;i<s.size();i++){

        if(isValid...){

                s.insert(s.begin()+i+1,'.');//加点

                pointnum++;

                backtracking(s,i+2,pointnum);//加入点后要再后挪一位!

                pointnum--;

                s.erase(s.begin()+i+1);

        }

        else

                break;

}

4.编写判断合法性函数

作为一个判断类的函数,返回值肯定是bool类型,

参数是为了传入待判断的片段,所以依旧是s,start,end

在函数体中利用if 判断IP地址一段的数字区间是否在[0,255],每一段开头第一个是否为0,区间长度是否小于等于3,该片段是否为空

否就返回false,都没有踩雷就返回true

bool isValid(string& s,int start,int end){   

        if (start > end || end - start + 1 > 3) {

            return false; // 片段为空或长度超过3

        }

        if (s[start] == '0' && start != end) {

            return false; // 前导0不合法

        }

        string str = s.substr(start, end - start + 1);

        int num = stoi(str);

        return num >= 0 && num <= 255;

}        

 最后整合代码,传入对应参数,在主函数中调用即可,

此外,在主函数中还可以加入一个剪枝,

如果字符串长度明显不符合IP地址的长度就不用调用回溯算法了

if(s.size()<=0 || s.size()>12)

        return result;

完整代码在下方! 

code 

class Solution {
public:
    vector<string> result;

    bool isValid(string& s,int start,int end){
        if (start > end || end - start + 1 > 3) {
            return false; // 片段为空或长度超过3
        }
        if (s[start] == '0' && start != end) {
            return false; // 前导0不合法
        }
        string str = s.substr(start, end - start + 1);
        int num = stoi(str);
        return num >= 0 && num <= 255;
    }     
     
    void backtracking(string s,int startindex,int pointnum){
        if(pointnum==3){
            if(isValid(s,startindex,s.size()-1)){//最后一段!
                result.push_back(s);
            }
            return;
        }
        for(int i=startindex;i<s.size();i++){
            if(isValid(s,startindex,i)){
                s.insert(s.begin()+i+1,'.');//加点
                pointnum++;
                backtracking(s,i+2,pointnum);
                pointnum--;
                s.erase(s.begin()+i+1);
            }
            else
                break;
        }
    }

    vector<string> restoreIpAddresses(string s) {
        if(s.size()<=0 || s.size()>12)
            return result;
        backtracking(s,0,0);
        return result;
    }
};


http://www.kler.cn/a/599106.html

相关文章:

  • 2025年如何避免使用验证码求解器时被IP封禁
  • pta 乐子人游戏
  • 意法半导体 (ST) 推出的短距离无线 MCU STM32WBA6----可用于连接工业设备的
  • 练习用Jupyter使用selenium【疑问未解决版】
  • 机器臂运动控制算法工程师面试
  • matrix-breakout-2-morpheus靶机攻略
  • 使用Python将视频转化为gif
  • AI 如何重塑数据湖的未来
  • android 接入google 登录
  • 事务的四大特性(ACID)详解
  • 同旺科技USB to SPI 适配器 ---- 多适配器、多从机混合调试
  • AI安全 | 利用链式思维推理劫持 LRM
  • Vue3中SuspensedefineAsyncComponent异步组件的应用场景。
  • 深度学习优化技术教程
  • CSRF SSRF RCE
  • 卷积神经网络 - 参数学习
  • 服务器中防火墙的重要性
  • 【QA】Qt中直接渲染和离屏渲染有什么区别
  • 爬虫入门re+bs4
  • 构建高效的LinkedIn图像爬取工具