On an N x N `board`, the numbers from `1` to `N*N`are written *boustrophedonically* starting from the bottom left of the board, and alternating direction each row.  For example, for a 6 x 6 board, the numbers are written as follows:

You start on square 1 of the board (which is always in the last row and first column).  Each move, starting from square x, consists of the following:

  • You choose a destination square S with number x+1x+2x+3x+4x+5, or x+6, provided this number is <= N*N.

    • (This choice simulates the result of a standard 6-sided die roll: ie., there are always at most 6 destinations, regardless of the size of the board.)
  • If S has a snake or ladder, you move to the destination of that snake or ladder.  Otherwise, you move to S.

A board square on row r and column c has a "snake or ladder" if board[r][c] != -1.  The destination of that snake or ladder is board[r][c].

Note that you only take a snake or ladder at most once per move: if the destination to a snake or ladder is the start of another snake or ladder, you do not continue moving.  (For example, if the board is [[4,-1],[-1,3]], and on the first move your destination square is 2, then you finish your first move at 3, because you do notcontinue moving to 4.)

Return the least number of moves required to reach square N*N.  If it is not possible, return -1.

Example 1:

Input: [
[-1,-1,-1,-1,-1,-1],
[-1,-1,-1,-1,-1,-1],
[-1,-1,-1,-1,-1,-1],
[-1,35,-1,-1,13,-1],
[-1,-1,-1,-1,-1,-1],
[-1,15,-1,-1,-1,-1]]
Output: 4
Explanation:
At the beginning, you start at square 1 [at row 5, column 0].
You decide to move to square 2, and must take the ladder to square 15.
You then decide to move to square 17 (row 3, column 5), and must take the snake to square 13.
You then decide to move to square 14, and must take the ladder to square 35.
You then decide to move to square 36, ending the game.
It can be shown that you need at least 4 moves to reach the N*N-th square, so the answer is 4.

Note:

  1. 2 <= board.length = board[0].length <= 20
  2. board[i][j] is between 1 and N*N or is equal to -1.
  3. The board square with number 1 has no snake or ladder.
  4. The board square with number N*N has no snake or ladder.

这道题给了一个 NxN 大小的二维数组,从左下角从1开始,蛇形游走,到左上角或者右上角到数字为 NxN,中间某些位置会有梯子,就如同传送门一样,直接可以到达另外一个位置。现在就如同玩大富翁 Big Rich Man 一样,有一个骰子,可以走1到6内的任意一个数字,现在奢侈一把,有无限个遥控骰子,每次都可以走1到6以内指定的步数,问最小能用几步快速到达终点 NxN 位置。博主刚开始做这道题的时候,看是求极值,以为是一道动态规划 Dynamic Programming 的题,结果发现木有办法重现子问题,没法写出状态转移方程,只得作罢。但其实博主忽略了一点,求最小值还有一大神器,广度优先搜索 BFS,最直接的应用就是在迷宫遍历的问题中,求从起点到终点的最少步数,也可以用在更 general 的场景,只要是存在确定的状态转移的方式,可能也可以使用。这道题基本就是类似迷宫遍历的问题,可以走的1到6步可以当作六个方向,这样就可以看作是一个迷宫了,唯一要特殊处理的就是遇见梯子的情况,要跳到另一个位置。这道题还有另一个难点,就是数字标号和数组的二维坐标的转换,这里起始点是在二维数组的左下角,且是1,而代码中定义的二维数组的 (0, 0) 点是在左上角,需要转换一下,还有就是这道题的数字是蛇形环绕的,即当行号是奇数的时候,是从右往左遍历的,转换的时候要注意一下。

