代码随想录算法训练营第28天|93. 复原IP地址、78.子集、90. 子集II

93. 复原IP地址

  • 递归参数
    startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。本题我们还需要一个变量pointNum,记录添加逗点的数量。

  • 递归终止条件
    pointNum表示逗点数量,pointNum为3说明字符串分成了4段了。然后验证一下第四段是否合法,如果合法就加入到结果集里。

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

    递归调用时,下一层递归的startIndex要从i+2开始(因为需要在字符串中加入了分隔符.),同时记录分割符的数量pointNum 要 +1。
    代码随想录算法训练营第28天|93. 复原IP地址、78.子集、90. 子集II_第1张图片

class Solution {
    List<String> result = new ArrayList<>();
    public List<String> restoreIpAddresses(String s) {
        if (s.length() > 12) return result; // 算是剪枝了
        backTracking(s, 0, 0);
        return result;
    }

    // startIndex:搜索的起始位置, pointNum:添加逗点的数量
    private void backTracking(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++;
                backTracking(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){
                return false;
            }
        }
        return true;
    }
}

78. 子集

如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!

递归终止条件是startIndex已经大于数组的长度了,就终止了,因为没有元素可取了。求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树。
代码随想录算法训练营第28天|93. 复原IP地址、78.子集、90. 子集II_第2张图片

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> subsets(int[] nums) {
        backTracking(nums, 0);
        return result;
    }

    private void backTracking(int[] nums, int startIndex){
        result.add(new ArrayList<>(path));//「遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合」。
        if (startIndex >= nums.length){ //终止条件可不加
            return;
        }
        for (int i = startIndex; i < nums.length; i++){
            path.add(nums[i]);
            backTracking(nums, i + 1);
            path.removeLast();
        }
    }
}

90. 子集II

集合里有重复元素了,而且求取的子集要去重。

从图中可以看出,同一树层上重复取2 就要过滤掉,同一树枝上就可以重复取2,因为同一树枝上元素的集合才是唯一子集!
代码随想录算法训练营第28天|93. 复原IP地址、78.子集、90. 子集II_第3张图片

class Solution {
    List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
    LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort( nums );
        subsetsWithDupHelper(nums, 0);
        return result;
    }

    private void subsetsWithDupHelper( int[] nums, int start ) {
        result.add(new ArrayList<>(path) );

        for ( int i = start; i < nums.length; i++ ) {
            // 跳过当前树层使用过的、相同的元素
            if ( i > start && nums[i - 1] == nums[i] ) {
                continue;
            }
            path.add( nums[i] );
            subsetsWithDupHelper( nums, i + 1 );
            path.removeLast();
        }
    }
}

今日总结

回溯法自我看来是由模板的,唯一需要考虑的是是否有组合问题之类的重复取元素的问题(startIndex)和递归的终止条件,如果再加一条就是检验子集是否有重复元素需要去重的问题。打卡群里的打卡人数越来越少了,day30开始复习了,虽然知道自己也没有思考得多认真吧,还是继续坚持下去,加油!

你可能感兴趣的:(算法,数据结构,leetcode)