LeetCode题解 回溯(五):332 重新安排行程;51 N皇后;37 解数独 —— hard三连

332 重新安排行程 hard

给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。

所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。

例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。

这道题目 有点儿像图论里的深度遍历搜索,但实际上回溯也可以解决。

难点在于,选择目标地点时需要有序,最好的解决办法是交给容器自己处理;

第二个难点是如何处理环形路径,方法就是“去重”,即统计每一个机场的目的机场出现的次数,如果目标机场已经不能被选中,那就可以考虑其他的机场了。

另外需要注意的是,本题中只需要找到一个符合要求的路径就可以了,所以一旦找到就可以直接退出

统计所有始发机场和目标机场,可以使用unordered_map<string, map<string, int>>,表示[始发机场,[目标机场,航班次数]],map结构本身就是有序的,可以解决我们手动排序的麻烦,用此结构来表示所有可以候选的飞行方向。

当航班次数大于0的时候,表示还可以飞;

单层递归的时候,需要从当前结果中最后一个机场出发,在候选表中找到该机场,遍历其目标机场(用range-for,就可以保证一定会从自然排序靠前的机场开始考虑)

放在路径中的是候选飞行表中的目标机场(即map中的string),然后可到达次数减一(即map中的int),然后继续遍历。

当遍历路径中的数组数量等于给定数组大小 + 1的时候,表示可以结束,已经找到一条合适的路径。

综上所述,代码如下:

unordered_map<string, map<string, int>> candidates;

    bool reback(int ticksNum, vector<string>& res) {
    
    
        if (res.size() == ticksNum + 1) return true;

        for (pair<const string, int>& destination: candidates[res[res.size() - 1]]) {
    
    
            if (destination.second > 0) {
    
    
                res.push_back(destination.first);
                destination.second--;
                if (reback(ticksNum, res)) return true;
                destination.second++;
                res.pop_back();
            }
        }
        return false;
    }

    vector<string> findItinerary(vector<vector<string>>& tickets) {
    
    
        
        vector<string> res;
        for (vector<string> ticket: tickets) {
    
    
            candidates[ticket[0]][ticket[1]]++;
        }
        res.push_back("JFK");
        reback(tickets.size(), res);

        return res;
    }

51 N皇后 hard

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

N皇后问题并不难解。

第一个问题是,皇后棋子所在位置符合规矩:同行,同列,同一对角线都不能存在重复的棋子,因此每次尝试放一个棋子的时候,都要判断是否合适。

其他的,和普通的回溯没什么区别,每一行放完之后,就要从第二行开始放,这样会高效一些;

题目要求的是返回所有组合

private:
    vector<vector<string>> result;

    void reback(int n, int row, vector<string>& chessboard) {
    
    
        if (row == n) {
    
    
            result.push_back(chessboard);
            return;
        }
        for (int col = 0; col < n; col++) {
    
    
            if (isVaild(row, col, chessboard, n)) {
    
    
                chessboard[row][col] = 'Q';
                reback(n, row + 1, chessboard);
                chessboard[row][col] = '.';
            }
        }
    }

    bool isVaild(int row, int col, vector<string>& chessboard, int n) {
    
    
        // 检查同列
        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; i--, j++) {
    
    
            if (chessboard[i][j] == 'Q')
                return false;
        }

        return true;
    }

public:
    vector<vector<string>> solveNQueens(int n) {
    
    
        
        vector<string> chessboard(n, string(n, '.'));

        reback(n, 0, chessboard);

        return result;
    }

37 解数独 hard

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则:

  • 数字 1-9 在每一行只能出现一次。

  • 数字 1-9 在每一列只能出现一次。

  • 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

数独怎么玩大家应该都知道,但是如何让程序完成,也属于回溯的领域。

本题要递归的深度更甚,因为每一次都是需要遍历每一行,每一列,和们实际玩儿数独的时候的思路很像。

每次遍历的时候,同样需要验证合理性。

整体代码如下,直接参考随想录给出的代码:

private:
    bool backtracking(vector<vector<char>>& board) {
    
    
        for (int i = 0; i < board.size(); i++) {
    
            // 遍历行
            for (int j = 0; j < board[0].size(); j++) {
    
     // 遍历列
                if (board[i][j] == '.') {
    
    
                    for (char k = '1'; k <= '9'; k++) {
    
         // (i, j) 这个位置放k是否合适
                        if (isValid(i, j, k, board)) {
    
    
                            board[i][j] = k;                // 放置k
                            if (backtracking(board)) return true; // 如果找到合适一组立刻返回
                            board[i][j] = '.';              // 回溯,撤销k
                        }
                    }
                    return false;  // 9个数都试完了,都不行,那么就返回false 
                }                
            }
        }
        return true; // 遍历完没有返回false,说明找到了合适棋盘位置了
    }
    bool isValid(int row, int col, char val, vector<vector<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++) {
    
     // 判断9方格里是否重复
            for (int j = startCol; j < startCol + 3; j++) {
    
    
                if (board[i][j] == val ) {
    
    
                    return false;
                }
            }
        }
        return true;
    }
public:
    void solveSudoku(vector<vector<char>>& board) {
    
    
        backtracking(board);
    }

猜你喜欢

转载自blog.csdn.net/qq_41205665/article/details/128705729