难点基本都提到了,现在开始写代码吧,既然是 BFS,就需要用队列 queue 来辅助,初始时将数字1放入,然后还需要一个 visited 数组,大小为 nxn+1。在 while 循环中进行层序遍历,取出队首数字,判断若等于 nxn 直接返回结果 res。否则就要遍历1到6内的所有数字i,则 num+i 就是下一步要走的距离,需要将其转为数组的二维坐标位置,这个操作放到一个单独的子函数中,后边再讲。有了数组的坐标,就可以看该位置上是否有梯子,有的话,需要换成梯子能到达的位置,没有的话还是用 num+i。有了下一个位置,再看 visited 中的值,若已经访问过了直接跳过,否则标记为 true,并且加入队列 queue 中即可,若 while 循环退出了,表示无法到达终点,返回 -1。将数字标号转为二维坐标位置的子函数也不算难,首先应将数字标号减1,因为这里是从1开始的,而代码中的二维坐标是从0开始的,然后除以n得到横坐标,对n取余得到纵坐标。但这里得到的横纵坐标都还不是正确的,因为前面说了数字标记是蛇形环绕的,当行号是奇数的时候,列数需要翻转一下,即用 n-1 减去当前列数。又因为代码中的二维数组起点位置在左上角,同样需要翻转一样,这样得到的才是正确的横纵坐标,返回即可,参见代码如下:

解法一:

class Solution {
public:
int snakesAndLadders(vector<vector<int>>& board) {
int n = board.size(), res = 0;
queue<int> q{{1}};
vector<bool> visited(n * n + 1);
while (!q.empty()) {
for (int k = q.size(); k > 0; --k) {
int num = q.front(); q.pop();
if (num == n * n) return res;
for (int i = 1; i <= 6 && num + i <= n * n; ++i) {
auto pos = getPosition(num + i, n);
int next = board[pos[0]][pos[1]] == -1 ? (num + i) : board[pos[0]][pos[1]];
if (visited[next]) continue;
visited[next] = true;
q.push(next);
}
}
++res;
}
return -1;
}
vector<int> getPosition(int num, int n) {
int x = (num - 1) / n, y = (num - 1) % n;
if (x % 2 == 1) y = n - 1 - y;
x = n - 1 - x;
return {x, y};
}
};

我们也可以让子函数直接返回正确的数字标号,而不是数组的二维坐标,这样的写法虽然解题思路和上面都一样,但击败率要更高一些,参见代码如下:


解法二:

