1. 程式人生 > 實用技巧 >【力扣】DFS與BFS實戰解題學習記錄

【力扣】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'的話,佇列中就會有很多重複的元素。