算法训练营(day28)

回溯算法模板

1
2
3
4
5
6
7
8
9
10
11
12
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}

for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}

93. 复原 IP 地址

题目链接:https://leetcode.cn/problems/restore-ip-addresses/description/

有效 IP 地址 正好由四个整数(每个整数位于 0255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

  • 例如:"0.1.2.201" "192.168.1.1"有效 IP 地址,但是 "0.011.255.245""192.168.1.312""192.168@1.1"无效 IP 地址。

给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。

解题思路

解题过程:回溯

  1. 定义地址有效判断
    • 字符串最大长度不超过12
    • 字符串仅包含 0-9 的数字
    • 如果每段字符串的首位为 0,则0后 不能跟任何数字
    • 每段字符串不能超过 255
  2. 定义终止条件:当 逗点. 等于3时,判断第四段字符串是否有效,合法则放入 result
  3. 定义循环条件
    • 横向:从 startIndex 遍历到 s.length() - 1
    • 纵向:如果合法,则对字符串加入 逗点. 隔开,下一个字符串起始位置向后延一位 (i + 2),计算 逗点的数
  4. 回溯:回滚逗点个数,同时将字符串连接起来

详细代码

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution {
List<String> result = new ArrayList<>();

public List<String> restoreIpAddresses(String s) {
if(s.length() > 12){
return result;
}
backTrack(s, 0, 0);
return result;
}

public boolean isValid(String s, int start, int end){
if(start > end){
return false;
}
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;
}
num = num * 10 + (s.charAt(i) - '0');
if(num > 255){
return false;
}
}
return true;
}

public void backTrack(String s, int startIndex, int num){
if(num == 3){
if(isValid(s, startIndex, s.length() - 1)){
result.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);
num++;
backTrack(s, i + 2, num);
num--;
s = s.substring(0, i + 1) + s.substring(i + 2);
}else{
break;
}
}

}
}

78. 子集

题目链接:https://leetcode.cn/problems/subsets/description/

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

解题思路

解题过程:回溯、子集无序

  1. 定义终止条件:每一个元素都要使用,所以需要全部遍历,终止条件可以省略
  2. 定义循环条件
    • 横向:从 startIndex 遍历到 nums.length - 1
    • 纵向:每一次从 startIndex 往后遍历
  3. 回溯:回滚 path 的末位元素

详细代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();

public List<List<Integer>> subsets(int[] nums) {
backTrack(nums, 0);
return result;
}

public void backTrack(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]);
backTrack(nums, i + 1);
path.removeLast();
}
}
}

90. 子集 II

题目链接:https://leetcode.cn/problems/subsets-ii/description/

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

解题思路

解题过程:回溯、子集无序、不能重复

  1. 有重复数组,为进行去重,先对数组进行排序
  2. 定义终止条件:每一个元素都要使用,所以需要全部遍历,终止条件可以省略
  3. 定义循环条件
    • 添加去重条件:相同元素直接跳过处理
    • 横向:从 startIndex 遍历到 nums.length - 1
    • 纵向:每一次从 startIndex 往后遍历
  4. 回溯:回滚 path 的末位元素

详细代码

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
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();

public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
backTrack(nums, 0);
return result;
}

public void backTrack(int[] nums, int startIndex){
result.add(new ArrayList<>(path));
if(startIndex >= nums.length){
return;
}
for(int i = startIndex; i < nums.length; i++){
if(i > startIndex && nums[i - 1] == nums[i]){
continue;
}
path.add(nums[i]);
backTrack(nums, i + 1);
path.removeLast();
}
}
}