【回溯第二篇】用回溯代替暴力破解解决切割问题:用n次递归代替n次for循环(本质)

177 阅读4分钟

文章目录

17.电话号码的字母组合

class Solution {
 
    // 组合问题: 每个子组合不允许重复就是要返回一个组合,因为返回只要求是要给一个List,List不带去重功能,所以要回溯
     List<String> sumResult=new ArrayList<>();
     StringBuilder stringBuilder=new StringBuilder();   //将path变为stringbuilder   path.add就是stringbuilder.append  path.remove(path.size()-1) 就是 stringBuilder.deleteCharAt(stringBuilder.length()-1)

    public  List<String> letterCombinations(String digits) {
        if (digits==null || digits.length()==0) return sumResult; // 返回[]而不是null
        Map<Character,String> digitDic =  getLetter(digits);  // 数据字典,得到n和k,可以开始组合了   n就是digitDic  k就是digitDic.size
        backtracking(digitDic, digits,0);   // k有了  n
        return sumResult;
    }
    public  void backtracking(Map<Character,String> digitDic,String digits,int startIndex){
        if (startIndex==digits.length()){   // digits.length() 就是 k,但是参数中使用digits比k要好,可以digits.charAt,k只能判断完成标志
            sumResult.add(stringBuilder.toString());  // 这里i<path.size 和 i<k 是一样的,因为进入的条件就是   path.size()==k
            return;   // return相当于下面有一个else
        }
        // 从输入的digits中取出的是字符,所以初始化的时候digitDic中应该放入字符,而不是字符串 
        // key为字符,value为字符串,只有key为字符才可以对的上 map.get取得出   则为Map<Character,String>
        
        String letters =  digitDic.get(digits.charAt(startIndex));   // 将输入的digits变为字符串(通过数据字典)
        // 然后遍历字符串
        for (int i=0;i< letters.length();i++){  // 从0开始,到一个数字对应的字符为止
            stringBuilder.append(letters.charAt(i));  // 每个字符遍历为止
            backtracking(digitDic,digits,startIndex+1);   // 因为是组合,这里是startIndex+1,深入下一个字符串
            stringBuilder.deleteCharAt(stringBuilder.length()-1);
        }
    }

    public  Map<Character,String> getLetter(String digits){
        char[] charArray = digits.toCharArray();  // k 就是 charArray.Length   k就是每个子组合的元素个数,就是for循环层数,就是递归层数,要随时减去,要回溯
        // n 就是多个大组合,getLetter中要返回这个大组合
        Map<Character,String> result = new HashMap<>();
        for(int i=0;i<charArray.length;i++){
            if (charArray[i] == '2') result.put('2',"abc");
            else   if (charArray[i] == '3') result.put('3',"def");
            else   if (charArray[i] == '4') result.put('4',"ghi");
            else   if (charArray[i] == '5') result.put('5',"jkl");
            else   if (charArray[i] == '6') result.put('6',"mno");
            else   if (charArray[i] == '7') result.put('7',"pqrs");
            else    if (charArray[i] == '8') result.put('8',"tuv");
            else    if (charArray[i] == '9') result.put('9',"wxyz");
        }
        return result;
    }
}

整个解法分为两部分,在Solution主函数中,
第一,先调用getLetter(digits);函数建立一个Map<Character,String>类型的数据字典;
第二,调用 backtracking 回溯函数

对于回溯函数:三个参数,Map<Character,String> digitDic 是传递过来的数据字典,先将数据放到了数据字典里面,然后递归的时候要取出来的,所以传递过来,其实,在递归函数中没有任何地方改变过这个数据字典,仅仅取出,所以可以作为类变量。
String digits 是刚开始的输入字符串,递归的时候要取出来的,所以传递过来,其实,在递归函数中没有任何地方改变过这个数据字典,仅仅取出,必须作为参数,无法作为类变量,因为这是Solution类的主函数的函数,只能传递
int startIndex 这个每次递归累加,必须作为参数了

对于for循环中,
stringBuilder.append(letters.charAt(i)); // 每个字符遍历为止
backtracking(digitDic,digits,startIndex+1); // 因为是组合,这里是startIndex+1,深入下一个字符串

对于两个类变量,
第一,sumResult是要作为Solution主函数的最终return返回值的,所以必须和题目要求的返回类型相同,所以是 List<String>
第二,stringBuilder是取代之前的List<String>类型的path变量的,是要放到sumResult里面的,因为是最后的返回值要求是[“ab”,“ac”]这种类型,所以用StringBuilder类型取代List<String>类型。

