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)){//防止出现null
for(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<>();//升序Map
temp.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]='.';
}
}
}
//将数组转为list
List<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--9
for(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;
}
}