class Solution {
public:
int snakesAndLadders(vector<vector<int>>& board) {
int n = board.size(), res = 0;
queue<int> q{{1}};
vector<bool> visited(n * n + 1);
while (!q.empty()) {
for (int k = q.size(); k > 0; --k) {
int num = q.front(); q.pop();
if (num == n * n) return res;
for (int i = 1; i <= 6 && num + i <= n * n; ++i) {
int next = getBoardValue(board, num + i);
if (next == -1) next = num + i;
if (visited[next]) continue;
visited[next] = true;
q.push(next);
}
}
++res;
}
return -1;
}
int getBoardValue(vector<vector<int>>& board, int num) {
int n = board.size(), x = (num - 1) / n, y = (num - 1) % n;
if (x % 2 == 1) y = n - 1 - y;
x = n - 1 - x;
return board[x][y];
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/909

参考资料:

https://leetcode.com/problems/snakes-and-ladders/

https://leetcode.com/problems/snakes-and-ladders/discuss/174643/C%2B%2B-solutions-Easy-to-understandBFS

https://leetcode.com/problems/snakes-and-ladders/discuss/173682/Java-concise-solution-easy-to-understand

[LeetCode All in One 题目讲解汇总(持续更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)

[LeetCode] 909. Snakes and Ladders 蛇梯棋的更多相关文章

  1. [Swift]LeetCode909. 蛇梯棋 | Snakes and Ladders

    On an N x N board, the numbers from 1 to N*N are written boustrophedonically starting from the botto ...

  2. 【leetcode】909. Snakes and Ladders

    题目如下: 解题思路:天坑题,不在于题目多难,而是要理解题意.题目中有两点要特别注意,一是“You choose a destination square S with number x+1, x+2 ...

  3. [lightoj P1151] Snakes and Ladders

    1151 - Snakes and Ladders Time Limit: 2 second(s)    Memory Limit: 32 MB 'Snakes and Ladders' or 'Sh ...

  4. Snakes and Ladders LightOJ - 1151( 概率dp+高斯消元)

    Snakes and Ladders LightOJ - 1151 题意: 有100个格子,从1开始走,每次抛骰子走1~6,若抛出的点数导致走出了100以外,则重新抛一次.有n个格子会单向传送到其他格 ...

  5. LightOJ - 1151 Snakes and Ladders —— 期望、高斯消元法

    题目链接:https://vjudge.net/problem/LightOJ-1151 1151 - Snakes and Ladders    PDF (English) Statistics F ...

  6. [LeetCode] 348. Design Tic-Tac-Toe 设计井字棋游戏

    Design a Tic-tac-toe game that is played between two players on a n x n grid. You may assume the fol ...

  7. [LeetCode] Valid Tic-Tac-Toe State 验证井字棋状态

    A Tic-Tac-Toe board is given as a string array board. Return True if and only if it is possible to r ...

  8. light oj 1151 - Snakes and Ladders 高斯消元+概率DP

    思路: 在没有梯子与蛇的时候很容易想到如下公式: dp[i]=1+(∑dp[i+j])/6 但是现在有梯子和蛇也是一样的,初始化p[i]=i; 当有梯子或蛇时转移为p[a]=b; 这样方程变为: dp ...

  9. LightOJ 1151 Snakes and Ladders(概率DP + 高斯消元)

    题意:1~100的格子,有n个传送阵,一个把进入i的人瞬间传送到tp[i](可能传送到前面,也可能是后面),已知传送阵终点不会有另一个传送阵,1和100都不会有传送阵.每次走都需要掷一次骰子(1~6且 ...

随机推荐

  1. Kubernetes容器集群管理环境 - Node节点的移除与加入

    一.如何从Kubernetes集群中移除Node比如从集群中移除k8s-node03这个Node节点,做法如下: 1)先在master节点查看Node情况 [root@k8s-master01 ~]# ...

  2. 线代第六章定义&定理整理(持续更新中)

    Chapter 6 6.1 Inner Products and Norms Definition (inner product). Let V be a vector space over F. A ...

  3. Kubernetes DaemonSet(部署守护进程)

    Kubernetes DaemonSet(部署守护进程) • 在每一个Node上运行一个Pod• 新加入的Node也同样会自动运行一个Pod 应用场景:Agent 官方文档:https://kuber ...

  4. SqlServer 创建数据库两种方式

    一个SqlServer 数据库实例大概可以创建三万多个数据库. 创建数据库的第一种方式:SqlServer Management Studio管理工具进行可视化创建. 1).打开数据库管理工具,在&q ...

  5. codeforces #592(Div.2)

    codeforces #592(Div.2) A Pens and Pencils Tomorrow is a difficult day for Polycarp: he has to attend ...

  6. 简单聊聊实时视频rtmp

    背景: 由于经常接触实时视频, 对实时视频略有了解. 实时视频是将视频流实时上传到服务器端进行解析, 由RTMP服务器处理. RTMP 服务器 自己动手搭建一个rtmp, 本文在 Linux环境中搭建 ...

  7. day 36

    目录 pymysql操作mysql 安装 连接 增 删 改 查 索引 为什么使用索引以及索引的作用 类比 索引的本质 索引的底层原理 索引的种类(重点) 主键索引 唯一索引 普通索引 索引的创建 主键 ...

  8. angular6 使用信息提示框toast

    angular6 可以使用的toast插件有好多个,在目前来看ngx-toastr在过去一年时间的使用量和受欢迎程度可以说是一骑绝尘,如下图: 我也就选择了ngx-toastr这个插件,使用步骤如下: ...

  9. Microsoft Surface 2019新品发布会汇总

    Microsoft Surface 2019 新品发布会汇总 10月2日晚,微软举行了Microsoft Surface 2019秋季新品发布会,本次发布会涉及如下设备内容等: SurfaceLapt ...

  10. JVM 对象查询语言(OQL)[转载]

    最近生产环境出现一个很奇怪的问题,测试环境无法重现,本地直连生产无法重现.于是用上 jmap + Java VisualVM 的 OQL (Object Query Language) 分析问题. 关 ...