【力扣一刷】代码随想录day28(回溯算法part4:93.复原IP地址、78.子集、90.子集II )

目录

【93.复原IP地址】中等题(偏难,坑很多)

【78.子集】中等题(偏简单)

【90.子集II】中等题


【93.复原IP地址】中等题(偏难,坑很多)

思路:以101023为例子

1、将题目抽象成树,子节点为指定开始索引start的子串,在遍历子节点的时候进行剪枝(打叉的地方表示剪枝),保证子节点合法

2、利用len指示IP的长度,如果已经够4个IP段,则处理结果。(图中是圆圈标注的是处理的结果,×表示长度够了,但是数组中数字没用完,不需要记录结果;√ 表示长度够了,数组中数字也用完了,得记录结果)

关键:重点考虑三个问题

  • 剪枝1:如果当前子节点>255怎么办?当前及剩下的子节点不需要遍历,使用break跳出for循环
  • 剪枝2:如果剩下的数字不足以构成4个IP段怎么办?当前及剩下子节点不需要遍历,使用break跳出for循环
  • 剪枝3:如果当前子节点以0开头怎么办?只递归遍历单独"0"这个子串的分支,​遍历完后直接return,不再遍历剩下的"0x"不合法的IP段。​​​​​​(图中的方框处就是遍历单独的"0")
class Solution {
    List<String> res = new ArrayList<>();
    StringBuffer path = new StringBuffer();
    public List<String> restoreIpAddresses(String s) {
        backtracking(s, 0, 0);
        return res;
    }

    // start是指当前层的所有子节点对应子串在数组中的开始索引
    // len是指当前层对应的IP段索引,从0开始,0-3对应4个IP段,当len - 4时,说明已经够4个IP段了,得考虑是否记录结果了
    public void backtracking(String s, int start, int len){
        // 终止条件(长度够了,判断 -> 数字用完了/数字没用完)
        if (len == 4){
            // 数组中的数字没用完
            if (start < s.length()) return; // 不记录,直接返回
            // 数组中的数字用完了
            else{
                // 这里不能直接删除path最后一个".",否则回溯恢复现场的时候会出错
                String ss = path.toString();
                res.add(ss.substring(0, ss.length() - 1));  // 把最后一个"."删除,再记录结果
                return;
            }
        }

        // 单层递归逻辑
        // 遍历所有子节点(左闭右闭,i指示的是子串的结束索引,包含i)
        for (int i = start; i < s.length(); i++){
            String sub = s.substring(start, i+1); // 取子串是左闭右开

            // 剪枝1:使IP有效(如果当前子节点超过255,就不需要再遍历剩下的子节点)
            if (Integer.valueOf(sub) > 255) break;  // 注意是break,不是continue

            // 剪枝2:加速遍历(如果剩下的数字不足以构建剩下的IP段(path需要4个IP段),则不再遍历剩下的子节点)
            if (s.length() - i - 1 < 4 - len - 1) break;

            path.append(sub + ".");
            backtracking(s, i+1, len+1); // 当前子串i结束,那下个子串就从i+1开始
            path.delete(path.length() - sub.length() - 1, path.length()); // 把子串和"."一起删除

            // 剪枝3:使IP有效(可以是单独的0,但是不能是以0开头的其它整数)
            if (sub.startsWith("0")) return; // 如果字符串的开头是"0",只需要把"0"的分支遍历完即可返回,其余的不合法
        }
    }

}

总结:

1、String类的常用方法

2、StringBuffer的常用方法

补充刷题常用的方法:

  • 删除指定索引的字符:deleteCharAt(int index);
  • 删除指定索引范围的字符串:delete(int start, int end);


【78.子集】中等题(偏简单)

思路:递归前在结果集中加入空集,递归过程中遍历的每个子节点都要把路径记录到结果集

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        res.add(new ArrayList(path)); // 加入空集
        backtracking(nums, 0);
        return res;
    }
    public void backtracking(int[] nums, int start){
        for (int i = start; i < nums.length; i++){
            path.add(nums[i]);
            res.add(new ArrayList(path));  // 每个子节点都需要记录路径
            backtracking(nums, i + 1);
            path.remove(path.size() - 1);
        }
    }
}


【90.子集II】中等题

思路:【78.子集】的区别在于,元素可以在数组中重复出现,需要先排序,后去重。

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);  // 先对nums进行排序,便于后序的去重
        backtracking(nums, 0);  // 不需要再单独加入空集,因为一进入递归,当前节点对应路径就是空集
        return res;

    }

    public void backtracking(int[] nums, int start){
        // 记录当前节点对应的路径
        res.add(new ArrayList(path));
        for (int i = start; i < nums.length; i++){
            // 去重:如果当前层前面已经遍历过相同的子节点,就不再记录当前子节点对应路径和递归遍历当前子节点
            if (i > start && nums[i] == nums[i - 1]) continue;  // 注意是i > start,而不是i>0,i>0不能代表同一层的子节点
            
            path.add(nums[i]);
            backtracking(nums, i + 1);
            path.remove(path.size() - 1);
        }
    }
}

注意:去重的时候,是判断当前子节点在当前层前面是否已经遍历过,是 i > start, 而不是 i > 0。i > start才能保证判断的是当前层的第二个节点,而当前层的第一个节点也可能符合 i > 0,万一该节点等于数组中上一个节点的值,则该节点就无法遍历了。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值