题目详细:LeetCode.332
这道题我是先看题解再有自己的思路,然后做出来的,解题思路的过程都写在代码注释里了,详细的题解可查阅:《代码随想录》 — 重新安排行程
Java解法(递归,回溯):
class Solution {
Deque<String> ans = new ArrayDeque<>();
// Map<出发机场, Map<目地机场, 机票数量>>
Map<String, Map<String, Integer>> map = new HashMap<>();
// 返回值boolean,true 代表找到一种合理的行程
public boolean backTrack(int n){
// 如果 到达的机场个数 == 机票数量 + 1,说明所有的机票都已被使用一次,是一种合理的行程
if(ans.size() == n + 1){
return true;
}
// 获取当前出发的机场
String start = ans.getLast();
if(this.map.containsKey(start)){
// 获取当前机场可到达的目的地(已按字典升序排序 )
Map<String, Integer> end_map = this.map.get(start);
// 按顺序遍历目的地并安排行程
for(Map.Entry<String, Integer> end_entry : end_map.entrySet()){
String end = end_entry.getKey();
int count = end_entry.getValue();
// 机票数量 conut > 0 才能继续行程
if(count > 0){
ans.offer(end);
end_map.put(end, count - 1); // 每张机票只能使用一次,对应的航班次数 - 1
if(backTrack(n)){
// 得到一种排序靠前的合理的行程即可返回结果
return true;
}
// 回溯
ans.removeLast();
end_map.put(end, count);
}
}
}
// 如果出发地已经没有下一段行程,则说明该行程安排不合理,返回 false
return false;
}
public List<String> findItinerary(List<List<String>> tickets) {
// 遍历每一段行程
for(List<String> ticket: tickets){
String start = ticket.get(0);
String end = ticket.get(1);
// 记录出发地到达多个目的地的次数
Map<String, Integer> temp;
if(!this.map.containsKey(start)){
// 容器按字典升序排序
temp = new TreeMap<>();
temp.put(end, 1);
}else{
temp = this.map.get(start);
temp.put(end, temp.getOrDefault(end, 0) + 1);
}
this.map.put(start, temp);
}
// 行程规定从 JFK 开始。
this.ans.offer("JFK");
this.backTrack(tickets.size());
return new ArrayList<String>(this.ans);
}
}
在搜索函数backTrack中,我们可以看到其返回值是boolean类型,为什么不是跟之前的题目的返回值一样是void类型呢?
因为在之前的题目中,不论是求解组合问题、子集问题还是求解子序列问题,这些都要求我们找出所有的结果放进结果集中返回,所以需要对树形结构的每一条路径都进行搜索;
但是在这道题中,虽然可能存在多种行程方案,但题目只要求我们找出一种合理的行程方案即可,也就是我们在树形结构中找到某一条满足结果的路径时,就可以直接返回该结果,并不需要继续对其他路径进行搜索寻找其他方案;
所以我们可以设置搜索函数的返回值为boolean类型,来标识在递归过程中是否已经找到了一种合理的行程方案,返回值为 true,那么就可以直接返回结果了。
题目详细:LeetCode.51
这道题我是先看题解再有自己的思路,然后做出来的,解题思路的过程都写在代码注释里了,详细的题解可查阅:《代码随想录》 — N皇后
与题解中的答案不同的是,我是选择了一维数组来表示棋盘;
再一个难点就是如何判断当前位置是否能放置皇后:
public boolean isVaild(int[] board, int row, int col){
for(int i = 0; i <= row; i++){
if(board[i] == col) return false;
if(Math.abs(i - row) == Math.abs(board[i] - col)) return false;
}
return true;
}
可以发现选用一维数组表示棋盘的好处,在于校验N皇后放置的位置时,遍历棋盘的时间复杂度为 O(n) ,且只需要判断以下两个条件即可:
board[i] == col
,如果某一行出现了与当前位置相同的列数,则表示在同一列上已存在皇后,当前位置不能放置皇后Math.abs(i - row) == Math.abs(board[i] - col)
,如果满足,则表示在同一对角线上已存在皇后,当前位置不能放置皇后。Java解法(递归,回溯):
class Solution {
List<List<String>> ans = new ArrayList<>();
// 将棋盘转化为题目要求的格式
public List<String> boardToList(int[] board){
List<String> res = new ArrayList<>();
for(int row = 0; row < board.length; row++){
int col = board[row];
char[] board_row = new char[board.length];
Arrays.fill(board_row, '.');
board_row[col] = 'Q';
res.add(new String(board_row));
}
return res;
}
// 判断指定位置是否能够放置皇后
public boolean isVaild(int[] board, int row, int col){
for(int i = 0; i <= row; i++){
// 判断同一列上是否存在皇后
if(board[i] == col) return false;
// 判断同一对角线上是否存在皇后
if(Math.abs(i - row) == Math.abs(board[i] - col)) return false;
}
return true;
}
public void backTrack(int[] board, int row){
// 如果最后一行已放完皇后,即表示满足一种放置方案,将该放置方案加入结果集
if(row == board.length){
this.ans.add(this.boardToList(board));
return;
}
// 循环:在当前行的每一列尝试放置皇后
for(int col = 0; col < board.length; col++){
// 判断当前位置是否能够放置皇后
if(this.isVaild(board, row, col)){
board[row] = col; // 记录皇后放置的位置
this.backTrack(board, row + 1); // 递归继续放置下一行
board[row] = -1; // 回溯
}
}
}
public List<List<String>> solveNQueens(int n) {
// 使用一维数组来表示棋盘,数组的下标表示第几行,下标对应元素的值表示第几列
int[] board = new int[n];
// 填充值为-1,表示该行未放置皇后
Arrays.fill(board, -1);
this.backTrack(board, 0);
return this.ans;
}
}
题目详细:LeetCode.37
这道题我是先看题解再有自己的思路,然后做出来的,解题思路的过程都写在代码注释里了,详细的题解可查阅:《代码随想录》 — 解数独
class Solution {
// 判断在指定位置填入指定数字的合法性
public boolean isVaild(char[][] board, int row, int col, int val){
// 判断同行同列是否存在重复的数值
for(int i = 0; i < board.length; i++){
if(board[row][i] == val) return false;
if(board[i][col] == val) return false;
}
// 判断分隔的9宫格里是否存在重复的数值
int startRow = (row / 3) * 3;
int startCol = (col / 3) * 3;
for (int i = startRow; i < startRow + 3; i++){
for (int j = startCol; j < startCol + 3; j++){
if (board[i][j] == val){
return false;
}
}
}
return true;
}
public boolean backTrack(char[][] board){
// 用双重循环来遍历行和列
for(int row = 0; row < board.length; row++){
for(int col = 0; col < board.length; col++){
// 用递归来尝试放置数字1~9
if(board[row][col] == '.'){
for(char n = '1'; n <= '9'; n++){
if(this.isVaild(board, row, col, n)){
board[row][col] = n;
if(this.backTrack(board)){
// 如果在递归过程中已得到一种数独解,那么直接返回true结束搜索,无需继续求解其他结果
return true;
}
board[row][col] = '.';
}
}
// 如果1~9都无法放置,说明该盘数独无解,返回false;
return false;
}
}
}
// 如果棋盘都已填入数字,即解出一种数独结果,返回true;
return true;
}
public void solveSudoku(char[][] board) {
this.backTrack(board);
}
}
详细的总结可查阅:《代码随想录》 — 回溯算法 — 总结
虽然最后的练习题难度都为Hard,但是一遍看题解,一遍自己理清思路,花的时间虽长,但还是啃下来了,痛快哉啊!
路漫漫其修远兮,吾将上下而求索。