分割类型(叶子节点):131.分割回文串

分割问题其实可以抽象为组合问题!

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

示例:
输入: “aab”
输出:
[
[“aa”,“b”],
[“a”,“a”,“b”]
]

class Solution {
    List<List<String>> result=new ArrayList<>();
    List<String> path =new ArrayList<>();
    public void backtracking(String s,int startIndex){
        if (startIndex >= s.length()){  // 游标已经到了最后一个了
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i=startIndex;i<s.length();i++){  // 这里每次不是从0开始,而是从startIndex开始,否则 i+1 传递过来根本就起不到作用
            if (isVerdict(s,startIndex,i)){
                // subting是前闭后开,所以要i+1,才能取到第i个
                path.add(s.substring(startIndex,i+1));  // 如果从startIndex到i,是一个回文,就放到path里面吧
            }else{
                continue;
            }
            backtracking(s,i+1);  // 每次要移动到后面的一个
            path.remove(path.size()-1);
        }
    }
    public boolean isVerdict(String s,int start,int end){
        for (int i=start,j=end;i<j;i++,j--) {    // 这里只能是 i<j,可以满足s长度为奇数或偶数,不能是i<=j(等于也比较),
            if (s.charAt(i)!=s.charAt(j)){
                return false;  // 不相等,返回false
            }
        }
        return true;  //最后return true
    }
    public List<List<String>> partition(String s) {
        backtracking(s,0);  // 给定的大组合是一个集合或者字符串,所以要从小标0开始,如果给定一个n,就是从0到n-1
        return result;
    }
}

分割类型(叶子节点):93.复原IP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

有效的 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 地址。

示例 1:
输入:s = “25525511135”
输出:[“255.255.11.135”,“255.255.111.35”]

示例 2:
输入:s = “0000”
输出:[“0.0.0.0”]

示例 3:输入:s = “1111”
输出:[“1.1.1.1”]

示例 4:输入:s = “010010”
输出:[“0.10.0.10”,“0.100.1.0”]

示例 5:输入:s = “101023”
输出:[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“101.0.2.3”]

提示:
0 <= s.length <= 3000
s 仅由数字组成

class Solution {
    List<String> result = new ArrayList<>();
    public void backtracking(StringBuilder stringBuilder,int startIndex,int pointNum){
        if (pointNum==3){
            if (isVerdict(stringBuilder,startIndex,stringBuilder.length()-1)){  // 因为isVerdict方法里面,是左闭右闭的  所以这里传入原字符串s 以及 0 - n-1
                result.add(stringBuilder.toString());   // 当前判定当前的s的分割是合法的ip,就放到result里面去     这里不用path变量了,因为下面的for循环就是修改s字符串的
            }
            // 满足两个条件(结束条件+成功条件=成功结束条件)就添加到result里面去
            // 仅仅满足结束条件,直接返回
            return;
        }
        // 这里从starrtIndex开始,不能从0开始,否则上一次i+2传递过来就没用了
        for(int i=startIndex;i<stringBuilder.length();i++){  
            if (isVerdict(stringBuilder,startIndex,i)){  // 从startIndex到i,左闭右闭区间
            // 如果现在的划分是合法的,插入点号了
                stringBuilder.insert(i+1,'.');
                pointNum++;

                backtracking(stringBuilder,i+2,pointNum);  // 插入点号之后,想要后面那个,就是i+2位置了
stringBuilder.deleteCharAt(i+1);   // 删掉掉i+1号位置,就是删除掉这个点号
                pointNum--;
            }else {
                break;
            }
        }
    }
    private boolean isVerdict(StringBuilder s,int startIndex,int endIndex){
        if (startIndex > endIndex){
            return false;
        }
        if (s.charAt(startIndex) == '0' && startIndex!=endIndex){  //
           return false;
        }
        int sum = 0;
        for (int i=startIndex;i<=endIndex;i++){  // 左闭右闭都要
            if (s.charAt(i) < '0' || s.charAt(i)>'9'){
                return false;  // 不是正整数去掉
            }
            sum = sum * 10 + (s.charAt(i) - '0');
            if (sum > 255){
                return false;  // 字符大于255 去掉
            }
        }
        return true;
    }
    public List<String> restoreIpAddresses(String s) {
        StringBuilder stringBuilder=new StringBuilder(s);
        backtracking(stringBuilder,0,0);
        return result;
    }
}