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

95 阅读5分钟

93. 复原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 地址。

给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

93. 复原 IP 地址 - 力扣(Leetcode)

思路

题干所说可以在" s 中插入 '.' 来形成有效的IP地址",

  1. 由于java语言的特性,在字符串中插入或删除一个字符的开销比较大,
    1. String是不可变的,每一次的插入和删除是都新new一个String,再将插入或删除的结果复制到新的字符串中,时间和空间开销都大。
    2. 即便使用String创建一个可变字符串StringBuilderStringBuffer,这两者的底层实现中仍然使用的是数组,插入或删除的时间复杂度都是 o(n)o(n)
  2. 对原字符串进行插入或删除等改动会导致下标的计算比较麻烦,

因此,我们可以不使用题目提供的方法。

  • 使用列表dotList按顺序记录插入字符串的点的下标,这样就无需对字符串进行改动。如,dotList.get(0)1时,表明在字符串下标为1的字符的后面插入第一个.,那么该字符串的形成的IP地址的第一段就是s[0, 1]
  • 以此类推,IP地址每一段如下:
    • 第一段:s[0, dotList.chatAt(0)]
    • 第二段:s[dotList.get(0) + 1, dotList.get(1)]
    • 第三段:s[dotList.get(1) + 1, dotList.get(2)]
    • 第四段:s[dotList.get(2) + 1, s.length() - 1]
    • 以上区间都是左闭右闭区间
  • 使用startIndex记录待分割字符串的起始下标,即待分割字符串为s[startIndex, s.length() - 1]
  • 那么,在初始条件中,startIndex的值为0
  • 在分割时,判断分割出来的字符是否符合标准有 2 个条件:
    • 是否前前置0;
    • 是否大小在[0, 255]内。
  • 因此,在一切割中,可以分为以下两个步骤:
    • 判断s[startIndex]是否为0
    • 如果是,则此次分割结果就是子串s[startIndex, startIndex],进行下一轮分割。
    • 否则,使用i标识分割结束位置,分割的子串为s[startIndex, i]
    • 注意,由于有效IP地址的每一段的数值小于256,即每一段长度小于等于3,因此,要满足条件 i < startIndex + 3 ,这样一方面可以剪枝,另一方面,可以避免我们调用的方法Integer.valueOf(String)出现异常
    • 然后,判断该子串形成的数字是否在有效范围内。若在,则将i添加到dotList中,继续进行分割字符串的操作;否则,当前分割方法不可行,不再继续做其他操作。
  • dotList中有 3 个元素时,即为字符串中添加了三个.了,第四段就自然形成了,由之前的步骤验证了前三段是有效的,此时要先验证第四段是否有效,有以下步骤:
    • 先获得第四段的长度 len
      • 如果 len 等于 0 或者 大于 3,则一定不符合要求;
      • 否则, 获取第四段的数字num
    • 再判断是否len > 1的同时第一个字符是'0',这意味着第四段有前置 00,不符合要求。
    • 然后判断num是否在有效范围,若在有效范围内,通过sdotList拼接字符串,再添加到结果集中。

代码

class Solution {
    public List<String> restoreIpAddresses(String s) {

        if(s.length() < 4){
            return new ArrayList<String>();
        }

        List<Integer> dotList = new ArrayList<>();
        List<String> res = new ArrayList<>();

        backTracking(s, dotList, 0, res);

        return res;

    }

