演算法刷題之九圖
圖
圖是一種比較複雜的資料結構,不能說比較複雜,而是最複雜的。雖然接觸到圖的演算法只有兩道題,但是真的讓我開啟眼界,打開了深度優先和廣度優先的大門。
- 島嶼數量
- 單詞接龍
島嶼數量
給你一個由'1'(陸地)和 '0'(水)組成的的二維網格,請你計算網格中島嶼的數量。
島嶼總是被水包圍,並且每座島嶼只能由水平方向和/或豎直方向上相鄰的陸地連線形成。
此外,你可以假設該網格的四條邊均被水包圍。
示例 1:
輸入:grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ] 輸出:1 示例 2: 輸入:grid = [ ["1","1","0","0","0"], ["1","1","0","0","0"], ["0","0","1","0","0"], ["0","0","0","1","1"] ] 輸出:3
方法:找出圖中連成一片的1,就是島嶼
- 我們需要建立一個 visited 陣列用來記錄某個位置是否被訪問過。
- 對於一個為 1 且未被訪問過的位置,我們遞迴進入其上下左右位置上為 1 的數,將其 visited 變成 true。
- 重複上述過程
- 找完相鄰區域後,我們將結果 res 自增1,然後我們在繼續找下一個為 1 且未被訪問過的位置,直至遍歷完.
DFS
DFS是按照某一個方向,一直找到不滿足條件為止。
先是 向上 一直到邊界,然後向下到邊界,再向左,最後向右。按照某一個方向一直到底
class Solution: def numIslands(self, grid: List[List[str]]) -> int: if not grid: return 0 count = 0 for i in range(len(grid)): for j in range(len(grid[0])): if grid[i][j] == '1': self.dfs(grid, i, j) count += 1 return count # 深度優先演算法。自己呼叫自己。 # 只有當這一片的1全部都被置0之後,才會退出,否則會呼叫下去。 def dfs(self, grid, i, j): if i < 0 or j < 0 or i >= len(grid) or j >= len(grid[0]) or grid[i][j] != '1': return grid[i][j] = '0' self.dfs(grid, i + 1, j) self.dfs(grid, i - 1, j) self.dfs(grid, i, j + 1) self.dfs(grid, i, j - 1)
BFS
BFS 是搜尋周邊,一圈一圈擴大範圍。以當前位置為中心,先判斷上面是否符合條件,符合則加入佇列中,在判斷右邊,加入佇列,再oudo下面,最後左邊。這樣當前位置周圍一圈都判斷結束,同時佇列中又儲存有四個方向的資料,下一次迴圈四個方向分別再向外擴充套件。
class Solution: def numIslands(self, grid: List[List[str]]) -> int: count = 0 for row in range(len(grid)): for col in range(len(grid[0])): if grid[row][col] == '1': # 發現陸地 count += 1 # 結果加1 grid[row][col] = '0' # 將其轉為 ‘0’ 代表已經訪問過 # 對發現的陸地進行擴張即執行 BFS,將與其相鄰的陸地都標記為已訪問 # 下面還是經典的 BFS 模板 land_positions = collections.deque() land_positions.append([row, col]) while len(land_positions) > 0: x, y = land_positions.popleft() for new_x, new_y in [[x, y + 1], [x, y - 1], [x + 1, y], [x - 1, y]]: # 進行四個方向的擴張 # 判斷有效性 if 0 <= new_x < len(grid) and 0 <= new_y < len(grid[0]) and grid[new_x][new_y] == '1': grid[new_x][new_y] = '0' # 因為可由 BFS 訪問到,代表同屬一塊島,將其置 ‘0’ 代表已訪問過 land_positions.append([new_x, new_y]) return count
單詞接龍
字典wordList 中從單詞 beginWord和 endWord 的 轉換序列 是一個按下述規格形成的序列:
序列中第一個單詞是 beginWord 。
序列中最後一個單詞是 endWord 。
每次轉換隻能改變一個字母。
轉換過程中的中間單詞必須是字典wordList 中的單詞。
給你兩個單詞 beginWord和 endWord 和一個字典 wordList ,找到從beginWord 到endWord 的 最短轉換序列 中的 單詞數目 。如果不存在這樣的轉換序列,返回 0。
示例 1:
輸入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
輸出:5
解釋:一個最短轉換序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的長度 5。
示例 2:
輸入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
輸出:0
解釋:endWord "cog" 不在字典中,所以無法進行轉換。
來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/word-ladder
著作權歸領釦網路所有。商業轉載請聯絡官方授權,非商業轉載請註明出處。
``思路`
從起點詞出發,每次變一個字母,經過 n 次變換,變成終點詞,希望 n 儘量小。
我們需要找出鄰接關係,比如hit變一個字母會變成_it、h_t、hi_形式的新詞,再看該新詞是否存在於單詞表,如果存在,就找到了一個下一層的轉換詞。
同時,要避免重複訪問,hot->dot->hot這樣變回來是沒有意義的,徒增轉換的長度。
所以,確定了下一個轉換詞,將它從單詞表中刪除(單詞表的單詞是唯一的)。
下一層的單詞可能有多個,都要考察,哪一條轉換路徑先遇到終點詞,它就最短。
整理一下
把單詞看作節點,由一個結點帶出下一層的鄰接點,用BFS去做。
維護一個佇列,讓起點詞入列,level 為 1,然後出列考察。
將它的每個字元變成26字母之一,逐個看是否在單詞表,如果在,該新詞為下一層的轉變詞。
將它入列,它的 level +1,並從單詞表中刪去這個詞。
出列、入列…重複,當出列的單詞和終點詞相同,說明遇到了終點詞,返回它的 level。
當佇列為空時,代表BFS結束,始終沒有遇到終點詞,沒有路徑通往終點,返回 0。
作者:xiao_ben_zhu
連結:https://leetcode-cn.com/problems/word-ladder/solution/shou-hua-tu-jie-127-dan-ci-jie-long-bfsde-dian-x-2/
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
if not wordList and endWord not in wordList:
return 0
word_set = set(wordList)
if beginWord in word_set:
word_set.remove(beginWord)
from collections import deque
queue = deque()
queue.append([beginWord, 0])
while queue:
word, level = queue.popleft()
if word == endWord:
return level + 1
for i in range(len(word)):
for j in range(26):
new_word = word[:i] + chr(ord('a') + j) + word[i+1:]
if new_word in word_set:
word_set.remove(new_word)
queue.append([new_word, level+1])
return 0
# 圖的小結
圖雖然只有兩道題,但是把深度優先
和廣度優先
說的十分清楚。
深度優先
: 迴圈 + 棧
廣度優先
: 迴圈 + 佇列