【面试经典 150 | 图的广度优先搜索】蛇梯棋

写在前面

本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……

专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:

  • Tag:介绍本题牵涉到的知识点、数据结构;
  • 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
  • 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
  • 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
  • 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。

Tag

【图】【广度优先搜索】


题目来源

909. 蛇梯棋


解题思路

方法一:广度优先搜索

思路

我们可以将该棋盘看作是一个包含 N 2 N^2 N2 个节点的有向图。图中的每个节点 x 可以通过掷骰子得到的数值(1-6)指向一些节点,即 xx + i 1 ≤ i ≤ 6 1 \le i \le 6 1i6)之间有一条有向边。如果 x + i 格子上有蛇或者梯子,则可以直接将 x + i 和蛇或者梯子的终点 y 相连,即 x + iy 之间有一条有向边。

如此一来,原问题就变成了求出从 1 N 2 N^2 N2 的最短路问题。

对于该问题,我们可以使用广度优先搜索。将节点编号和到达该节点的移动次数作为搜索状态,顺着该节点的出边扩展新状态,直至到达终点 N 2 N^2 N2,返回此时的移动次数。若无法到达终点则返回 −1。

具体地,使用一个队列存储搜索状态,将初始状态 ( 1 , 0 ) (1, 0) (1,0) 加入队列,表示当前位于起始点 1,移动次数为 0。然后不断取出队首,每次取出队首元素时扩展新状态,即遍历该节点的出边,若出边对应节点未被访问,则将该节点和移动次数加一的结果作为新状态,加入队列。如此循环直至到达终点或队列为空。

id2rc

需要注意的是,我们还要实现一个函数:将网格标号转化成对应的行号和列号。我们队列中存储的是网格的编号,在根据骰子掷出的结果移动到相应的格点时,我们需要根据格点的值判断是否有蛇或梯子的存在从而更新到达的格点的位置。此时我们需要将网格标号转化成对应的行号和列号。

该函数实现起来相对容易,将编号 (id - 1) / n 即可得到对应行 r(id - 1) % n 得到对应列 c。注意本题是从左下角编号为 1,以 S 行更新编号的,所有还需要修改编号对应的行号。对应的更新行 r = n - 1 - r。奇数行的列需要更新 c = n - 1 - c

代码

class Solution {
private:
    // 将网格标号转化成对应的行号和列号
    pair<int, int> id2rc(const int id, const int n) {
        int r = (id - 1) / n, c = (id - 1) % n;
        if (r & 1) {    // 奇数行
            c = n - 1 - c;
        }
        return {n - 1 - r, c};
    }
public:
    int snakesAndLadders(vector<vector<int>>& board) {
        int n = board.size();
        vector<int> visited(n * n + 1); // 标记位置 1-n*n 是否被访问过
        queue<pair<int, int>> que;
        que.emplace(1, 0);    // 从网格 1 出发到达 1 的最小距离为 0
        while (!que.empty()) {
            auto p = que.front(); que.pop();
            for (int i = 1; i <= 6; ++i) {  // 掷骰子更新位置
                int nx = p.first + i;
                if (nx > n * n) { // 超出边界
                    break;
                }
                auto rc = id2rc(nx, n);
                if (board[rc.first][rc.second] > 0) {   // 存在梯子或蛇
                    nx = board[rc.first][rc.second];
                }
                if (nx == n * n) {  // 到达终点
                    return p.second + 1;
                }
                if (!visited[nx]) {
                    visited[nx] = 1;
                    que.emplace(nx, p.second + 1);
                }
            }
        }
        return -1;
    }
};

复杂度分析

时间复杂度: O ( n 2 ) O(n^2) O(n2) n n n 为棋盘 board 的边长。棋盘的每个格子至多入队一次。

空间复杂度: O ( n 2 ) O(n^2) O(n2)。我们需要 O ( n 2 ) O(n^2) O(n2) 的空间来存储每个格子是否被访问过。


写在最后

如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度的方法,欢迎评论区交流。

最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wang_nn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值