909. 蛇梯棋
问题描述
给你一个大小为 n x n
的整数矩阵 board
,方格按从 1
到 n2
编号,编号遵循 转行交替方式 ,从左下角开始 (即,从 board[n - 1][0]
开始)每一行交替方向。
玩家从棋盘上的方格 1
(总是在最后一行、第一列)开始出发。
每一回合,玩家需要从当前方格 curr
开始出发,按下述要求前进:
- 选定目标方格
next
,目标方格的编号符合范围[curr + 1, min(curr + 6, n2)]
。该选择模拟了掷 六面体骰子 的情景,无论棋盘大小如何,玩家最多只能有 6 个目的地。 - 传送玩家:如果目标方格
next
处存在蛇或梯子,那么玩家会传送到蛇或梯子的目的地。否则,玩家传送到目标方格next
。 - 当玩家到达编号
n2
的方格时,游戏结束。
r
行 c
列的棋盘,按前述方法编号,棋盘格中可能存在 “蛇” 或 “梯子”;如果 board[r][c] != -1
,那个蛇或梯子的目的地将会是 board[r][c]
。编号为 1
和 n2
的方格上没有蛇或梯子。
注意,玩家在每回合的前进过程中最多只能爬过蛇或梯子一次:就算目的地是另一条蛇或梯子的起点,玩家也 不能 继续移动。
- 举个例子,假设棋盘是
[[-1,4],[-1,3]]
,第一次移动,玩家的目标方格是2
。那么这个玩家将会顺着梯子到达方格3
,但 不能 顺着方格3
上的梯子前往方格4
。
返回达到编号为 n2
的方格所需的最少移动次数,如果不可能,则返回 -1
。
示例 1:
输入:board = [[-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]]
输出:4
解释:
首先,从方格 1 [第 5 行,第 0 列] 开始。
先决定移动到方格 2 ,并必须爬过梯子移动到到方格 15 。
然后决定移动到方格 17 [第 3 行,第 4 列],必须爬过蛇到方格 13 。
接着决定移动到方格 14 ,且必须通过梯子移动到方格 35 。
最后决定移动到方格 36 , 游戏结束。
可以证明需要至少 4 次移动才能到达最后一个方格,所以答案是 4 。
示例 2:
输入:board = [[-1,-1],[-1,3]]
输出:1
提示:
n == board.length == board[i].length
2 <= n <= 20
grid[i][j]
的值是-1
或在范围[1, n2]
内- 编号为
1
和n2
的方格上没有蛇或梯子
解题思路与代码实现
主要思路:
读懂题意,求从起点开始,每次能探索[next + 1, Math.min(next + 6, n*n)]
的编号范围,遇到蛇和梯子(在board矩阵中对应值不为-1)可以进行跳转,但不能连续跳,求到达终点的最少步数。
开始时起点1入队,然后进行BFS
,每次探索[next + 1, Math.min(next + 6, n*n)]
的编号范围,如果未越界且未曾访问过,则入队。当访问到终点n*n
时,返回当前的步长;如果无法返回到终点,返回-1。
class Solution {
public int snakesAndLadders(int[][] board) {
int n = board.length; // 矩阵边长n
LinkedList<int[]> queue = new LinkedList<>(); // 辅助队列
boolean[] visited = new boolean[n * n + 1]; // 标记数组
queue.offer(new int[] { 1, 0 }); // 起点入队,元素依次为起点所在方格的编号和已花费的步长
visited[1] = true; // 标记已访问
// BFS
while (!queue.isEmpty()) {
int[] cur = queue.poll();
// 搜索范围
for (int i = 1; i <= 6; i++) {
int next = cur[0] + i;
if (next > n * n) { // 越界
break;
}
int[] points = getPoint(next, n); // 转(x, y)坐标
if (board[points[0]][points[1]] != -1) { // 碰到了梯子则进行跳跃
next = board[points[0]][points[1]];
}
if (next == n * n) { // 到达终点n*n
return cur[1] + 1;
}
if (!visited[next]) {
// 未访问过的则加入队列,步数+1
visited[next] = true;
queue.offer(new int[] { next, cur[1] + 1 });
}
}
}
return -1;
}
/**
* index编号转(x, y)坐标
*/
private int[] getPoint(int index, int n) {
int row = n - 1 - (index - 1) / n; // 从上往下哪一行
int col = (index - 1) % n; // 从左往右哪一列
// 该行需要逆序,(index - 1) / n表示从下往上数的行号,从0开始计数
if ((index - 1) / n % 2!= 0) {
col = n - 1 - col;
}
// 返回(x, y)坐标
return new int[] { row, col };
}
}
踩坑点
无
127. 单词接龙
问题描述
字典 wordList
中从单词 beginWord
到 endWord
的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk
:
- 每一对相邻的单词只差一个字母。
- 对于
1 <= i <= k
时,每个si
都在wordList
中。注意,beginWord
不需要在wordList
中。 sk == endWord
给你两个单词 beginWord
和 endWord
和一个字典 wordList
,返回 从 beginWord
到 endWord
的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0
。
示例 1:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
示例 2:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:0
解释:endWord "cog" 不在字典中,所以无法进行转换。
提示:
1 <= beginWord.length <= 10
endWord.length == beginWord.length
1 <= wordList.length <= 5000
wordList[i].length == beginWord.length
beginWord
、endWord
和wordList[i]
由小写英文字母组成beginWord != endWord
wordList
中的所有字符串 互不相同
解题思路与代码实现
主要思路:
从beginWord
入队后进行BFS,逐个字符尝试修改,当新的字符串在wordList
中且未尝试过,加入到队列中,直到生成了beginWord
;不能生成则返回-1;
class Solution {
public int ladderLength(String beginWord, String beginWord, List<String> wordList) {
if (beginWord.equals(endWord) || wordList.isEmpty()) {
return 0;
}
Set<String> wordSet = new HashSet<>(wordList); // 转set
if (!wordSet.contains(endWord)) {
return 0;
}
LinkedList<String> queue = new LinkedList<>(); // 辅助队列
HashMap<String, Integer> map = new HashMap<>(); // 记录尝试过的字符串及其经过的单词数量
queue.offer(beginWord);
map.put(beginWord, 1);
// BFS
while (!queue.isEmpty()) {
String currentStr = queue.poll(); // 当前字符串
for (int i = 0; i < currentStr.length(); i++) {
for (char c = 'a'; c <= 'z'; c++) {
StringBuilder builder = new StringBuilder(currentStr);
builder.setCharAt(i, c);
String newStr = builder.toString(); // 修改一个字符,变成新字符串
if (newStr.equals(endWord)) {
// 返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目
return map.get(currentStr) + 1;
}
// 如果字典中包含newWord字符串且从beginWord到newWord长度未被记录
if (wordSet.contains(newStr) && !map.containsKey(newStr)) {
// 更新map和queue
queue.addLast(newStr); // 添加newWord到队尾
map.put(newStr, map.get(currentStr) + 1); // 记录newWord单词对应的路径长度
}
}
}
}
return 0;
}
}
踩坑点
无