    // dotList将 s 分成 3 段
    // [0, dotList.chatAt(0)]
    // [dotList.get(0) + 1, dotList.get(1)]
    // [dotList.get(1) + 1, dotList.get(2)]
    // [dotList.get(2) + 1, s.length() - 1]
    private void backTracking(String s, List<Integer> dotList, int startIndex,List<String> res){

        if(dotList.size() == 3){
            // 前三段分割完成
            // 判断最后一段 [dotList.get(2) + 1, s.length() - 1] 是否满足条件
            int len = s.length() - startIndex;

            // 无法分割出最后一段
            // 最后一段明显超出255
            // 加入 len > 3 ,避免转换异常
            if(len == 0 || len > 3){
                return;
            }

            int num = Integer.valueOf(s.subSequence(startIndex, s.length()).toString());
            if((len > 1 && s.charAt(startIndex) == '0') || num > 255){
                return;
            }
            
            // 整个字符串分割完成
            // 每个字段在分割过程中完成验证
            // 根据 s 和 dotList 拼接字符串
            StringBuilder sb = new StringBuilder();
            // subSequence : [a, b) 左闭右开区间
            sb.append(s.subSequence(0, dotList.get(0) + 1));
            sb.append(".");
            sb.append(s.subSequence(dotList.get(0) + 1, dotList.get(1) + 1));
            sb.append(".");
            sb.append(s.subSequence(dotList.get(1) + 1, dotList.get(2) + 1));
            sb.append(".");
            sb.append(s.subSequence(dotList.get(2) + 1, s.length()));

            // 加入结果集
            res.add(new String(sb.toString()));
        
            return;
        }

        // 待分割字符串下标为[startIndex, s.length() - 1]
        // 此处将字符串分割成 s[startIndex, i]
        for(int i = startIndex; i < s.length() && i < startIndex + 3; i++){
            if(i == startIndex && s.charAt(i) == '0'){
                // 如果首字符为0,必须直接分割
                dotList.add(startIndex);
                backTracking(s, dotList, startIndex + 1, res);
                dotList.remove(dotList.size() - 1);
                break;
            }

            // 判断s[startIndex, i]是否有效
            Integer num = Integer.valueOf(s.subSequence(startIndex, i + 1).toString());
            if(num <= 255){
                // 有效
                dotList.add(i);
                backTracking(s, dotList, i + 1, res);
                dotList.remove(dotList.size() - 1);
            }

            // 无效,直接剪枝
        }


    }
}

78.子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同

78. 子集 - 力扣(Leetcode)

思路

本题要求给定数组nums的子集,子集出现在数组枚举的过程中,即每枚举一种数组元素的选择,就产生一种子集。
为了不出现重复的子集,只要要求每次取值都在当前取值元素后面的元素中选择即可。

代码

class Solution {
    public List<List<Integer>> subsets(int[] nums) {

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

        res.add(new ArrayList<Integer>());

        backTracking(nums, 0, path, res);

        return res;
        

    }

    private void backTracking(int[] nums, int startIndex, List<Integer> path, List<List<Integer>> res){
        if(startIndex == nums.length){
            return;
        }

        for(int i = startIndex; i < nums.length; i++){
            path.add(nums[i]);
            res.add(new ArrayList<Integer>(path));
            backTracking(nums, i + 1, path, res);
            path.remove(path.size() - 1);
        }
    }
}

90.子集II

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

90. 子集 II - 力扣(Leetcode)

思路

本题题干与第40题给出的条件相同,数组元素可重复且每个元素只能使用 1 次,所求的与78.子集相同,因此,

  • 去重的方法与第40题相同,
  • 获取结果的方法与第78题相同。

代码

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {

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

        res.add(new ArrayList<Integer>());

        Arrays.sort(nums);

        backTracking(nums, 0, path, res);

        return res;

    }

    private void backTracking(int[] nums, int startIndex, List<Integer> path, List<List<Integer>> res){

        if(startIndex == nums.length){
            return;
        }

        for(int i = startIndex; i < nums.length; i++){
            // 当前元素与前一元素相同
            // 如果进行相同操作,产生同样的结果
            // 需要去重
            if(startIndex < i && nums[i] == nums[i - 1]){
                continue;
            }

            path.add(nums[i]);
            res.add(List.copyOf(path));
            backTracking(nums, i + 1, path, res);
            path.remove(path.size() - 1);

        }
    }
}