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

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

93.复原IP地址

题目链接

问题简述:为一串数字添加.分割成有效的ip地址,输出所有的可能。

思考:这个感觉和上一道回文串很像,就是判断回文串改为判断数字是否有效。不过还是有很多细节需要注意。

算法思路

  • 定义函数用双指针判断字符串的[left,right]范围数字是否有效,如果长度大于3则无效;如果子串以0开头,且不是单个0,视为无效的IP地址段;将字符转换为数字,如果在[0,255]则有效,否则无效。

  • 定义lists存储所有组合,定义path存储当前分割的数字。startidx为当前指向的字符。

  • 递归函数中如果当前path中数字大于四个则直接返回;如果等于四个,且遍历完了s,则将ip加入lists,加入时用.来间隔。依次从startIdx遍历所有元素,每次判断startIndex到当前i是否有效,如果有效则将这一段加入path,然后继续递归这一段之后的字符串后,进行回溯,即将path最后的数字移除。

import java.util.ArrayList;
import java.util.List;

class Solution {
    //定义返回值
    List<String> lists = new ArrayList<>();
    List<String> path = new ArrayList<>();

    public List<String> restoreIpAddresses(String s) {
        backtracking(s, 0);
        return lists;
    }
    //回溯函数
    public void backtracking(String s, int startidx){
        //如果path中数字个数大于4直接返回
        if (path.size() > 4) return;
        //如果path中数字个数等于4且遍历完了s,则将ip加入lists
        if (path.size() == 4 && startidx >= s.length()){
            StringBuilder sb = new StringBuilder(path.get(0));
            for (int i = 1; i < 4; i++) {
                sb.append('.');
                sb.append(path.get(i));
            }
            lists.add(new String(sb));
        }
        //依次遍历每个元素,判断startidx到当前i元素的数字是否有效
        for (int i = startidx; i < s.length(); i++) {
            if (isValid(s, startidx, i)){
                //如果有效则将数字加入进path,并递归再回溯,substring方法是加入左闭右开的字符
                String str = s.substring(startidx, i + 1);
                path.add(new String(str));
                //递归
                backtracking(s, i + 1);
                //回溯
                path.remove(path.size() - 1);
            }
        }
    }
    //判断是否有效,取left到right的左闭右闭区间
    public boolean isValid(String s, int left, int right){
        // 检查子串长度是否超过合理范围
        if (right - left + 1 > 3) {
            return false;
        }
        // 如果子串以0开头,且不是单个0,视为无效的IP地址段
        if (left != right && s.charAt(left) == '0') {
            return false;
        }try {
            int number = Integer.parseInt(s.substring(left, right + 1));
            return number >= 0 && number <= 255;
        } catch (NumberFormatException e) {
            // 子串不是有效的整数表示
            return false;
        }
    }
}

78.子集

题目链接

问题简述:求出无重复集合的子集。

思考:之前的题都是在递归树的叶子结点保存值,这道题是在所有结点保存值。然后我单独添加了空集,其实也可以在递归内最先添加一下。

算法思路

  • 定义一个startIndex作为每次递归中candidates的起始位置;list存储每个结果集;result存储list的集合。

  • 在主函数加入空集。

  • 每次递归从startIndex将i指向的元素加入list,然后将list加入结果集中。再递归当前i的下一个元素,后进行回溯。

import java.util.ArrayList;
import java.util.List;

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        //加入空集
        result.add(new ArrayList<>(list));
        backtracking(nums, 0);
        return result;
    }

    public void backtracking(int[] nums, int startIdx){
        for (int i = startIdx; i < nums.length; i++) {
            //加入当前元素
            list.add(nums[i]);
            //保留递归树中每个结点的值,即为每个子集
            result.add(new ArrayList<>(list));
            //递归i的下一个元素
            backtracking(nums, i + 1);
            //回溯
            list.remove(list.size() - 1);
        }
    }
}

90.子集II

题目链接

问题简述:求出有重复集合的子集。

思考:这道题关键在于去重,把两道题杂糅起来了。这个题当时有个疑问,我想知道去重那里条件的i>0是为了防止数组下标出现-1对吗,那这里为什么不可以把i>0替换为startidx>0呢,毕竟staetidx是i的起始位置,仍能保证i>0,我就因为这个位置找了好久bug。

  • 答:可能有些时候idx等于0,但是i已经大于0了,这时候该跳过当前元素了,但是用idx>0就不能跳过了。

算法思路

  • 定义一个startIndex作为每次递归中candidates的起始位置;list存储每个结果集;result存储list的集合;定义一个used数组,长度为candidates长度,初始元素为0,used用来标记当前元素是否在path中,如果在的话变为1,进行回溯时变回0。

  • 在主函数加入空集。

  • 每次递归将startIndex指向的元素加入list,先进行去重,判断当前元素是否等于上一个元素,如果等于的话,还要判断used[i - 1]是否等于0,如果不为0说明上一个元素正在当前的list集合中;只有当used[i - 1] == 0时,我们已经确定上一个元素已经不在当前的list集合中,才可以进行去重。然后遍历所有元素,每次加入当前元素,更改used标志位,然后进行递归下一个位置元素,最后进行回溯,更改used,并推出list最后的元素。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        //加入空集
        result.add(new ArrayList<>(list));
        //进行排序为了去重
        Arrays.sort(nums);
        //标记使用
        int[] used = new int[nums.length];
        backtracking(nums, 0, used);
        return result;
    }

    public void backtracking(int[] nums, int startIdx, int[] used){
        for (int i = startIdx; i < nums.length; i++) {
            //去重
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0) continue;
            //加入当前元素
            list.add(nums[i]);
            used[i] = 1;
            //保留递归树中每个结点的值,即为每个子集
            result.add(new ArrayList<>(list));
            //递归i的下一个元素
            backtracking(nums, i + 1, used);
            //回溯
            used[i] = 0;
            list.remove(list.size() - 1);
        }
    }
}

感想

诶,遇到问题想不明白就很难再继续下去,总是要挺一段时间,周围的很多人都找到工作了,虽然不是技术岗吧,我还要继续坚持吗?感觉状态不行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值