所用代码 java
复原IP地址 LeetCode 93
题目链接:复原IP地址 LeetCode 93 - 中等
思路
本题主题思路和昨天的切割回文数类似,主要就是看切割点的位置。
class Solution {
List<Integer> path = new LinkedList<>();
List<String> res = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
backtracking(s, 0);
return res;
}
public void backtracking(String s, int startIndex){
if (path.size() > 4) return;
// 当切割点位于最后时,代表切割完毕
// 且ip地址需要4位才算合法ip
if (startIndex == s.length() && path.size() == 4){
// System.out.println("path = " + path);
//拼接
StringBuilder sb = new StringBuilder();
for (int i = 0; i < path.size()-1; i++) {
sb.append(path.get(i) + ".");
}
// 最后再拼接组后一个数,后面不需要 "."
sb.append(path.get(path.size()-1));
res.add(sb.toString());
return;
}
for (int i = startIndex; i < s.length(); i++) {
String str = s.substring(startIndex, i+1);
// 排除首位为0的情况(两位数以上)
if (str.length()>1 && str.charAt(0) == '0') return;
int num = Integer.valueOf(str);
if (num < 0 || num > 255) return;
path.add(num);
// 第二个数的切割点时从下一位开始的
backtracking(s, i + 1);
path.remove(path.size()-1);
}
}
}
总结
本题关键点有几个:
-
终止条件:
startIndex == s.length() && path.size() == 4
不仅要考虑切割点在末尾的情况,还需确定path的元素刚好四位 -
需要合法ip:
- 合法情况下的数值:
(num < 0 || num > 255)
- 非合法的数值:
str.length()>1 && str.charAt(0) == '0'
- 合法情况下的数值:
-
切割字符串操作:
s.substring(startIndex, i+1)
为左闭右开,起始startIndex是不变的,而终点i是一种变化的。
剪枝操作: 当path.size() > 4
就可以直接返回,该条件可直接写在for循环里面
本题还可以以昨天的题为模板,写出同样的回溯:
- 判断切割的数字是否合法 isValid
- 合法的情况下字符串添加 ” . “ ,此时起始位置就是
i + 2
class Solution {
List<String> res = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
backtracking(s, 0, 0);
return res;
}
// pointNum 添加逗号的点
public void backtracking(String s,int startIndex, int pointNum){
// 刚好有3个逗号,即分割成了四个数
if (pointNum == 3){
if (isValid(s, startIndex, s.length()-1)){
res.add(s);
}
return;
}
for (int i = startIndex; i < s.length(); i++) {
if (isValid(s, startIndex, i)){
s = s.substring(0, i+1) + "." + s.substring(i+1);
pointNum++;
backtracking(s, i + 2, pointNum);
pointNum--;
// 回溯的时候还要删除掉逗号
s = s.substring(0, i+1) + s.substring(i+2);
}else continue;
}
}
// 左闭右闭区间判断数字是否合法
public boolean isValid(String s, int start, int end){
if (start > end) return false;
// 首位为 0 ,且传入的不是一位数
if (s.charAt(start) == '0' && start != end) return false;
int num = 0;
for (int i = start; i <= end; i++) {
// 遇到非数字不合法
if (s.charAt(i) > '9' || s.charAt(i) < '0') return false;
// 构建起点为start,终点为end的数字
num = 10 * num + (s.charAt(i) - '0');
if (num > 255) return false;
}
return true;
}
}
子集 LeetCode 78
题目链接:子集 LeetCode 78 - 中等
思路
本题为标准的回溯题,可在每次回溯之前收集结果
示意图如下:
class Solution {
List<Integer> path = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
backtracking(nums, 0);
return res;
}
public void backtracking(int[] nums, int startIndex){
// 先收获结果,再进行递归
res.add(new LinkedList<>(path));
// 终止条件可不写,因为startIndex >= nums.length就不会进入下面的for循环了
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);
}
// 该条件也可以省略,因为他直接结束了
return;
}
}
总结
本题收获结果是在树的每一个结点,所用我们一开始就可以把结果加进去。
子集II LeetCode 90
题目链接:子集II LeetCode 90 - 中等
思路
本题一看和上题类似,都在所有的结点收集结果,只是多了一个去重操作,但是去重的方式都和昨天的组合总和II LeetCode 40 一模一样。
示意图如下:
class Solution {
List<Integer> path = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
int[] used;
public List<List<Integer>> subsetsWithDup(int[] nums) {
used = new int[nums.length];
// 去重操作得先排序
Arrays.sort(nums);
backtracking(nums, 0);
return res;
}
public void backtracking(int[] nums, int startIndex){
// 所有树节点都要收集结果
res.add(new LinkedList<>(path));
for (int i = startIndex; i < nums.length; i++) {
// 数层去重操作
if (i != 0 && nums[i-1] == nums[i] && used[i-1] == 0){
// System.out.println("used" + Arrays.toString(used));
continue;
}
path.add(nums[i]);
used[i] = 1;
backtracking(nums, i + 1);
// 回溯
path.remove(path.size()-1);
used[i] = 0;
}
}
}
总结
只要掌握了子集问题,以及组合的去重方式,本题就很简单,因为方法都是类似的,无非就两点:
- 结果在树的结点收集
- 使用used标记进行数层去重
另外还有一种不使用uesd数组进行去重的方法:
示意图:
class Solution {
List<Integer> path = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
// 去重操作得先排序
Arrays.sort(nums);
backtracking(nums, 0);
return res;
}
public void backtracking(int[] nums, int startIndex){
// 所有树节点都要收集结果
res.add(new LinkedList<>(path));
for (int i = startIndex; i < nums.length; i++) {
// 数层去重操作
if (i > startIndex && nums[i-1] == nums[i]) {
continue;
}
path.add(nums[i]);
backtracking(nums, i + 1);
// 回溯
path.remove(path.size()-1);
}
}
}
本方法主要是巧妙借助了startIndex与i的关系,startIndex表示的就是递归的深度(每次递归都会往更深一层探索,即i+1,也就是startIndex+1),也就是树枝,而i表示的是递归的宽度,也就是数层
,而i > startIndex && nums[i-1] == nums[i]
则表示在同一层里,前后两个数相等的情况