例1、二进制手表

例2、N皇后问题

例3、全排列

例4、搜索

例5、将数组拆分成斐波那契数列

例1、二进制手表

github 401题

一、问题描述

题目描述

使用回溯算法

一般:

dfs(全部可取节点borad,当前节点下标ij,节点状态visited,目标路径,path,已走到路径的下标idx)

参考了labuladong大佬的算法框架

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
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return

for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择

private void backtrack("原始参数") {
//终止条件(递归必须要有终止条件)
if ("终止条件") {
//一些逻辑操作(可有可无,视情况而定)
return;
}

for (int i = "for循环开始的参数"; i < "for循环结束的参数"; i++) {
//一些逻辑操作(可有可无,视情况而定)

//做出选择

//递归
backtrack("新的参数");
//一些逻辑操作(可有可无,视情况而定)

//撤销选择
}
}

作者:sdwwld
链接:https://leetcode-cn.com/problems/split-array-into-fibonacci-sequence/solution/javahui-su-suan-fa-tu-wen-xiang-jie-ji-b-vg5z/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

框架链接

二、具体代码

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
53
54
55
56
57
class Solution(object):
def readBinaryWatch(self, num):
"""
:type num: int
:rtype: List[str]
"""
nums=[8,4,2,1,32,16,8,4,2,1] #前四个是时 后6个是分
self.res=[]
track=[] #存所选的led灯下标i,nums[i]即为对应的数
def backtrack(nums,track,num):
if len(track)==num:
h=0
f=0
for i in track:
if i <=3:
h+=nums[i] #时
else:
f+=nums[i] #分
if f<10: #进行分钟上的补零
temp=str(h)+":0"+str(f)
else:
temp=str(h)+":"+str(f)
self.res.append(temp)
return
for i in range(len(nums)):
if not isValid(track,i):
continue
track.append(i)
backtrack(nums,track,num)
track.pop()
def isValid(track,i): #判断合理性
if track==[]:
return True
if i <=track[-1]: #8,9 和 9,8表示的时间一致 所以只取8,9
return False
res=0
if i<=3:
for j in track:
if j<=3:
res+=nums[j]
res+=nums[i]
if res<12: #时针合理性
return True
else:
return False
else:
for j in track:
if j>3:
res+=nums[j]
res+=nums[i]
if res<60: #分针合理性
return True
else:
return False
backtrack(nums,track,num)
return self.res

例2、N皇后问题

github 51题

一、问题描述

题目描述
题目描述

二、具体代码

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class Solution {

List<List<String>> res = new ArrayList<>();

/* 输入棋盘的边长n,返回所有合法的放置 */
public List<List<String>> solveNQueens(int n) {
// "." 表示空,"Q"表示皇后,初始化棋盘
char[][] board = new char[n][n];
for (char[] c : board) {
Arrays.fill(c, '.');
}
backtrack(board, 0);
return res;
}

public void backtrack(char[][] board, int row) {
// 每一行都成功放置了皇后,记录结果
if (row == board.length) {
res.add(charToList(board));
return;
}

int n = board[row].length;
// 在当前行的每一列都可能放置皇后
for (int col = 0; col < n; col++) {
// 排除可以相互攻击的格子
if (!isValid(board, row, col)) {
continue;
}
// 做选择
board[row][col] = 'Q';
// 进入下一行放皇后
backtrack(board, row + 1);
// 撤销选择
board[row][col] = '.';
}
}

/* 判断是否可以在 board[row][col] 放置皇后 */
public boolean isValid(char[][] board, int row, int col) {
int n = board.length;
// 检查列是否有皇后冲突
for (int i = 0; i < n; i++) {
if (board[i][col] == 'Q') {
return false;
}
}

// 检查右上方是否有皇后冲突
for (int i = row - 1, j = col + 1; i >=0 && j < n; i--, j++) {
if (board[i][j] == 'Q') {
return false;
}
}

// 检查左上方是否有皇后冲突
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q') {
return false;
}
}
return true;
}

public List charToList(char[][] board) {
List<String> list = new ArrayList<>();
for (char[] c : board) {
list.add(String.valueOf(c));
}
return list;
}
}

例3、全排列

一、问题描述

给定n,返回n的全排列

二、具体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
List<List<Integer>> res=new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
int[] visited=new int[nums.length];
backtrace(res,new ArrayList<>(),nums,visited);
return res;
}
public void backtrace(List<List<Integer>> res,List<Integer> path,int[] nums,int[] visited){
if(path.size()==nums.length){
res.add(new ArrayList<>(path));
return;
}
for(int i=0;i<nums.length;i++){
if(visited[i]==1) continue;
path.add(nums[i]);
visited[i]=1;
backtrace(res,path,nums,visited);
path.remove(path.size()-1);
visited[i]=0;
}
}
}

47_1 全排列II

一、问题描述

题目描述

二、具体代码

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"""
回溯+剪枝
private void dfs(int[] nums, int len, int depth, boolean[] used, Deque<Integer> path, List<List<Integer>> res)
int[] nums:输入的待排列数组
int len:最终终止时数组长度
int depth:目前数组的长度
boolean[] used:记录哪些数字被使用过 使用过true 没使用过false
path:记录已选的数字
res:记录全部结果
"""
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;

