【代码训练营】day 27 | 93.复原IP地址 & 78.子集 & 90.子集II

所用代码 java

复原IP地址 LeetCode 93

题目链接:复原IP地址 LeetCode 93 - 中等

思路

本题主题思路和昨天的切割回文数类似,主要就是看切割点的位置。

class Solution {
    List<Integer> path = new LinkedList<>();
    List<String> res = new ArrayList<>();
    public List<String> restoreIpAddresses(String s) {
        backtracking(s, 0);
        return res;
    }public void backtracking(String s, int startIndex){
        if (path.size() > 4) return;
        // 当切割点位于最后时,代表切割完毕
        // 且ip地址需要4位才算合法ip
        if (startIndex == s.length() && path.size() == 4){
//            System.out.println("path = " + path);
            //拼接
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < path.size()-1; i++) {
                sb.append(path.get(i) + ".");
            }
            // 最后再拼接组后一个数,后面不需要 "."
            sb.append(path.get(path.size()-1));
            res.add(sb.toString());
            return;
        }for (int i = startIndex; i < s.length(); i++) {
            String str = s.substring(startIndex, i+1);
            // 排除首位为0的情况(两位数以上)
            if (str.length()>1 && str.charAt(0) == '0') return;
            int num = Integer.valueOf(str);
            if (num < 0 || num > 255) return;
            path.add(num);
            // 第二个数的切割点时从下一位开始的
            backtracking(s, i + 1);
            path.remove(path.size()-1);
        }
    }
}

总结

本题关键点有几个:

  • 终止条件:startIndex == s.length() && path.size() == 4 不仅要考虑切割点在末尾的情况,还需确定path的元素刚好四位

  • 需要合法ip:

    • 合法情况下的数值:(num < 0 || num > 255)
    • 非合法的数值:str.length()>1 && str.charAt(0) == '0'
  • 切割字符串操作:s.substring(startIndex, i+1) 为左闭右开,起始startIndex是不变的,而终点i是一种变化的。

剪枝操作: 当path.size() > 4 就可以直接返回,该条件可直接写在for循环里面


本题还可以以昨天的题为模板,写出同样的回溯:

  1. 判断切割的数字是否合法 isValid
  2. 合法的情况下字符串添加 ” . “ ,此时起始位置就是i + 2
class Solution {
    List<String> res = new ArrayList<>();
    public List<String> restoreIpAddresses(String s) {
        backtracking(s, 0, 0);
        return res;
    }// pointNum 添加逗号的点
    public void backtracking(String s,int startIndex, int pointNum){
        // 刚好有3个逗号,即分割成了四个数
        if (pointNum == 3){
            if (isValid(s, startIndex, s.length()-1)){
                res.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);
                pointNum++;
                backtracking(s, i + 2, pointNum);
                pointNum--;
                // 回溯的时候还要删除掉逗号
                s = s.substring(0, i+1) + s.substring(i+2);
            }else continue;
        }
    }// 左闭右闭区间判断数字是否合法
    public boolean isValid(String s, int start, int end){
        if (start > end) return false;
        // 首位为 0 ,且传入的不是一位数
        if (s.charAt(start) == '0' && start != end) return false;
        int num = 0;
        for (int i = start; i <= end; i++) {
            // 遇到非数字不合法
            if (s.charAt(i) > '9' || s.charAt(i) < '0') return false;
            // 构建起点为start,终点为end的数字
            num = 10 * num + (s.charAt(i) - '0');
            if (num > 255) return false;
        }
        return true;
    }
}

子集 LeetCode 78

题目链接:子集 LeetCode 78 - 中等

思路

本题为标准的回溯题,可在每次回溯之前收集结果

示意图如下:

27.2.jpg

class Solution {
    List<Integer> path = new LinkedList<>();
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        backtracking(nums, 0);
        return res;
    }public void backtracking(int[] nums, int startIndex){
        // 先收获结果,再进行递归
        res.add(new LinkedList<>(path));
        // 终止条件可不写,因为startIndex >= nums.length就不会进入下面的for循环了
        if (startIndex >= nums.length) return;
        for (int i = startIndex; i < nums.length; i++) {
            path.add(nums[i]);
            backtracking(nums, i+1);
            path.remove(path.size()-1);
        }
        // 该条件也可以省略,因为他直接结束了
        return;
    }
}

总结

本题收获结果是在树的每一个结点,所用我们一开始就可以把结果加进去。

子集II LeetCode 90

题目链接:子集II LeetCode 90 - 中等

思路

本题一看和上题类似,都在所有的结点收集结果,只是多了一个去重操作,但是去重的方式都和昨天的组合总和II LeetCode 40 一模一样。

示意图如下:

27.3.jpg

class Solution {
    List<Integer> path = new LinkedList<>();
    List<List<Integer>> res = new ArrayList<>();
    int[] used;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        used = new int[nums.length];
        // 去重操作得先排序
        Arrays.sort(nums);
        backtracking(nums, 0);
        return res;
    }public void backtracking(int[] nums, int startIndex){
        // 所有树节点都要收集结果
        res.add(new LinkedList<>(path));
        for (int i = startIndex; i < nums.length; i++) {
            // 数层去重操作
            if (i != 0 && nums[i-1] == nums[i] && used[i-1] == 0){
//                System.out.println("used" + Arrays.toString(used));
                continue;
            }
            path.add(nums[i]);
            used[i] = 1;
            backtracking(nums, i + 1);
            // 回溯
            path.remove(path.size()-1);
            used[i] = 0;
        }
    }
}

总结

只要掌握了子集问题,以及组合的去重方式,本题就很简单,因为方法都是类似的,无非就两点:

  1. 结果在树的结点收集
  2. 使用used标记进行数层去重

另外还有一种不使用uesd数组进行去重的方法:

示意图:

27.4.jpg

class Solution {
    List<Integer> path = new LinkedList<>();
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        // 去重操作得先排序
        Arrays.sort(nums);
        backtracking(nums, 0);
        return res;
    }public void backtracking(int[] nums, int startIndex){
        // 所有树节点都要收集结果
        res.add(new LinkedList<>(path));
        for (int i = startIndex; i < nums.length; i++) {
            // 数层去重操作
            if (i > startIndex && nums[i-1] == nums[i]) {
                continue;
            }
            path.add(nums[i]);
            backtracking(nums, i + 1);
            // 回溯
            path.remove(path.size()-1);
        }
    }
}

本方法主要是巧妙借助了startIndex与i的关系,startIndex表示的就是递归的深度(每次递归都会往更深一层探索,即i+1,也就是startIndex+1),也就是树枝,而i表示的是递归的宽度,也就是数层 ,而i > startIndex && nums[i-1] == nums[i] 则表示在同一层里,前后两个数相等的情况

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值