随想录日记part25【很难】
t i m e : time: time: 2024.03.21
主要内容:回溯算法在之前的学习中已经熟练掌握,今天对其进行挑战并进行总结:1:重新安排行程 ;2.N皇后 ;3.解数独
- 332.重新安排行程
- 51. N皇后
- 37. 解数独
Topic1重新安排行程
题目:
给你一份航线列表 t i c k e t s tickets tickets ,其中 t i c k e t s [ i ] = [ f r o m i , t o i ] tickets[i] = [fromi, toi] tickets[i]=[fromi,toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。所有这些机票都属于一个从 J F K JFK JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 J F K JFK JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。例如,行程 [ " J F K " , " L G A " ] ["JFK", "LGA"] ["JFK","LGA"] 与 [ " J F K " , " L G B " ] ["JFK", "LGB"] ["JFK","LGB"] 相比就更小,排序更靠前。假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
输入: t i c k e t s = [ [ " M U C " , " L H R " ] , [ " J F K " , " M U C " ] , [ " S F O " , " S J C " ] , [ " L H R " , " S F O " ] ] tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]] tickets=[["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
输出: [ " J F K " , " M U C " , " L H R " , " S F O " , " S J C " ] ["JFK","MUC","LHR","SFO","SJC"] ["JFK","MUC","LHR","SFO","SJC"]
思路:
这道题目有几个难点:
- 一个行程中,如果航班处理不好容易变成一个圈,成为死循环
- 有多种解法,字母序靠前排在前面,让很多同学望而退步,如何该记录映射关系呢 ?
- 使用回溯法(也可以说深搜) 的话,那么终止条件是什么呢?
- 搜索的过程中,如何遍历一个机场所对应的所有机场。
[ [ " J F K " , " K U L " ] , [ " J F K " , " N R T " ] , [ " N R T " , " J F K " ] [["JFK", "KUL"], ["JFK", "NRT"], ["NRT", "JFK"] [["JFK","KUL"],["JFK","NRT"],["NRT","JFK"]为例,抽象为树形结构如下:
按照回溯模板我们进行回溯三部曲:
递归三部曲:
1.回溯函数模板返回值以及参数
在这里要定义一个全局变量 r e s u l t result result用来存放符合条件结果的集合。一个全局变量 p a t h path path存放某一种可能行程。一个全局变量 u s e d [ ] used[] used[],表示某张票是否使用。
所以整体代码如下:
List<List<String>> result = new ArrayList<>();// 存放所有的可能行程
List<String> path = new LinkedList<>();// 存放某一种可能行程
boolean[] used;
void reback(ArrayList<List<String>> tickets)
2.回溯函数终止条件
当 p a t h path path保存的地点数量和应该输出的节点数一致时表示找到其中的一种可能。
代码如下:
if (tickets.size() + 1 == path.size()) {
result.add(new ArrayList(path));
return;
}
3.回溯搜索的遍历过程
然后就是递归和回溯的过程,实现代码思路简单实现如下:
if (used[i] == false && tickets.get(i).get(0).equals(path.getLast())) {
used[i] = true;
path.add(tickets.get(i).get(1));
reback(tickets);
path.removeLast();
used[i] = false;
}
我将所有的可能都存在了 r e s u l t result result中,我们要输出的是字典序最小的那种可能,所以我对其进行字典序重排操作:
// 按字典序排序
Collections.sort(result, (list1, list2) -> {
Iterator<String> iter1 = list1.iterator();
Iterator<String> iter2 = list2.iterator();
while (iter1.hasNext() && iter2.hasNext()) {
int comparison = iter1.next().compareTo(iter2.next());
if (comparison != 0) {
return comparison;
}
}
最后输出 r e s u l t . g e t ( 0 ) result.get(0) result.get(0)
完整的代码如下:
class Solution {
List<List<String>> result = new ArrayList<>();// 存放所有的可能行程
List<String> path = new LinkedList<>();// 存放某一种可能行程
boolean[] used;
public List<String> findItinerary(List<List<String>> tickets) {
used = new boolean[tickets.size()];
Arrays.fill(used, false);
path.add("JFK");
reback((ArrayList) tickets);
// 按字典序排序
Collections.sort(result, (list1, list2) -> {
Iterator<String> iter1 = list1.iterator();
Iterator<String> iter2 = list2.iterator();
while (iter1.hasNext() && iter2.hasNext()) {
int comparison = iter1.next().compareTo(iter2.next());
if (comparison != 0) {
return comparison;
}
}
return Integer.compare(list1.size(), list2.size());
});
return result.get(0);//选取第一个输出
}
public void reback(ArrayList<List<String>> tickets) {
if (tickets.size() + 1 == path.size()) {
result.add(new ArrayList(path));
return;
}
for (int i = 0; i < tickets.size(); i++) {
if (used[i] == false && tickets.get(i).get(0).equals(path.getLast())) {
used[i] = true;
path.add(tickets.get(i).get(1));
reback(tickets);
path.removeLast();
used[i] = false;
}
}
}
}
Topic2N皇后
题目:
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n n n 皇后问题研究的是如何将 n n n 个皇后放置在 n × n n×n n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n n n ,返回所有不同的 n n n 皇后问题的解决方案。
每一种解法包含一个不同的 n n n 皇后问题的棋子放置方案,该方案中 ′ Q ′ 'Q' ′Q′ 和 ′ . ′ '.' ′.′ 分别代表了皇后和空位。
输入:
n
=
4
n = 4
n=4
输出:
[
[
"
.
Q
.
.
"
,
"
.
.
.
Q
"
,
"
Q
.
.
.
"
,
"
.
.
Q
.
"
]
,
[
"
.
.
Q
.
"
,
"
Q
.
.
.
"
,
"
.
.
.
Q
"
,
"
.
Q
.
.
"
]
]
[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
思路:
按照回溯模板我们进行回溯三部曲:
递归三部曲:
1.回溯函数模板返回值以及参数
在这里要定义一个全局变量 r e s u l t result result用来存放符合条件结果的集合。但是中途时使用了一个棋盘数组 c h e s s b o a r d chessboard chessboard
所以整体代码如下:
List<List<String>> result=new ArrayList<>();//保存结果
void reback(int n,int q,char[][] chessboard)
2.回溯函数终止条件
可以看出,当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回了
if(q==n){
result.add(Array2List(chessboard));
return;
}
3.回溯搜索的遍历过程
递归深度就是 q q q控制棋盘的行,每一层里 f o r for for循环 i i i控制棋盘的列,一行一列,确定了放置皇后的位置。每次都是要从新的一行的起始位置开始搜,所以都是从0开始
for(int i=0;i<n;i++){
if(iscan(q,i,n,chessboard)){
chessboard[q][i]='Q';
reback(n,q+1,chessboard);
chessboard[q][i]='.';
}
}
完整的代码如下:
class Solution {
List<List<String>> result = new ArrayList<>();// 保存结果
public List<List<String>> solveNQueens(int n) {
char[][] chessboard = new char[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
chessboard[i][j] = '.';
}
}
reback(n, 0, chessboard);
return result;
}
public List Array2List(char[][] chessboard) {// char[][]转换为String
List<String> res = new ArrayList<>();
for (char[] c : chessboard) {
res.add(String.copyValueOf(c));
}
return res;
}
public boolean iscan(int x, int y, int n, char[][] chessboard) {
for (int i = 0; i < y; i++) {
if (chessboard[x][i] == 'Q')
return false;
}
for (int i = 0; i < x; i++) {
if (chessboard[i][y] == 'Q')
return false;
}
for (int i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j--) {
if (chessboard[i][j] == 'Q')
return false;
}
for (int i = x - 1, j = y + 1; i >= 0 && j < n; i--, j++) {
if (chessboard[i][j] == 'Q')
return false;
}
return true;
}
public void reback(int n, int q, char[][] chessboard) {
if (q == n) {
result.add(Array2List(chessboard));
return;
}
for (int i = 0; i < n; i++) {
if (iscan(q, i, n, chessboard)) {
chessboard[q][i] = 'Q';
reback(n, q + 1, chessboard);
chessboard[q][i] = '.';
}
}
}
}
时间复杂度:
O
(
n
!
)
O(n!)
O(n!)
空间复杂度:
O
(
n
)
O(n)
O(n)
Topic3解数独
题目:
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
1.数字 1-9 在每一行只能出现一次。
2.数字 1-9 在每一列只能出现一次。
3.数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
输入:
b
o
a
r
d
=
[
[
"
5
"
,
"
3
"
,
"
.
"
,
"
.
"
,
"
7
"
,
"
.
"
,
"
.
"
,
"
.
"
,
"
.
"
]
,
[
"
6
"
,
"
.
"
,
"
.
"
,
"
1
"
,
"
9
"
,
"
5
"
,
"
.
"
,
"
.
"
,
"
.
"
]
,
[
"
.
"
,
"
9
"
,
"
8
"
,
"
.
"
,
"
.
"
,
"
.
"
,
"
.
"
,
"
6
"
,
"
.
"
]
,
[
"
8
"
,
"
.
"
,
"
.
"
,
"
.
"
,
"
6
"
,
"
.
"
,
"
.
"
,
"
.
"
,
"
3
"
]
,
[
"
4
"
,
"
.
"
,
"
.
"
,
"
8
"
,
"
.
"
,
"
3
"
,
"
.
"
,
"
.
"
,
"
1
"
]
,
[
"
7
"
,
"
.
"
,
"
.
"
,
"
.
"
,
"
2
"
,
"
.
"
,
"
.
"
,
"
.
"
,
"
6
"
]
,
[
"
.
"
,
"
6
"
,
"
.
"
,
"
.
"
,
"
.
"
,
"
.
"
,
"
2
"
,
"
8
"
,
"
.
"
]
,
[
"
.
"
,
"
.
"
,
"
.
"
,
"
4
"
,
"
1
"
,
"
9
"
,
"
.
"
,
"
.
"
,
"
5
"
]
,
[
"
.
"
,
"
.
"
,
"
.
"
,
"
.
"
,
"
8
"
,
"
.
"
,
"
.
"
,
"
7
"
,
"
9
"
]
]
board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
board=[["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出:
[
[
"
5
"
,
"
3
"
,
"
4
"
,
"
6
"
,
"
7
"
,
"
8
"
,
"
9
"
,
"
1
"
,
"
2
"
]
,
[
"
6
"
,
"
7
"
,
"
2
"
,
"
1
"
,
"
9
"
,
"
5
"
,
"
3
"
,
"
4
"
,
"
8
"
]
,
[
"
1
"
,
"
9
"
,
"
8
"
,
"
3
"
,
"
4
"
,
"
2
"
,
"
5
"
,
"
6
"
,
"
7
"
]
,
[
"
8
"
,
"
5
"
,
"
9
"
,
"
7
"
,
"
6
"
,
"
1
"
,
"
4
"
,
"
2
"
,
"
3
"
]
,
[
"
4
"
,
"
2
"
,
"
6
"
,
"
8
"
,
"
5
"
,
"
3
"
,
"
7
"
,
"
9
"
,
"
1
"
]
,
[
"
7
"
,
"
1
"
,
"
3
"
,
"
9
"
,
"
2
"
,
"
4
"
,
"
8
"
,
"
5
"
,
"
6
"
]
,
[
"
9
"
,
"
6
"
,
"
1
"
,
"
5
"
,
"
3
"
,
"
7
"
,
"
2
"
,
"
8
"
,
"
4
"
]
,
[
"
2
"
,
"
8
"
,
"
7
"
,
"
4
"
,
"
1
"
,
"
9
"
,
"
6
"
,
"
3
"
,
"
5
"
]
,
[
"
3
"
,
"
4
"
,
"
5
"
,
"
2
"
,
"
8
"
,
"
6
"
,
"
1
"
,
"
7
"
,
"
9
"
]
]
[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
思路:
按照回溯模板我们进行回溯三部曲:
递归三部曲:
1.回溯函数模板返回值以及参数
所以整体代码如下:
boolean iscan(char i,int x,int y,char[][] board)
2.回溯函数终止条件
本题递归不用终止条件,解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。
3.回溯搜索的遍历过程
一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!
for(int x=0;x<9;x++){
for(int y=0;y<9;y++){
if(board[x][y]!='.')continue;
for(char i='1';i<='9';i++){
if(iscan(i,x,y,board)){
board[x][y]=i;
if(reback(board)==true) return true;
board[x][y]='.';
}
}
return false;
}
}
完整的代码如下:
class Solution {
public void solveSudoku(char[][] board) {
boolean res = reback(board);
}
public boolean iscan(char i, int x, int y, char[][] board) {
for (int j = 0; j < 9; j++) {
if (board[x][j] == i)
return false;
}
for (int j = 0; j < 9; j++) {
if (board[j][y] == i)
return false;
}
int starx = x / 3 * 3;
int stary = y / 3 * 3;
for (int p = starx; p < starx + 3; p++) {
for (int q = stary; q < stary + 3; q++) {
if (board[p][q] == i)
return false;
}
}
return true;
}
private boolean reback(char[][] board) {
for (int x = 0; x < 9; x++) {
for (int y = 0; y < 9; y++) {
if (board[x][y] != '.')
continue;
for (char i = '1'; i <= '9'; i++) {
if (iscan(i, x, y, board)) {
board[x][y] = i;
if (reback(board) == true)
return true;
board[x][y] = '.';
}
}
return false;
}
}
return true;
}
}
时间复杂度:
O
(
n
!
)
O(n!)
O(n!)
空间复杂度:
O
(
n
)
O(n)
O(n)