代码随想录算法训练营第30天 | 332、51、37

文章介绍了使用回溯算法解决332.重新安排行程和51.N皇后问题的思路与解法,强调了皇后问题的对角线判断以及行程规划中的环形路径处理。解题过程中涉及了递归、字典序排序和状态保存等关键点。

332. 重新安排行程

题目描述

给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
示例1:
输入:tickets=[["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]tickets=[["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
输出:["JFK","MUC","LHR","SFO","SJC"]["JFK","MUC","LHR","SFO","SJC"]["JFK","MUC","LHR","SFO","SJC"]
示例2:
输入:tickets=[["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]tickets=[["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
输出:["JFK","ATL","JFK","SFO","ATL","SFO"]["JFK","ATL","JFK","SFO","ATL","SFO"]["JFK","ATL","JFK","SFO","ATL","SFO"]

思路

震惊,竟然已经进阶到困难题了吗???
光读题就读了三遍才看明白想让我求啥,说实话要不是这题放在回溯这我真的会一眼认定这是一道图论深搜题。要素过多,没啥想法,直接看答案思路:
首先分解本题的几个关键点:
1、一个行程中可能会出现环
2、字母排序
3、回溯的跳出条件
4、一个起点与多个终点的对应关系表达
真的很难,看答案也只能说,看完了之后觉得,啊真有道理,好巧妙,但我觉得自己想是想不到的。而且我真的好不喜欢这里面用map的解法啊,所以就po一个解法。

解法

class Solution {
    LinkedList<String> res;
    LinkedList<String> path = new LinkedList<>();
    public List<String> findItinerary(List<List<String>> tickets) {
        Collections.sort(tickets,(a,b) -> a.get(1).compareTo(b.get(1)));
        path.add("JFK");
        boolean[] used = new boolean[tickets.size()];
        helper((ArrayList)tickets,used);
        return res;
    }
    public boolean helper(ArrayList<List<String>> tickets,boolean[] used){
        if(path.size() == tickets.size() + 1){
            res = new LinkedList(path);
            return true;
        }
        for(int i = 0;i< tickets.size();i++){
            if(!used[i] && tickets.get(i).get(0).equals(path.getLast())){
                path.add(tickets.get(i).get(1));
                used[i] = true;
                if(helper(tickets,used)){
                    return true;
                }
                used[i] = false;
                path.removeLast();
            }
        }
        return false;
    }
}

总结

不是我等凡人应该做的题,可以说是完全想不到,官方答案用的是深搜,过段时间入门了深搜之后再回头看看吧。

51. N皇后

题目描述

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
示例1:
输入:n=4n = 4n=4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]][[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]][[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
示例2:
输入:n=1n = 1n=1
输出:[["Q"]][["Q"]][["Q"]]

思路

皇后问题可以说是回溯的经典应用之一了,但之前一直听说,一直都没有尝试,今天一试只能说它配得上困难的等级。但和332不同,本题看了之后会有一个基本的思路。
这里可以将N皇后问题拆解成一下几个部分:
1、判断是否应该放置皇后,需要注意的是,所需要判断的除了行、列、主对角线之外,还有135°的副对角线,这个是很容易遗漏的。
2、回溯的过程中跳出递归的条件设置,这里因为是以行为主的放置,意思就是,每行肯定只能放一个,那么就一行一行放,所以当行数与n相等时就可以跳出了。同时,因为此时是以行为主的放置,故而在判断函数中可以不进行行的判断。
3、递归循环中应该如何保存,这个我认为是一个比较麻烦的点。因为这里面需要操作字符串、需要判断位置是否可以放置,需要保存结果。
基本思路有一些,但是距离写出来还是有一些差距,答案我感觉还是很巧妙的,尤其是45°和135°判定的地方,真的很有思考量!!!

解法1

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,'.');
        }
        helper(n,0,chessboard);
        return res;
    }
    public void helper(int n,int row,char[][] chessboard){
        if(row == n){
            res.add(arrayToList(chessboard));
            return;
        }
        for(int col = 0;col < n;col++){
            if(isValid(row,col,n,chessboard)){
                chessboard[row][col] = 'Q';
                helper(n,row+1,chessboard);
                chessboard[row][col] = '.';
            }
        }
    }
    public List arrayToList(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;
            }
        }
        for(int i = row-1, j = col-1;i >= 0 && j >= 0;i-- , j--){
            if(chessboard[i][j] == 'Q'){
                return false;
            }
        }
        for(int i = row-1,j = col+1;i>=0 && j<=n-1;i--,j++){
            if(chessboard[i][j] == 'Q'){
                return false;
            }
        }
        return true;
    }
}

解法2

class Solution {
    List<List<String>> res = new ArrayList<>();
    boolean[] usedCol, usedDiag45, usedDiag135;
    public List<List<String>> solveNQueens(int n) {
        usedCol = new boolean[n];
        usedDiag45 = new boolean[2*n -1];
        usedDiag135 = new boolean[2*n -1];
        int[] board = new int[n];
        helper(board,n,0);
        return res;
    }
    public void helper(int[] board,int n,int row){
        if(row == n){
            List<String> tmp = new ArrayList<>();
            for(int i : board){
                char[] str = new char[n];
                Arrays.fill(str,'.');
                str[i] = 'Q';
                tmp.add(new String(str));
            }
            res.add(tmp);
            return;
        }
        for(int col = 0;col < n;col++){
            if(usedCol[col]|usedDiag45[row+col]|usedDiag135[row-col+n-1]){
                continue;
            }
            board[row] = col;
            usedCol[col] = true;
            usedDiag45[col+row] = true;
            usedDiag135[row-col+n-1] = true;
            helper(board,n,row+1);
            usedCol[col] = false;
            usedDiag45[row+col] = false;
            usedDiag135[row-col+n-1] = false;
        }
    }
}

总结

好好看,好好学。(但感觉好想比332稍微简单那么一点,这是可以说的吗

37. 解数独

题目描述

编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
示例就不写了,数独大家应该也都玩过。

思路

本题能看出是N皇后的一个升级版,升级就升级在了需要填入的元素变多了,同时,判定的方法也更加复杂了。但基本思路还是差不多的,不过需要重点说一下的是那个九宫格判重的地方,一定要说,真的很巧妙,完全想不到,思考量拉满了。

解法

class Solution {
    public void solveSudoku(char[][] board) {
        helper(board);
    }
    public boolean helper(char[][] board){
        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++){
                    if(isValid(i,j,k,board)){
                        board[i][j] = k;
                        if(helper(board)){
                            return true;
                        }
                        board[i][j] = '.';
                    }
                }
                return false;
            }
        }
        return true;
    }
    public boolean isValid(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;
            }
        }
        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;
    }
}

总结

好好看,好好学。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值