93.复原IP地址
题目链接:93.复原IP地址
文档讲解:代码随想录
状态:还行,判断字符是否有效卡了好久
思路:通过回溯算法遍历所有可能的分割方式,每个IP地址段必须是0到255之间的整数且不能有前导零,当选满4个合法段且字符串用完时加入结果集,否则立即回溯。
题解:
List<String> res = new ArrayList<>(); // 存储所有可能的IP地址结果StringBuilder sb = new StringBuilder(); // 用于构建当前的IP地址public List<String> restoreIpAddresses(String s) {// 边界条件判断,如果字符串为空,或者长度不符合IP地址的规则,则直接返回空列表if (s == null || s.length() < 4 || s.length() > 12) {return res;}char[] chars = s.toCharArray(); // 将字符串转换为字符数组,方便处理backtracking(chars, 0, 0); // 调用回溯方法处理字符数组return res; // 返回结果列表}// 回溯方法,参数分别为字符数组 chars、起始索引 startIndex、当前深度 kpublic void backtracking(char[] chars, int startIndex, int k) {// 如果深度为4,且字符串长度加上3个点正好等于原始长度+4,表示找到一个合法IP地址if (k == 4 && sb.length() == chars.length + 4) {res.add(sb.substring(0, sb.length() - 1)); // 去掉最后一个点,并加入结果列表return;}// 遍历从startIndex开始的连续1到3个字符for (int i = startIndex; i < startIndex + 3 && i < chars.length; i++) {// 判断当前选取的子字符串是否合法if (isValid(chars, startIndex, i)) {int len = sb.length(); // 记录添加数字前的长度sb.append(chars, startIndex, i - startIndex + 1).append('.'); // 将子字符串添加到sb中backtracking(chars, i + 1, k + 1); // 递归调用,深度加1sb.setLength(len); // 回溯时恢复到之前的长度,删除最后添加的子字符串及点}}}// 判断字符数组中从 begin 到 end 索引的数字是否合法public boolean isValid(char[] chars, int start, int end) {// 如果0开头且长度大于1,则为不合法情况,返回falseif (chars[start] == '0' && end > start) {return false;}// 将字符数组的一部分转换为整数int num = Integer.parseInt(new String(chars, start, end - start + 1));// 判断数字是否在合法范围内return num <= 255 && num >= 0;}
78.子集
题目链接:78.子集
文档讲解:代码随想录
状态:还行
思路:子集问题就是收集路径,不过要加上一个空子集。在收集路径的时候可以利用深度,也可以利用startIndex来控制什么时候收集结束。
题解:
List<List<Integer>> res = new ArrayList<>(); // 存储所有子集的列表LinkedList<Integer> list = new LinkedList<>(); // 用于构建当前子集的链表// 主方法,入口点,传入数组 numspublic List<List<Integer>> subsets(int[] nums) {backtracking(nums, 0, 0); // 调用回溯方法处理数组 numsreturn res; // 返回结果列表}// 回溯方法,参数分别为数组 nums、起始索引 startIndex、当前深度 kpublic void backtracking(int[] nums, int startIndex, int k) {if (k < nums.length) {res.add(new ArrayList<>(list)); // 将当前子集加入结果列表}if (k == nums.length) {res.add(new ArrayList<>(list)); // 将当前子集加入结果列表return;}/* 或者这样写也可以res.add(new ArrayList<>(path));if (startIndex >= nums.length) {return;}*/// 遍历从 startIndex 开始的数组元素for (int i = startIndex; i < nums.length; i++) {list.add(nums[i]); // 将当前元素加入子集中backtracking(nums, i + 1, k + 1); // 递归调用,深度加1list.removeLast(); // 回溯,移除最后添加的元素,继续寻找其他子集}}
90.子集II
题目链接:90.子集II
文档讲解:代码随想录
状态:还行,卡了小会儿
思路:对于给定的元素中存在重复的情况可以使用排序+used数组来跳过重复元素。
题解:
// 保存所有子集的结果List<List<Integer>> res = new ArrayList<>();// 保存当前子集LinkedList<Integer> list = new LinkedList<>();// 记录数组中的元素是否被使用过boolean[] used;/*** 主方法,返回所有子集* @param nums 输入的数组* @return 所有不重复的子集*/public List<List<Integer>> subsetsWithDup(int[] nums) {// 初始化 used 数组used = new boolean[nums.length];// 对数组进行排序,以便处理重复元素Arrays.sort(nums);// 调用回溯函数backtracking(nums, 0, used);return res;}/*** 回溯方法,生成子集* @param nums 输入的数组* @param startIndex 回溯的起始索引* @param used 元素使用情况的记录*/public void backtracking(int[] nums, int startIndex, boolean[] used) {// 将当前子集加入结果集res.add(new ArrayList<>(list));// 如果起始索引等于数组长度,结束回溯if (startIndex == nums.length) {return;}// 遍历数组中的每一个元素for (int i = startIndex; i < nums.length; i++) {// 跳过重复元素,避免生成重复子集if (i > 0 && nums[i - 1] == nums[i] && !used[i - 1]) {continue;}// 添加当前元素到子集中list.add(nums[i]);// 标记当前元素为使用过used[i] = true;// 递归调用回溯函数,继续生成子集backtracking(nums, i + 1, used);// 回溯,撤销上一步操作used[i] = false;list.removeLast();}}