Day30~93.复原IP地址、78.子集、90.子集II

81 阅读3分钟

摘要

本文主要介绍了LeetCode回溯算法的几个题目,包括93.复原IP地址、78.子集、90.子集II。

1、93.复原IP地址

1.1 思路

40.组合总和II

  • 同131.分割回文串,回溯+判断IP地址是否正确

  • 字符串 s 的处理

    • 在str的后⾯插⼊⼀个逗点 s = s.substring(0, i + 1) + "." + s.substring(i + 1)
    • 插⼊逗点之后下⼀个⼦串的起始位置为i+2
    • 回溯删掉逗点 s = s.substring(0, i + 1) + s.substring(i + 2)
  • 判断子串是否合法

    • 段位以0为开头的数字不合法
    • 段位里有非正整数字符不合法
    • 段位如果大于255了不合法

1.2 代码

    // 同131.分割回文串,回溯+判断IP地址是否正确
    public List<String> restoreIpAddresses(String s) {
        List<String> list = new ArrayList<>();
        StringBuilder builder = new StringBuilder();
        doRestoreIpAddresses(list, s, 0, 0);
        return list;
    }
​
    public void doRestoreIpAddresses(List<String> list, String s, int start, int point) {
        if(point == 3) {
            if(isValid(s, start, s.length()-1)) {
                list.add(s);
            }
            return;
        }
        
        for(int i=start; i<s.length(); i++) {
            if(!isValid(s, start, i)) {
                break;
            }
            s = s.substring(0, i+1) + '.' + s.substring(i+1);
            
            doRestoreIpAddresses(list, s, i+2, point+1);
​
            s = s.substring(0, i + 1) + s.substring(i + 2);
        }
    }
​
    // 判断字符串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) { // 如果⼤于255了不合法
                return false;
            }
        }
        return true;
    }

2、78.子集

2.1 思路

  • 首先将当前子集current添加到结果集中,然后从start索引开始遍历数组nums,依次将元素加入到current中,递归地探索下一个元素。
  • 完成递归后,我们再回溯,移除最后一个元素,以便继续探索其他可能的子集。
  • 这个算法的关键在于在每个步骤都将当前子集添加到结果中,确保所有子集都会被包括进来。
  • 最终,结果集中将包含所有可能的子集。

2.2 代码

    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> list = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        doSubsets(list, path, nums, 0);
        return list;
    }
​
    public void doSubsets(List<List<Integer>> list, LinkedList<Integer> path, int[] nums, int start) {
        list.add(new ArrayList<>(path));
​
        for(int i=start; i<nums.length; i++) {
            path.add(nums[i]);
            
            doSubsets(list, path, nums, i+1);
            
            path.removeLast();
        }
    }

3、90.子集II

3.1 思路

  • 去重:同40.组合总和II
  • 回溯:同78.子集

3.2 代码

    // 同40.组合总和II
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> list = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        //为了将重复的数字都放到一起,所以先进行排序
        Arrays.sort(nums);
        doSubsetsWithDup(list, path, nums, 0);
        return list;
    }
​
    public void doSubsetsWithDup(List<List<Integer>> list, LinkedList<Integer> path, int[] nums, int start) {
        list.add(new ArrayList<>(path));
​
        for(int i=start; i<nums.length; i++) {
            //正确剔除重复解的办法
            //跳过同一树层使用过的元素
            if ( i > start && nums[i]==nums[i - 1] ) {
                continue;
            }
​
            path.add(nums[i]);
            
            doSubsetsWithDup(list, path, nums, i+1);
            
            path.removeLast();
        }
    }

附录

组合问题的特点

【回溯】93复原IP地址、78子集、90子集II - 昨夜西风凋碧树的文章 - 知乎

关于组合问题,我发现有这么几个特点:

1,需要求解的组合,通常会要求“不重复”。如何做到?

(1)如果区间本身就是不重复的,那么只要每次跳到第二个数字就可以。

(2)如果区间里有重复数字,那么就要先排序,然后在树层进行去重。有used数组和start_index两种方法。

2,单个组合里,一个数字能不能使用多次?

(1)如果一个数字可以使用多次,递归的时候,index就不用更新。

(2)如果一个数字不可以使用多次,递归的时候,index一定要更新。

凡是组合问题,先搞清楚这两个问题。

参考资料

代码随想录-93.复原IP地址

代码随想录-78.子集

代码随想录-90.子集II