day30 | LeetCode 332.重新安排行程、LeetCode 51.N皇后、LeetCode 37.解数独

332.重新安排行程

本题是求解欧拉回路 / 欧拉通路的题目。

我们化简本题题意:给定一个 n 个点 m 条边的图,要求从指定的点出发,经过所有的边恰好一次可以理解为给定起点的「一笔画」问题。本体的附加的要求是这个一笔画的路径需要是路径的字典序最小。

一笔画问题与欧拉图或者半欧拉图有着紧密的联系,下面给出定义:

  • 通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉通路;
  • 通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路;
  • 具有欧拉回路的无向图称为欧拉图;
  • 具有欧拉通路但不具有欧拉回路的无向图称为半欧拉图。

因为本题保证至少存在一种合理的路径,们只需要输出这条欧拉通路的路径即可。如果没有说明肯定存在答案,那么这题又会变得更加复杂。

别看名字唬人,但这题其实思路挺清晰的,先用邻接表建图,然后在图中深搜,搜索过程中将走过的边(邻接关系)删除。

这个算法有个名字,叫 hierholzer 算法。用于解决已知图中存在欧拉路径,要找出一个欧拉路径的问题。过程如下:

  • 任选一个点为起点(题目告诉你了),遍历它所有邻接的边(设置不同的分支)。
  • DFS 搜索,访问邻接的点,并且将走过的边(邻接关系)删除。
  • 如果走到的当前点,已经没有相邻边了,则将当前点推入 res。
  • 随着递归的出栈,点不断推入 res 的开头,最后就得到一个从起点出发的欧拉路径。
class Solution {
public:
    unordered_map<string, vector<string>> graph; // 存储机场与可飞往的目的地列表
    vector<string> result; // 存储最终的旅行路径

    void dfs(const string& airport) {
        auto& destinations = graph[airport]; // 获取当前机场的目的地列表
        while (!destinations.empty()) { // 只要还有目的地就继续
            string next = destinations.back(); // 获取字典序最小的目的地
            destinations.pop_back(); // 移除这个目的地
            dfs(next); // 递归访问下一个目的地
        }
        result.push_back(airport); // 在返回过程中构建路径
    }

    vector<string> findItinerary(vector<vector<string>>& tickets) {
        // 构建图
        for (auto& ticket : tickets) {
            graph[ticket[0]].push_back(ticket[1]);
        }
        // 对每个机场的目的地列表按字典序降序排序,以便在DFS中按升序处理
        for (auto& pair : graph) {
            sort(pair.second.rbegin(), pair.second.rend());
        }
        // 开始深度优先搜索
        dfs("JFK");
        // 因为添加是在返回过程中完成的,所以要反转结果列表
        reverse(result.begin(), result.end());
        return result;
    }
};

51. N皇后

这题大体思路和以前的是差不多的,但实现细节出现了问题,我最开始是采用的思路是用 used 数组从上往下标记不能放的位置,for 循环内每放置一个皇后就将该行以下的不能放的位置标记,然后递归到下一层遍历。

但实现这个思路的时候发现有问题,我不好维护这个 used 数组,因为涉及回溯,这个数组很难回溯递归。

看了卡哥的题解,发现卡哥的判断是否合法是从当前位置往上看的,向上找两个斜角线上是否有皇后。

学习到了。

class Solution {
public:
    vector<vector<string>> result;
    // n 为输入的棋盘大小
    // row 是当前递归到棋盘的第几行了
    void backtracking(int n, int row, vector<string>& chessboard) {
        if (row == n) {
            result.push_back(chessboard);
            return;
        }
        for (int col = 0; col < n; col++) {
            if (isValid(row, col, chessboard, n)) { // 验证合法就可以放
                chessboard[row][col] = 'Q'; // 放置皇后
                backtracking(n, row + 1, chessboard);
                chessboard[row][col] = '.'; // 回溯,撤销皇后
            }
        }
    }
    bool isValid(int row, int col, vector<string>& chessboard, int n) {
        // 检查列
        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; i--, j++) {
            if (chessboard[i][j] == 'Q') {
                return false;
            }
        }
        return true;
    }
    public:
        vector<vector<string>> solveNQueens(int n) {
            result.clear();
            std::vector<std::string> chessboard(n, std::string(n, '.'));
            backtracking(n, 0, chessboard);
            return result;
        }
};

37. 解数独

这题递归思路有,卡在判断是否合法了,交给二刷的我去思考了。我就直接看题解了。

class Solution {
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);
    }
};

  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值