332. 重新安排行程
给你一份航线列表 tickets
,其中 tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
- 例如,行程
["JFK", "LGA"]
与["JFK", "LGB"]
相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
示例 1:
输入:tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]] 输出:["JFK","MUC","LHR","SFO","SJC"]
本题用回溯法,遍历所有的票的使用顺序,由于所有机票至少存在一种合理的行程,我们先将票按照起始位置的开头字母小的排序,递归过程中找到其中一种合理行程就返回,该行程一定是字典排序更小的行程。
递归三部曲:
1.确定返回值和参数的类型
使用全局变量保存结果 ,参数传入所有的票(List<List<String>> tickets),和每张票的使用情况(used[])
由于只取第一个结果,所有返回值类型设置为boolean类型,当找到第一个结果时,就返回true
List<String> path=new LinkedList<>();List<String> result;
boolean travel(List<List<String>> tickets,boolean used[])
2.确定递归结束条件
当记录路径的数组的长度==票的长度+1,说明合理用完了所有票,找到了合理旅游路径,结束递归
if(path.size()==tickets.size()+1){result=new ArrayList<>(path);return true;}
3.单层递归逻辑
递归找到所有方案
for(int i=0;i<tickets.size();i++){if(!used[i]&&path.get(path.size()-1).equals(tickets.get(i).get(0))){path.add(tickets.get(i).get(1));used[i]=true;//找到第一个方案,结束递归if( travel(tickets,used))return true;path.remove(path.size()-1);used[i]=false;}}return false;
总体代码:
class Solution {List<String> path=new LinkedList<>();List<String> result;public List<String> findItinerary(List<List<String>> tickets) {boolean[] used=new boolean[tickets.size()];//给票排序Collections.sort(tickets,(a,b)->a.get(1).compareTo(b.get(1)));path.add("JFK");travel(tickets,used);return result;}boolean travel(List<List<String>> tickets,boolean used[]){if(path.size()==tickets.size()+1){result=new ArrayList<>(path);return true;}for(int i=0;i<tickets.size();i++){if(!used[i]&&path.get(path.size()-1).equals(tickets.get(i).get(0))){path.add(tickets.get(i).get(1));used[i]=true;//找到第一个方案,结束递归if( travel(tickets,used))return true;path.remove(path.size()-1);used[i]=false;}}return false;}
}
使用本方法因为排序的原因会出现超时
改进方法:
用Map<出发机场, Map<到达机场, 航班次数>> map来记录车票,Map<到达机场, 航班次数>为升序TreeMap
class Solution {private Deque<String> res;private Map<String, Map<String, Integer>> map;private boolean backTracking(int ticketNum){if(res.size() == ticketNum + 1){return true;}String last = res.getLast();if(map.containsKey(last)){//防止出现nullfor(Map.Entry<String, Integer> target : map.get(last).entrySet()){int count = target.getValue();if(count > 0){res.add(target.getKey());target.setValue(count - 1);if(backTracking(ticketNum)) return true;res.removeLast();target.setValue(count);}}}return false;}public List<String> findItinerary(List<List<String>> tickets) {map = new HashMap<String, Map<String, Integer>>();res = new LinkedList<>();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<>();//升序Maptemp.put(t.get(1), 1);}map.put(t.get(0), temp);}res.add("JFK");backTracking(tickets.size());return new ArrayList<>(res);}
}
本题难在容器的选择
51. N 皇后 - 力扣(LeetCode)
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
Example 1:
Input: n = 4 Output: [[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] 解释:如上图所示,4 皇后问题存在两个不同的解法。
皇后们的约束条件:
- 不能同行
- 不能同列
- 不能同斜线
Boolean isVaild(char[][] chessboard,int row,int col,int n){//把皇后放在n*n的棋盘的(row,col)位置//检查列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;}
思路:用回溯法,遍历出所有可以放置的可能性
递归三部曲:
1.确定返回值和参数类型
要把所有能摆放的位置都得出,用全局变量public List<List<String>> result=new ArrayList<>();记录所有合理的棋盘,返回值为void,传入棋盘(char[][] chessboard),要放置的皇后在哪一行(int row),棋盘的宽度(n);
public void backTracking(int n,int row,char[][] chessboard)
2.确定递归结束条件
当row==n时,说明所有行都放置了皇后,找到了一种合理放法,将该棋盘存入result中,递归结束
if(row==n){
List<String> temp= array2List(chessboard);
result.add(temp);
return;
}
3.确定单层递归逻辑
遍历该行的每一个位置并检查其合理性,如果合理,进入下一行棋盘的摆放
for(int i=0;i<n;i++){
//如果当前位置合法,就递归放下一行
if(isVaild(chessboard,row,i,n)){
chessboard[row][i]='Q';
backTracking(n,row+1,chessboard);
chessboard[row][i]='.';
}
}
总体代码:
class Solution {public List<List<String>> result=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 result;}public void backTracking(int n,int row,char[][] chessboard){if(row==n){List<String> temp= array2List(chessboard);result.add(temp);return;}for(int i=0;i<n;i++){//如果当前位置合法,就递归放下一行if(isVaild(chessboard,row,i,n)){chessboard[row][i]='Q';backTracking(n,row+1,chessboard);chessboard[row][i]='.';}}}//将数组转为listList<String> array2List(char[][] chessboard){List<String> result=new ArrayList<>();for(int i=0;i<chessboard.length;i++){StringBuilder temp=new StringBuilder();for(int j=0;j<chessboard[0].length;j++){temp.append(chessboard[i][j]);}result.add(temp.toString());}return result;}Boolean isVaild(char[][] chessboard,int row,int col,int n){//把皇后放在n*n的棋盘的(row,col)位置//检查列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. 解数独
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.'
表示。
示例 1:
输入: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"]] 解释:输入的数独如上图所示,唯一有效的解决方案如下所示:
思路:n皇后问题一行只需要放一个皇后,本题一行可能填好几个数,所以本题是二维递归 ,
一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!
判断棋盘是否合法
判断棋盘是否合法有如下三个维度:
- 同行是否重复
- 同列是否重复
- 9宫格里是否重复
Boolean isVaild(int row,int col,char val,char[][] board){//同行是否重复for(int i=0;i<9;i++){if(board[row][i]==val){return false;}}//同列是否重复for(int i=0;i<9;i++){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;}
递归三部曲:
1.确定返回值和参数类型
只有一个解,只需要找一个解,返回类型为boolean,当回溯返回true时,结束当前递归
Boolean backTracking(char[][] board)
当有多个解时,返回类型为void,需要找遍所有可能
传入棋盘char[][] board
2.确定递归结束条件
本题递归不用终止条件,解数独是要遍历整个树形结构寻找可能的叶子节点就立刻返回。
递归的下一层的棋盘一定比上一层的棋盘多一个数,等数填满了棋盘自然就终止(填满当然好了,说明找到结果了),所以不需要终止条件!
3.确定当层逻辑
一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!
class Solution {public void solveSudoku(char[][] board) {backTracking(board);}Boolean backTracking(char[][] board){for (int i = 0; i < 9; i++){ // 遍历行for (int j = 0; j < 9; j++){ // 遍历列if (board[i][j] != '.'){ // 跳过原始数字continue;}//找到没填数字的空位,在这个空位填1--9for(char k='1';k<='9';k++){if(isVaild(i,j,k,board)){board[i][j]=k;if(backTracking(board))return true;board[i][j]='.';//}}return false;//9个数都填了,都不对,返回false}}//遍历完没有返回false,说明找到了合适棋盘位置了return true;}Boolean isVaild(int row,int col,char val,char[][] board){//同行是否重复for(int i=0;i<9;i++){if(board[row][i]==val){return false;}}//同列是否重复for(int i=0;i<9;i++){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;}
}
思路:本题与上一题思路一致, 一个for循环遍历数组的行,一个for循环遍历数组的列,一行一列确定下来之后,遍历所有符号得出这个位置的数!
代码参考:
import com.sun.org.apache.bcel.internal.generic.NEW;import java.util.*;public class Main {static int[][] result = new int[5][5];static int sum = 0;static char[] f = {'&', '|', '^'};public static void main(String args[]) {int[] A = {1, 0, 1, 0, 1};for (int i = 1; i < result.length; i++)Arrays.fill(result[i], -1);result[0] = A;backTracking(result);System.out.println(sum);}public static void backTracking(int[][] result) {if (result[4][0] != -1) {if (result[4][0] == 1)sum++;result[4][0] = -1;return;}for (int k = 1; k < 5; k++) {for (int m = 0; m < 5 - k; m++) {//找到没填数字的空位if (result[k][m] != -1)continue;
//遍历所有符号,填上对应的数for (int n = 0; n < f.length; n++) {result[k][m] = fun(f[n], result[k - 1][m], result[k - 1][m + 1]);backTracking(result);result[k][m] = -1;}return;//遍历完当前所有符号,
//表示这个位置填某一个符合的计算结果情况已经遍历完了,
//返回上一层,将上一个数修改}}}public static int fun(char a, int b, int c) {if (a == '&') {return b & c;} else if (a == '|') {return b | c;} elsereturn b ^ c;}}