摘要
本文主要介绍了LeetCode回溯算法的几个题目,包括93.复原IP地址、78.子集、90.子集II。
1、93.复原IP地址
1.1 思路
-
同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)
- 在str的后⾯插⼊⼀个逗点
-
判断子串是否合法
- 段位以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一定要更新。
凡是组合问题,先搞清楚这两个问题。