【力扣】扫雷游戏
来源:力扣(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'的话,队列中就会有很多重复的元素。
浙公网安备 33010602011771号