Harukaze

 

【力扣】扫雷游戏

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/minesweeper

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

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

如果一个地雷('M')被挖出,游戏就结束了- 把它改为 'X'。

如果一个没有相邻地雷的空方块('E')被挖出,修改它为('B'),并且所有和其相邻的未挖出方块都应该被递归地揭露。

如果一个至少与一个地雷相邻的空方块('E')被挖出,修改它为数字('1'到'8'),表示相邻地雷的数量。

如果在此次点击中,若无更多方块可被揭露,则返回面板。

输入:

[['E', 'E', 'E', 'E', 'E'],

 ['E', 'E', 'M', 'E', 'E'],

 ['E', 'E', 'E', 'E', 'E'],

 ['E', 'E', 'E', 'E', 'E']]

Click : [3,0]

 

输出:

[['B', '1', 'E', '1', 'B'],

 ['B', '1', 'M', '1', 'B'],

 ['B', '1', '1', '1', 'B'],

 ['B', 'B', 'B', 'B', 'B']]

 

输入:

[['B', '1', 'E', '1', 'B'],

 ['B', '1', 'M', '1', 'B'],

 ['B', '1', '1', '1', 'B'],

 ['B', 'B', 'B', 'B', 'B']]

Click : [1,2]

 

输出:

[['B', '1', 'E', '1', 'B'],

 ['B', '1', 'X', '1', 'B'],

 ['B', '1', '1', '1', 'B'],

 ['B', 'B', 'B', 'B', 'B']]

 1 class Solution:
 2     def updateBoard(self, board: List[List[str]], click: List[int]) -> List[List[str]]:
 3         x = click[0]
 4         y = click[1]
 5         if board[x][y] == M:
 6             board[x][y] == x
 7             return board
 8         elif board[x][y] == E:
 9             board[x][y] == B
10             dfs(x,y)
11     def dfs(x,y):
12         direction = [[-1,0],[1,0],[0,-1],[0,1],[1,1],[1,-1],[-1,1],[-1,-1]] for i,j in direction

经过本小白的初步判断如果点击的方块不是炸弹的话,那么需要进入一个深度优先搜索的循环体来进行遍历,我在思考如果点击了方块A,如何得到方块A的其他八个邻居,因此第12行代码诞生了,紧接着我需要考虑边界问题,苦想没有思路,只能借阅大佬们的代码参观学习了。

思路一:DFS

 1 class Solution:
 2     def updateBoard(self, board: List[List[str]], click: List[int]) -> List[List[str]]:
 3         i, j = click
 4         row, col = len(board), len(board[0])
 5         if board[i][j] == "M":
 6             board[i][j] = "X"
 7             return board
 8 
 9         # 计算空白块周围的炸弹
10         def cal(i, j):
11             res = 0
12             for x in [1, -1, 0]:
13                 for y in [1, -1, 0]:
14                     if x == 0 and y == 0: continue
15                     if 0 <= i + x < row and 0 <= j + y < col and board[i + x][j + y] == "M": res += 1
16             return res
17 
18         def dfs(i, j):
19             num = cal(i, j)
20             if num > 0:
21                 board[i][j] = str(num)
22                 return
23             board[i][j] = "B"
24             for x in [1, -1, 0]:
25                 for y in [1, -1, 0]:
26                     if x == 0 and y == 0: continue
27                     nxt_i, nxt_j = i + x, j + y
28                     if 0 <= nxt_i < row and 0 <= nxt_j < col and board[nxt_i][nxt_j] == "E": dfs(nxt_i, nxt_j)
29 
30         dfs(i, j)
31         return board
32 作者:powcai
33 链接:https://leetcode-cn.com/problems/minesweeper/solution/bfs-dfs-by-powcai-8/
34 来源:力扣(LeetCode)

根据扫雷规则,情况1:方块A周围存在炸弹。方块A邻居有多少炸弹就需要显示多少数字,每次执行深度优先搜索递归前,需要首先判断该方块A旁有多少炸弹。可以看到通过双重循环,可以简单的表示方块A周围的邻居坐标,还能简便的判断边界问题,这个点学习了。

情况2:方块A周围不存在炸弹,给方块A赋值B(Blank)即可,按照深度优先搜索思想,将邻居节点作为新参数传给dfs进行递归调用。[邻居节点的条件限制:首先不能超越矩阵界限,其次必须为E(Empty)才可以递归,如果为数字,表明该方块经过计算了]

 成片的无雷区被迭代计算

 

介绍思路二之前先学习下collections 库的deque方法

使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低.

deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:

1 from collections import deque
2 q = deque(['a', 'b', 'c'])
3 q.append('x')
4 q.appendleft('y')
5 q #deque(['y', 'a', 'b', 'c', 'x'])

deque除了实现list的append()pop()外,还支持appendleft()popleft(),这样就可以非常高效地往头部添加或删除元素。

思路二BFS  :

 1 class Solution:
 2     def updateBoard(self, board: List[List[str]], click: List[int]) -> List[List[str]]:
 3         i, j = click
 4         row, col = len(board), len(board[0])
 5         if board[i][j] == "M":
 6             board[i][j] = "X"
 7             return board
 8 
 9         # 计算空白快周围的***
10         def cal(i, j):
11             res = 0
12             for x in [1, -1, 0]:
13                 for y in [1, -1, 0]:
14                     if x == 0 and y == 0: continue
15                     if 0 <= i + x < row and 0 <= j + y < col and board[i + x][j + y] == "M": res += 1
16             return res
17 
18         def bfs(i, j):
19             queue = collections.deque([[i, j]])
20             while queue:
21                 i, j = queue.pop()
22                 num = cal(i, j)
23                 if num > 0:
24                     board[i][j] = str(num)
25                     continue
26                 board[i][j] = "B"
27                 for x in [1, -1, 0]:
28                     for y in [1, -1, 0]:
29                         if x == 0 and y == 0: continue
30                         nxt_i, nxt_j = i + x, j + y
31                         if nxt_i < 0 or nxt_i >= row or nxt_j < 0 or nxt_j >= col: continue 
32                         if board[nxt_i][nxt_j] == "E":
33                             queue.appendleft([nxt_i, nxt_j])
34                             board[nxt_i][nxt_j] = "G"
35 
36         bfs(i, j)
37         return board
38 作者:powcai
39 链接:https://leetcode-cn.com/problems/minesweeper/solution/bfs-dfs-by-powcai-8/
40 来源:力扣(LeetCode)

思路和上面相同,区别在于递归调用改成了广度优先搜索,首先方块A进队,然后出队,如果该方块周围有炸弹则计算炸弹数,本次操作结束。如果该方块周围无炸弹,该块赋值为B(Blank)。按照广度优先搜索算法的思想,符合条件的邻居首先全部进入队列,再执行下一步出队操作。和思路一相同,必须为E的块才能进队。bfs最下面入队列之后将状态改为‘G’,起访问标记作用,主要是与'E'区别开,表示他已经被访问过,不设为'G'的话,队列中就会有很多重复的元素。

 

posted on 2020-12-27 14:23  Harukaze  阅读(188)  评论(0)    收藏  举报

导航