public class Solution {
public List<List<Integer>> permuteUnique(int[] nums) {
int len = nums.length;
List<List<Integer>> res = new ArrayList<>();
if (len == 0) {
return res;
}
// 排序(升序或者降序都可以),排序是剪枝的前提
Arrays.sort(nums);
boolean[] used = new boolean[len];
// 使用 Deque 是 Java 官方 Stack 类的建议
Deque<Integer> path = new ArrayDeque<>(len);
dfs(nums, len, 0, used, path, res);
return res;
}

private void dfs(int[] nums, int len, int depth, boolean[] used, Deque<Integer> path, List<List<Integer>> res) {
if (depth == len) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < len; ++i) {
if (used[i]) {
continue;
}
// 剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义
// 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
continue;
}
path.addLast(nums[i]);
used[i] = true;
dfs(nums, len, depth + 1, used, path, res);
// 回溯部分的代码,和 dfs 之前的代码是对称的
used[i] = false;
path.removeLast();
}
}
}

### 剑指 Offer 38 字符串的全排列
#### 一、问题描述
![题目描述](剑指offer/38.png)
#### 二、具体代码
```Java
"""
回溯+剪枝
"""
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Solution {
public String[] permutation(String s) {
int len = s.length();
if (len == 0) {
return new String[0];
}
// 转换成字符数组是常见的做法
char[] charArr = s.toCharArray();
// 排序是为了去重方便
Arrays.sort(charArr);
// 由于操作的都是字符,使用 StringBuilder
StringBuilder path = new StringBuilder();
boolean[] used = new boolean[len];

// 为了方便收集结果,使用动态数组
List<String> res = new ArrayList<>();
dfs(charArr, len, 0, used, path, res);

// 记得转成字符串数组
return res.toArray(new String[0]);
}

/**
* @param charArr 字符数组
* @param len 字符数组的长度
* @param depth 当前递归深度
* @param used 当前字符是否使用
* @param path 从根结点到叶子结点的路径
* @param res 保存结果集的变量
*/
private void dfs(char[] charArr,
int len,
int depth,
boolean[] used,
StringBuilder path,
List<String> res) {
if (depth == len) {
// path.toString() 恰好生成了新的字符对象
res.add(path.toString());
return;
}
for (int i = 0; i < len; i++) {
if (!used[i]) {
if (i > 0 && charArr[i] == charArr[i - 1] && !used[i - 1]) {
continue;
}
used[i] = true;
path.append(charArr[i]);

dfs(charArr, len, depth + 1, used, path, res);

// 递归完成以后,需要撤销选择,递归方法执行之前做了什么,递归方法执行以后就需要做相应的逆向操作
path.deleteCharAt(path.length() - 1);
used[i] = false;
}
}
}
}

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
## 例4、搜索 
### 一、问题描述
![题目描述](Backtrack/search.png)
### 二、具体代码

```Python
class Solution:
def exist(self, board, word):
def check(i, j, k): #判断从(i,j)处开始搜索 能否匹配到word[k:]
if board[i][j] != word[k]:
return False
if k == len(word) - 1:
return True

visited.add((i, j))
result = False
for di, dj in directions:
newi, newj = i + di, j + dj
if 0 <= newi < len(board) and 0 <= newj < len(board[0]):
if (newi, newj) not in visited:
if check(newi, newj, k + 1):
result = True
break

visited.remove((i, j))
return result

directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
h, w = len(board), len(board[0])
visited = set()
for i in range(h):
for j in range(w):
if check(i, j, 0):
return True

return False

例5、将数组拆分成斐波那契数列

一、问题描述

题目描述

二、具体代码

问题思路

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
"""
回溯
"""
public List<Integer> splitIntoFibonacci(String S) {
List<Integer> res = new ArrayList<>();
backtrack(S.toCharArray(), res, 0);
return res;
}

public boolean backtrack(char[] digit, List<Integer> res, int index) {
//边界条件判断,如果截取完了,并且res长度大于等于3,表示找到了一个组合。
if (index == digit.length && res.size() >= 3) {
return true;
}
for (int i = index; i < digit.length; i++) {
//两位以上的数字不能以0开头
if (digit[index] == '0' && i > index) {
break;
}
//截取字符串转化为数字
long num = subDigit(digit, index, i + 1);
//如果截取的数字大于int的最大值,则终止截取
if (num > Integer.MAX_VALUE) {
break;
}
int size = res.size();
//如果截取的数字大于res中前两个数字的和,说明这次截取的太大,直接终止,因为后面越截取越大
if (size >= 2 && num > res.get(size - 1) + res.get(size - 2)) {
break;
}
if (size <= 1 || num == res.get(size - 1) + res.get(size - 2)) {
//把数字num添加到集合res中
res.add((int) num);
//如果找到了就直接返回
if (backtrack(digit, res, i + 1)) //1确定 继续后面的分
return true;
//如果没找到,就会走回溯这一步,然后把上一步添加到集合res中的数字给移除掉
res.remove(res.size() - 1); //1排除 尝试12
}
}
return false;
}

//相当于截取字符串S中的子串然后转换为十进制数字
private long subDigit(char[] digit, int start, int end) {
long res = 0;
for (int i = start; i < end; i++) {
res = res * 10 + digit[i] - '0';
}
return res;
}