题目描述

让我们一起来玩扫雷游戏!

给定一个代表游戏板的二维字符矩阵。 ‘M’ 代表一个未挖出的地雷,‘E’ 代表一个未挖出的空方块,‘B’ 代表没有相邻(上,下,左,右,和所有4个对角线)地雷的已挖出的空白方块,数字(’1’ 到 ‘8’)表示有多少地雷与这块已挖出的方块相邻,‘X’ 则表示一个已挖出的地雷。

现在给出在所有未挖出的方块中(’M’或者’E’)的下一个点击位置(行和列索引),根据以下规则,返回相应位置被点击后对应的面板:

  1. 如果一个地雷(’M’)被挖出,游戏就结束了- 把它改为 ‘X’。
  2. 如果一个没有相邻地雷的空方块(’E’)被挖出,修改它为(’B’),并且所有和其相邻的未挖出方块都应该被递归地揭露。
  3. 如果一个至少与一个地雷相邻的空方块(’E’)被挖出,修改它为数字(’1’到’8’),表示相邻地雷的数量。
  4. 如果在此次点击中,若无更多方块可被揭露,则返回面板。

例如:

输入:

1
2
3
4
[['E', 'E', 'E', 'E', 'E'],
['E', 'E', 'M', 'E', 'E'],
['E', 'E', 'E', 'E', 'E'],
['E', 'E', 'E', 'E', 'E']]

Click : [3,0]

输出:

1
2
3
4
[['B', '1', 'E', '1', 'B'],
['B', '1', 'X', '1', 'B'],
['B', '1', '1', '1', 'B'],
['B', 'B', 'B', 'B', 'B']]

解法

思路:这道题的逻辑其实比较简单,模拟扫雷的逻辑就可以了,难就难在我一开始没想到用什么方式来遍历😥题还是刷少了,很喜欢官方题解里这句话——整体看来,一次点击过程会从一个位置出发,逐渐向外圈扩散,这就引导我们使用【搜索】的方法实现

对每个方块,我们有下面两种情况:

  • 当前点击的是未挖出的地雷,修改为X即可
  • 当前点击的是未挖出的空方块,我们需要对其周围的八个方块进行判断。如果有地雷,需要统计地雷个数,将当前方块修改为数字;否则修改为B

为了代码简洁,我们可以设置两个数组保存当前元素要移动的步数

我们可以使用深度优先搜索(Depth First Search, DFS)广度优先搜索(Board First Search, BFS)来进行遍历,注意重复搜索的情况。

1. DFS

对于深度优先搜索来说,我们递归地进行元素遍历。而只要遍历的元素不是E,即不是未挖出的空方块,说明其状态被修改过,就不需要遍历了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {
int[] directR = new int[]{1, -1, 0, 0, 1, -1, -1, 1};
int[] directC = new int[]{0, 0, 1, -1, -1, -1, 1, 1};

public char[][] updateBoard(char[][] board, int[] click) {
int x = click[0], y = click[1];
if (board[x][y] == 'M') {
board[x][y] = 'X';
} else {
// dfs遍历修改
dfs(board, x, y);
}
return board;
}

private void dfs(char[][] board, int r, int c) {
int count = 0;
int tr, tc;
for (int i = 0; i < 8; i++) {
tr = r + directR[i];
tc = c + directC[i];
// 注意超界的判断
if (tr < 0 || tr > board.length - 1 || tc < 0 || tc > board[0].length - 1) continue;
if (board[tr][tc] == 'M') {
count++;
}
}
if (count > 0) {
board[r][c] = (char) (count + '0');
} else {
board[r][c] = 'B';
// 递归判断周围八个元素
for (int i = 0; i < 8; i++) {
tr = r + directR[i];
tc = c + directC[i];
// 如果元素不为E,则不遍历
if (tr < 0 || tr > board.length - 1 || tc < 0 || tc > board[0].length - 1 || board[tr][tc] != 'E') continue;
dfs(board, tr, tc);
}
}
}
}

时间复杂度为$O(m \times n)$ 遍历board二维数组
空间复杂度为$O(m \times n)$ 最差递归栈空间存储全部元素

2. BFS

对于广度优先搜索,我们将该节点周围的节点添加到队列中。解决重复的问题是,入列的E需要先改掉标记。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.util.Queue;
import java.util.LinkedList;
class Solution {
int[] directR = new int[]{1, -1, 0, 0, 1, -1, -1, 1};
int[] directC = new int[]{0, 0, 1, -1, -1, -1, 1, 1};

public char[][] updateBoard(char[][] board, int[] click) {
int x = click[0], y = click[1];
Queue<int[]> queue = new LinkedList<>();
if (board[x][y] == 'M') {
board[x][y] = 'X';
} else {
// bfs遍历修改
queue.offer(new int[]{x, y});
int[] index;
int count, tr, tc;
while (queue.size() != 0) {
index = queue.poll();
count = 0;
// 对周围八个元素进行判断
for (int i = 0; i < 8; i++) {
tr = index[0] + directR[i];
tc = index[1] + directC[i];
if (tr < 0 || tr > board.length - 1 || tc < 0 || tc > board[0].length - 1) continue;
if (board[tr][tc] == 'M') {
count++;
}
}
if (count > 0) {
board[index[0]][index[1]] = (char) (count + '0');
} else {
board[index[0]][index[1]] = 'B';
for (int i = 0; i < 8; i++) {
tr = index[0] + directR[i];
tc = index[1] + directC[i];
if (tr < 0 || tr > board.length - 1 || tc < 0 || tc > board[0].length - 1 || board[tr][tc] != 'E') continue;
// 改掉标记,防止重复入列
board[tr][tc] = 'B';
queue.offer(new int[]{tr, tc});
}
}
}
}
return board;
}
}

时间复杂度为$O(m \times n)$ 遍历board二维数组
空间复杂度为$O(m \times n)$ 额外的队列空间储存元素

参考