代码随想录-回溯算法-分割问题(Java)

文章详细介绍了如何使用回溯算法来解决131.分割回文串和93.复原IP地址这两道编程题目。在分割回文串的问题中,重点在于理解切割问题和回溯搜索的过程,通过递归和for循环构建树形结构进行搜索。而在复原IP地址问题中,关键在于判断每段是否为有效的IP段,并限制分割次数为4次。两种问题都应用了回溯法来寻找所有可能的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分割回文串

链接: 131.分割回文串

思路

本题涉及两个关键问题:

  1. 切割问题,有不同的切割方式
  2. 回文问题

切割问题类似组合问题

例如对于字符串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] 就是要截取的子串。

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

    代码如下:

    for (int i = startIndex; i < s.size(); i++) {
        if (isPalindrome(s, startIndex, i)) { // 是回文子串
            // 获取[startIndex,i]在s中的子串
            string str = s.substr(startIndex, i - startIndex + 1);
            path.push_back(str);
        } else {                // 如果不是则直接跳过
            continue;
        }
        backtracking(s, i + 1); // 寻找i+1为起始位置的子串
        path.pop_back();        // 回溯过程,弹出本次已经填在的子串
    }
    

    注意切割过的位置,不能重复切割,所以,backtracking(s, i + 1); 传入下一层的起始位置为i + 1

代码

class Solution {
    List<String> path = new ArrayList<>();
    List<List<String>> results = new ArrayList<>();
    void backTracking(String s,int startIndex){
        if (startIndex >= s.length()){
            results.add(new ArrayList<>(path));
            return;
        }
        for (int i = startIndex;i < s.length();i++){
            if (isPalindrome(s,startIndex,i)){
                //左闭右开区间
                path.add(s.substring(startIndex,i + 1));
            }else continue;
            backTracking(s,i + 1);
            path.remove(path.size() - 1);
        }
    }
    boolean isPalindrome(String s,int left,int right){
        if (left > right)   return false;
        while (left < right){
            if (s.charAt(left) != s.charAt(right))  return false;
            left++;
            right--;
        }
        return true;
    }
    public List<List<String>> partition(String s) {
        backTracking(s,0);
        return results;
    }
}

复原IP地址

链接: 93.复原IP地址

思路

[我自己的思路,想了很多,但很混乱,思路清晰比得上方法简便]

切割问题都可以使用回溯搜索法把所有可能性搜出来

切割问题可以抽象为树形结构
在这里插入图片描述
【先自己写出来一下子,边写边找思路】

回溯三部曲

  • 递归参数

    startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。

    本题我们还需要一个变量pointNum,记录添加逗点的数量。

    代码如下:

        List<String> results = new ArrayList<>();
        void backTracking(String s,int startIndex,int pointSum){
        }
    
  • 终止条件

    终止条件和131.分割回文串 (opens new window)情况就不同了,本题明确要求只会分成4段,所以不能用切割线切到最后作为终止条件,而是分割的段数作为终止条件。

    pointNum表示逗点数量,pointNum为3说明字符串分成了4段了。

    然后验证一下第四段是否合法,如果合法就加入到结果集里

    代码如下:

if (pointSum == 3){
    if (isValid(s,startIndex,s.size() - 1)){
        results.add(s); 
    }
    return;
}
  • 单层搜索的逻辑

    131.分割回文串 (opens new window)中已经讲过在循环遍历中如何截取子串。

    for (int i = startIndex; i < s.size(); i++)循环中 [startIndex, i] 这个区间就是截取的子串,需要判断这个子串是否合法。

    如果合法就在字符串后面加上符号.表示已经分割。

    如果不合法就结束本层循环,如图中剪掉的分支:

在这里插入图片描述
然后就是递归和回溯的过程:

递归调用时,下一层递归的startIndex要从i+2开始(因为需要在字符串中加入了分隔符.),同时记录分割符的数量pointNum 要 +1。

回溯的时候,就将刚刚加入的分隔符. 删掉就可以了,pointNum也要-1。

判断子串是否合法

最后就是在写一个判断段位是否是有效段位了。

主要考虑到如下三点:

  • 段位以0为开头的数字不合法
  • 段位里有非正整数字符不合法
  • 段位如果大于255了不合法

代码

class Solution {
    List<String> result = new ArrayList<>();

    public List<String> restoreIpAddresses(String s) {
        if (s.length() > 12) return result; // 算是剪枝了
        backTrack(s, 0, 0);
        return result;
    }

    // startIndex: 搜索的起始位置, pointNum:添加逗点的数量
    private void backTrack(String s, int startIndex, int pointNum) {
        if (pointNum == 3) {// 逗点数量为3时,分隔结束
            // 判断第四段⼦字符串是否合法,如果合法就放进result中
            if (isValid(s,startIndex,s.length()-1)) {
                result.add(s);
            }
            return;
        }
        for (int i = startIndex; i < s.length(); i++) {
            if (isValid(s, startIndex, i)) {
                s = s.substring(0, i + 1) + "." + s.substring(i + 1);    //在str的后⾯插⼊⼀个逗点
                pointNum++;
                backTrack(s, i + 2, pointNum);// 插⼊逗点之后下⼀个⼦串的起始位置为i+2
                pointNum--;// 回溯
                s = s.substring(0, i + 1) + s.substring(i + 2);// 回溯删掉逗点
            } else {
                break;
            }
        }
    }

    // 判断字符串s在左闭⼜闭区间[start, end]所组成的数字是否合法
    private Boolean isValid(String s, int start, int end) {
        if (start > end) {
            return false;
        }
        if (s.charAt(start) == '0' && start != end) { // 0开头的数字不合法
            return false;
        }
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s.charAt(i) > '9' || s.charAt(i) < '0') { // 遇到⾮数字字符不合法
                return false;
            }
            num = num * 10 + (s.charAt(i) - '0');
            if (num > 255) { // 如果⼤于255了不合法
                return false;
            }
        }
        return true;
    }
}

重新敲一遍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值