93.复原IP地址
思路
使用回溯进行字符串切割,通过 startIndex
控制切割的位置
递归深度决定切割次数,但只能递归两层,第三层需要取末尾,在终止条件中判断
每个切割的字符串需要进行合法性判断
剪枝优化
- 判断切割的字符串是否有效,如果无效,直接将当前节点对应的整棵树的右下角全部剪掉
解题方法
描述你的解题方法
复杂度
- 时间复杂度:
添加时间复杂度, 示例: O ( n ) O(n) O(n)
- 空间复杂度:
添加空间复杂度, 示例: O ( n ) O(n) O(n)
Code
class Solution {
private List<String> result = new ArrayList<>();
private LinkedList<String> path = new LinkedList<>();
int level = 0;
// 使用回溯进行字符串切割,通过 startIndex 控制切割的位置
// 递归深度决定切割次数,因此只能递归两层,第三层直接取末尾,在终止条件中判断
// 每个切割的字符串需要进行合法性判断
public List<String> restoreIpAddresses(String s) {
if (s.length() > 0) {
backTracking(s, 0);
}
return result;
}
private void backTracking(String s, int startIndex) {
// 终止条件
if (level == 3) {
String temp = s.substring(startIndex, s.length());
if (isValid(temp)) {
path.add(temp);
result.add(path.stream().collect(Collectors.joining(".")));
path.removeLast();
}
return;
}
// 横向遍历
for (int i = startIndex; i < s.length(); i++) {
// 当前节点处理
// 字符串范围 [startIndex, i]
// 判断切割的字符串是否有效,如果无效,直接将当前节点对应的整棵树的右下角全部剪掉
String temp = s.substring(startIndex, i + 1);
if (!isValid(temp)) {
return;
}
path.add(temp);
level += 1;
// 纵向遍历
backTracking(s, i + 1);
path.removeLast();
level -= 1;
}
return;
}
private boolean isValid(String temp) {
if (temp.length() == 0) {
return false;
}
if ("0".equals(temp)) {
return true;
}
if (temp.charAt(0) - '0' == 0) {
return false;
}
long num = Long.valueOf(temp);
if (num < 0l || num > 255l) {
return false;
}
return true;
}
}
78.子集
思路
通过回溯,构建逻辑树
- 横向遍历中,通过 startIndex 控制获取的数组元素位置
- 纵向递归遍历中,上一层已经获取的元素不能再获取,要顺移一位
- 每个节点(含根节点)构建的 path 都是一个结果集
解题方法
依旧是回溯的模板题
复杂度
- 时间复杂度:
添加时间复杂度, 示例: O ( n ) O(n) O(n)
- 空间复杂度:
添加空间复杂度, 示例: O ( n ) O(n) O(n)
Code
class Solution {
private List<List<Integer>> result = new ArrayList<>();
private LinkedList<Integer> path = new LinkedList<>();
// 通过回溯,构建逻辑树,每个节点(含根节点)构建的 path 都是一个结果集
public List<List<Integer>> subsets(int[] nums) {
backTracking(nums, 0);
return result;
}
private void backTracking(int[] nums, int startIndex) {
// 当前节点 path 放入结果集
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.removeLast();
}
return;
}
}
90.子集II
思路
回溯中,有两种去重方式,一种是横向遍历的去重,一种是纵向递归上的去重
这里应该采用横向遍历上的去重,也即遍历过的数不能再遍历
注意:去重时不能把当前节点也代入去重逻辑,否则会误判导致当前节点及其子树被剪掉
解题方法
描述你的解题方法
复杂度
- 时间复杂度:
添加时间复杂度, 示例: O ( n ) O(n) O(n)
- 空间复杂度:
添加空间复杂度, 示例: O ( n ) O(n) O(n)
Code
class Solution {
private List<List<Integer>> result = new ArrayList<>();
private LinkedList<Integer> path = new LinkedList<>();
// 回溯中,有两种去重方式,一种是横向遍历的去重,一种是纵向递归上的去重,这里应该采用横向遍历上的去重,也即遍历过的数不能
public List<List<Integer>> subsetsWithDup(int[] nums) {
// 排序
Arrays.sort(nums);
backTracking(nums, 0);
return result;
}
private void backTracking(int[] nums, int startIndex) {
// 收集每个节点的结果,包含根节点
result.add(new ArrayList<>(path));
// 终止条件
if (nums.length < startIndex) {
return;
}
for (int i = startIndex; i < nums.length; i++) {
// 横向去重,如果当前节点的值和前一个节点相同,说明重复,当前节点及其所有子树都剪掉
// 去重需要从起始位置的第二个位置开始
if (i != startIndex && nums[i] == nums[i - 1]) {
continue;
}
path.add(nums[i]);
backTracking(nums, i + 1);
path.removeLast();
}
return;
}
}