332.重新安排行程
难点:
- 一个行程中,如果航班处理不好容易变成一个圈,成为死循环(出发机场和到达机场也会重复的)
- 有多种解法,字母序靠前排在前面,让很多同学望而退步,如何该记录映射关系呢 ?(为什么一定要增删元素呢,出发机场和到达机场是会重复的,搜索的过程没及时删除目的机场就会死循环。可以使用"航班次数"这个字段的数字做相应的增减,来标记到达机场是否使用过了,相当于说我不删,我就做一个标记!)
- 使用回溯法(也可以说深搜) 的话,那么终止条件是什么呢?(航班数量加一作为终止条件)
- 搜索的过程中,如何遍历一个机场所对应的所有机场。(通过map的键出发站找到对应的值map找到终点站与机票数)
由于 TreeMap 会对键进行排序,因此存储的终点城市会按照升序排序。这样,我们可以快速地找到某个起点城市可以到达的所有终点城市以及对应的机票数量。
[ ["JFK", "SFO"], ["JFK", "LAX"], ["SFO", "LAX"] ]
{ "JFK": { "SFO": 1, "LAX": 1 }, "SFO": { "LAX": 1 } }
class Solution {
LinkedList<String> res = new LinkedList<>();
Map<String,Map<String,Integer>> map = new HashMap<>();
public List<String> findItinerary(List<List<String>> tickets) {
for(List<String> t : tickets){
Map<String,Integer> temp;
if(map.containsKey(t.get(0))){
temp = map.get(t.get(0));
temp.put(t.get(1),temp.getOrDefault(t.get(1),0)+1);
}else{
temp = new TreeMap<>();
temp.put((t.get(1)),1);
}
map.put(t.get(0),temp);
}
res.add("JFK");
backtracking(tickets.size());
return res;
}
public boolean backtracking(int ticknum){
if(res.size() == ticknum +1 )return true;
String last = res.getLast();
if(map.containsKey(last)){
for(Map.Entry<String,Integer> targets : map.get(last).entrySet()){
int count = targets.getValue();
if(count > 0){
res.add(targets.getKey());
targets.setValue(count - 1);
if(backtracking(ticknum)) return true;
res.removeLast();
targets.setValue(count);
}
}
}
return false;
}
}
51. N皇后
约束条件:
- 不能同行
- 不能同列
- 不能同斜线
二维矩阵中矩阵的高就是这棵树的高度,矩阵的宽就是树形结构中每一个节点的宽度。
只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了。
class Solution {
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
char [][] chessboard = new char[n][n];
for(char[] c : chessboard){
Arrays.fill(c,'.');
}
backtracking(n,0,chessboard);
return res;
}
public void backtracking(int n,int row,char[][] chessboard){
if(row == n){
res.add(Array2List(chessboard));
return;
}
for (int col = 0;col < n; col++) {
if (isValid (row, col, n, chessboard)) {
chessboard[row][col] = 'Q';
backtracking(n, row+1, chessboard);
chessboard[row][col] = '.';
}
}
}
public List Array2List(char[][] chessboard) {
List<String> list = new ArrayList<>();
for (char[] c : chessboard) {
list.add(String.copyValueOf(c));
}
return list;
}
public boolean isValid(int row, int col, int n, char[][] chessboard) {
//对于每一行在每次操作时只会选取一个位置来填充故而不需要做判断
// 检查列
for (int i=0; i<row; i++) { // 相当于剪枝
if (chessboard[i][col] == 'Q') {
return false;
}
}
// 检查45度对角线 从该点到左上角
for (int i=row-1, j=col-1; i>=0 && j>=0; i--, j--) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
// 检查135度对角线 从该点到右上角
for (int i=row-1, j=col+1; i>=0 && j<=n-1; i--, j++) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
return true;
}
}
37. 解数独(“二维递归”)
N皇后问题 (opens new window)是因为每一行每一列只放一个皇后,只需要一层for循环遍历一列,递归来遍历行,然后一行一列确定皇后的唯一位置。
本题就不一样了,本题中棋盘的每一个位置都要放一个数字(而N皇后是一行只放一个皇后),并检查数字是否合法,解数独的树形结构要比N皇后更宽更深。
递归终止条件
本题递归不用终止条件,解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。
不用终止条件会不会死循环?
递归的下一层的棋盘一定比上一层的棋盘多一个数,等数填满了棋盘自然就终止(填满当然好了,说明找到结果了),所以不需要终止条件!
因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!那么会直接返回, 这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!
判断棋盘是否合法
判断棋盘是否合法有如下三个维度:
- 同行是否重复
- 同列是否重复
- 9宫格里是否重复
class Solution {
public void solveSudoku(char[][] board) {
backtracking(board);
}
public boolean backtracking(char[][] board){
//「一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,
// 一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!」
for (int i = 0; i < 9; i++){ // 遍历行
for (int j = 0; j < 9; j++){ // 遍历列
if (board[i][j] != '.'){ // 跳过原始数字
continue;
}
for (char k = '1'; k <= '9'; k++){ // (i, j) 这个位置放k是否合适
if (isValidSudoku(i, j, k, board)){
board[i][j] = k;
if (backtracking(board)){ // 如果找到合适一组立刻返回
return true;
}
board[i][j] = '.';
}
}
// 9个数都试完了,都不行,那么就返回false
return false;
// 因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!
// 那么会直接返回, 「这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!」
}
}
// 遍历完没有返回false,说明找到了合适棋盘位置了
return true;
}
/**
* 判断棋盘是否合法有如下三个维度:
* 同行是否重复
* 同列是否重复
* 9宫格里是否重复
*/
private boolean isValidSudoku(int row, int col, char val, char[][] board){
// 同行是否重复
for (int i = 0; i < 9; i++){
if (board[row][i] == val){
return false;
}
}
// 同列是否重复
for (int j = 0; j < 9; j++){
if (board[j][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;
}
}