代码随想录算法训练营第三十天|332.重新安排行程、51. N皇后、37. 解数独
332.重新安排行程
问题简述:从给定的飞机票中,输出能使用全部飞机票的行程,如果存在多个行程,选择字母排序靠前的行程。
思考:第一次做困难的题,这道题与之前最大的区别在于回溯函数有boolean
的返回值,选择排序靠前行程在于先对目的地进行排序,最先找到的行程就是靠前的行程。最后还有一个用例没过,二刷再看吧,可以剪枝。
算法思路:
- 定义res为最终的路径,定义path为当前的路径,used数组标记机票使用情况。
- 主函数先对机票按照目的地进行排序,并在path中添加初始地
JKK
,在进行回溯。 - 每次回溯时传入飞机票集合,返回值为
boolean
,指的是是否找到了一个路径。如果路径中包含了票数加一个地点,则将路径保存到res返回true;如果机票没用完,则遍历所有机票,如果当前票已被使用则跳过本次循环,若未被使用,找到出发地为path最后一个地方的票并在path中加入改票的目的地。修改这张票的使用情况,然后进行递归,如果为true则返回true,即找到了一条路线。没找到路径则修改used并回溯path。如果循环结束没找到路径,则返回fasle。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class Solution {
//定义返回值
List<String> res = new ArrayList<>();
List<String> path = new ArrayList<>();
int[] used;
public List<String> findItinerary(List<List<String>> tickets) {
//将每张票按照目的地进行排序
Collections.sort(tickets, (a, b) -> a.get(1).compareTo(b.get(1)));
path.add("JFK");
used = new int[tickets.size()];
backtracking(tickets);
return res;
}
//回溯函数
public boolean backtracking(List<List<String>> tickets){
//path中元素个数和nums相等时保存path
if (path.size() == tickets.size() + 1){
res = path;
return true;
}
//每次都遍历所有结点,如果当前结点已经在path中则跳过
for (int i = 0; i < tickets.size(); i++) {
//如果当前结点已经被使用,说明当前票已被使用
if (used[i] == 1) continue;
if (used[i] == 0 && tickets.get(i).get(0).equals(path.get(path.size() - 1))){
path.add(tickets.get(i).get(1));
used[i] = 1;
//找到了路径直接返回true
if (backtracking(tickets)) return true;
//没找到路径进行回溯
used[i] = 0;
path.remove(path.size() - 1);
}
}
// 如果遍历所有结点都没有找到有效路径,返回false
return false;
}
}
51. N皇后
问题简述:在棋盘上放置皇后棋子,要求放置的棋子行列和斜45、135度都不再有其他棋子。
思考:之前处理的都是数组,这道题处理棋盘稍微不一样些。注意在检查有效时只需要判断之前行的元素。
算法思路:
-
定义一个二维数组chessboard初始化全为
.
;定义函数chessToList
将二维数组转换为输出结果;isValid
用来判断当前位置是否有效。row
为当前处理的行。 -
每次递归从先判断是否已经处理完了最后一行,如果处理完了则row等于n,此时将二维数组转换为输出结果并返回;如果没处理完最后一行,依次遍历当前行元素,并判断该位置是否有效,如果有效则放置棋子,再进行递归下一行,递归结束进行回溯。最终res存储返回的结果。
import java.util.ArrayList;
import java.util.List;
class Solution {
//定义返回值
List<List<String>> res = 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] = '.';
}
}
backtracking(n, 0, chessboard);
return res;
}
//回溯函数
public void backtracking(int n, int row, char[][] chessboard){
if(row == n){
res.add(chessToList(chessboard, n));
return;
}
for (int i = 0; i < n; i++) {
if (isValid(chessboard, row, i, n)){
chessboard[row][i] = 'Q';
backtracking(n, row + 1, chessboard);
chessboard[row][i] = '.';
}
}
}
//二维数组转为list
public List<String> chessToList(char[][] chessboard, int n){
List<String> path = new ArrayList<>();
for (int i = 0; i < n; i++) {
StringBuilder s = new StringBuilder();
for (int j = 0; j < n; j++) {
s.append(chessboard[i][j]);
}
path.add(new String(s));
}
return path;
}
//判断位置是否有效
public boolean isValid(char[][] chessboard, int row, int i, int n){
for (int j = 0; j < n; j++) {
if (chessboard[row][j] == 'Q') return false;
if (chessboard[j][i] == 'Q') return false;
}
// 检查45度对角线
for (int i1=row-1, j1=i-1; i1>=0 && j1>=0; i1--, j1--) {
if (chessboard[i1][j1] == 'Q') {
return false;
}
}
// 检查135度对角线
for (int i1 = row-1, j1 = i+1; i1 >=0 && j1 <=n-1; i1--, j1++) {
if (chessboard[i1][j1] == 'Q') {
return false;
}
}
return true;
}
}
37. 解数独
问题简述:填满给定的数独。
思考:这个和第一道相似地方在于回溯函数返回值都为boolean
。然后递归中的循环是二层循环。结束条件就是遍历完所有的结点。
算法思路:遍历所有9*9结点,判断该结点是否有数字。如果没有数字依次将1~9放入,判断该数字是否符合要求,如果符合要求则填入该数字。再进行递归,如果递归返回true则直接返回true,再进行回溯。如果跳出1~9的数字循环说明没找到有效填充方式,则返回fasle。如果某次递归遍历完了所有结点,则返回true,此时找到了一种有效的填充方式。
class Solution {
public void solveSudoku(char[][] board) {
backtracking(board);
}
public boolean backtracking(char[][] board){
//遍历所有结点
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
//如果结点不是数字
if (board[i][j] == '.'){
//遍历所有1~9的数字
for (int k = 1; k < 10; k++) {
//判断k能否放置
if (incol(board, i, j, k)){
board[i][j] = (char)(k + '0');
if (backtracking(board)) return true;
board[i][j] = '.';
}
}
//如果没有数字可以放置,返回 false
return false;
}
}
}
//格子都填满了
return true;
}
public boolean incol(char[][] board, int row, int col, int i){
char ch = (char)(i + '0');
for (int j = 0; j < 9; j++) {
if (board[row][j] == ch) return false;
if (board[j][col] == ch) return false;
}
// 检查 3x3 宫
int boxRow = row / 3 * 3;
int boxCol = col / 3 * 3;
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 3; k++) {
if (board[boxRow + j][boxCol + k] == ch) return false;
}
}
return true;
}
}
感想
诶。