第七章回溯算法part03,分割问题、子集问题。分割问题要注意合法终止条件的判断。
93. 复原IP地址
1.1 解题分析
分析回溯算法三部曲:
- 参数和返回值是什么? 需要一个startIndex,记录当前切割到哪里了
- 确定递归的终止条件。 当遍历到字符串末尾,并且正好是4段字符串,说明找到了一个可行解,加入到result中,本层递归终止;
- 确定单层搜索的过程。 for循环横向遍历,负责切割字符串,并且在这里判断数字是否符合ip地址的要求,递归纵向遍历,与回溯配合使用。
第一遍做错的点:
- 终止条件不完整,IP段数没限制,IP地址必须是4段,不能少,也不能多。
- 前导0判断逻辑是错的,把单个’0’这种情况判定为false;
- 剪枝晚了,对于超过3位数的数字串,不应该切割出来;
- 缺少剪枝1:当IP地址段数已经超过4段时,应该终止本层递归;
- 缺少剪枝2:当剩余的字符数量不合理时,也没有必要继续递归,应该提前终止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class Solution { private List<String> path = new ArrayList<>(); private List<String> result = new ArrayList<>();
public List<String> restoreIpAddresses(String s) { backtracking(s, 0); return result; }
public void backtracking(String s, int startIndex) { if (path.size() > 4) return; if(path.size() == 4) { if (startIndex == s.length()) result.add(String.join(".", path)); return; } int remain = s.length() - startIndex; int need = 4 - path.size(); if (remain < need || remain > need * 3) return; for (int i = startIndex; i < s.length() && i < startIndex + 3; i++) { String sub = s.substring(startIndex, i + 1); if (!isSuit(sub)) continue; path.add(sub); backtracking(s, i + 1); path.remove(path.size() - 1); } }
private boolean isSuit(String s) { if (s.length() > 1 && s.charAt(0) == '0') return false; return Integer.parseInt(s) <= 255; } }
|
78. 子集
2.1 解题分析
子集问题,和组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点。子集问题也是一种组合问题,因为它的集合是无序的,子集{1,2}和子集{2,1}是一样的,因此,取过的元素不会重复取,需要startIndex控制遍历开始的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private List<Integer> path = new ArrayList<>(); private List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) { backtracking(nums, 0); return result; } public void backtracking(int[] nums, int startIndex) { result.add(new ArrayList<>(path)); if (startIndex == nums.length) return; for (int i = startIndex; i < nums.length; i++) { path.add(nums[i]); backtracking(nums, i + 1); path.remove(path.size() - 1); } }
|
90. 子集II
3.1 解题分析
- 和组合问题的去重一样,要先对整数数组排序,然后定义一个used数组,同一树层的元素如果一致要进行去重。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Solution { private List<Integer> path = new ArrayList<>(); private List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) { Arrays.sort(nums); backtracking(nums, 0, new boolean[nums.length]); return result; } public void backtracking(int[] nums, int startIndex, boolean[] used) { result.add(new ArrayList<>(path)); for (int i = startIndex; i < nums.length; i++) { if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue; path.add(nums[i]); used[i] = true; backtracking(nums, i + 1, used); path.remove(path.size() - 1); used[i] = false; } } }
|
- 但在本题中,实现同一树层的去重,实际上并不需要使用used数组,在同一层中,如果当前值和前一个值相同,跳过即可:
1
| if (i > startIndex && nums[i] == nums[i - 1]) continue;
|