回溯模板题

题目:

131. 分割回文串

难度中等

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

回文串 是正着读和反着读都一样的字符串。

示例 1:

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

示例 2:

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

提示:

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

首先写下回溯模板:

class Solution {
    public List<List<String>> partition(String s) {
        List<List<String>> res = new ArrayList<>();
        backroll();
        return res;
    }
    void backroll(int start,List<List<String>> result){
        if(){
            result.add();
            return;
        }
        for(){
            //操作
            //递归
            //撤销操作
        }
    }
}

看题目,要求分割回文串的方案,如何判断一个字符串是否为回文串?

这个回文串是镶嵌在for()里面的,需要循环很多次,所以不能每次都调用一个函数单独判断,所以要用到动态规划,也就是dp[i][j],表示s[i,j]是否是回文串。注意条件也即状态转移方程:s.charAt(i) == s.charAt(j) && (j-i <= 2 || dp[i+1][j-1] == 1的时候,dp[i][j]就为1(为回文串),j-i <= 2表示当前区间除去最外层的i和j,只有一个字符,当然是回文串了,如aba。

	    int len = s.length();
        int[][] dp = new int[len][len];
        for(int right = 0;right < len;right++){
            for(int left  = 0;left <= right;left++){
                if(s.charAt(left) == s.charAt(right) && (right-left <= 2 || dp[left+1][right-1] == 1)){
                    dp[left][right] = 1;
                }
            }
        }

上面还有个模板是让一个区间在数组里面遍历的办法:先right再left,并且取名不容易搞混。left <= right是为了让一个字符也能进入回文串判断逻辑,毕竟有right-left <= 2可以让它过。

剩下就是模板具体化:

class Solution {
    public List<List<String>> partition(String s) {
        //不用动态规划的话,回溯判断回文串有点难受,所以要动规减少回文串判断的时间复杂度
        List<List<String>> res = new ArrayList<>();
        int len = s.length();
        int[][] dp = new int[len][len];
        for(int right = 0;right < len;right++){
            for(int left  = 0;left <= right;left++){
                if(s.charAt(left) == s.charAt(right) && (right-left <= 2 || dp[left+1][right-1] == 1)){
                    dp[left][right] = 1;
                }
            }
        }
        backroll(0,res,new ArrayList<>(),s,dp);
        return res;
    }
    void backroll(int start,List<List<String>> result,List<String> templist,String s,int[][] dp){
        if(start == s.length()){
            result.add(new ArrayList<>(templist));
            return;
        }
        for(int i = start;i < s.length();i++){
            if(dp[start][i] == 1){
                templist.add(s.substring(start,i+1));
                backroll(i+1,result,templist,s,dp);
                templist.remove(templist.size()-1);
            }
        }
    }
}

最后是啰嗦注释版:

class Solution {
    public List<List<String>> partition(String s) {
        List<List<String>> result =  new ArrayList<>();
        int len = s.length();
        //dp
        int[][] dp = new int[len][len];
        for(int right = 0;right < len;right++){
            for(int left = 0;left <= right;left++){
                if(s.charAt(left) == s.charAt(right) && (right - left <= 2 || dp[left+1][right-1] == 1)){
                    dp[left][right] = 1;
                }
            }
        }
        backroll(0,result,new ArrayList<>(),s,dp);
        return result;
    }
    void backroll(int start,List<List<String>> result,List<String> templist,String s,int[][] dp){
        if(start == s.length()){//一轮遍历已经过了,一组方案就结束了,如果没有一组符合就会add []进去(实际不可能,毕竟'a'也是回文串,而且题目说了1<=s.length不会为空字符串)
            result.add(new ArrayList<>(templist));
            return;
        }
        for(int i = start;i < s.length();i++){//因为你每次都不能从头遍历,需要接着上一次递归的地方往后遍历寻找回文串,所以i=start
            if(dp[start][i] == 1){//只有回文串才有资格进入我们的方案,这里也可以调用判断s.substring(start,i+1)是否为回文串,但是这样复杂度是一个0(n),递归本身很吃内存,然后又是O(N^2)时间复杂度......稍显不妥,所以需要dp优化,也即查表法。
                templist.add(s.substring(start,i+1));//substring(i,j)是截取s的[i,j)区间!
                backroll(i+1,result,templist,s,dp);
                templist.remove(templist.size()-1);//回去分支做选择
            }
        }
    }
}
posted @ 2021-04-14 22:36  0errorstartsuccess  阅读(45)  评论(0)    收藏  举报