【力扣】DFS與BFS實戰解題學習記錄
來源:力扣(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'的話,佇列中就會有很多重複的元素。