1. 程式人生 > 其它 >演算法刷題之九圖

演算法刷題之九圖

圖是一種比較複雜的資料結構,不能說比較複雜,而是最複雜的。雖然接觸到圖的演算法只有兩道題,但是真的讓我開啟眼界,打開了深度優先和廣度優先的大門。

  1. 島嶼數量
  2. 單詞接龍

島嶼數量

給你一個由'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,就是島嶼

  1. 我們需要建立一個 visited 陣列用來記錄某個位置是否被訪問過。
  2. 對於一個為 1 且未被訪問過的位置,我們遞迴進入其上下左右位置上為 1 的數,將其 visited 變成 true。
  3. 重複上述過程
  4. 找完相鄰區域後,我們將結果 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

# 圖的小結
圖雖然只有兩道題,但是把深度優先廣度優先說的十分清楚。
深度優先: 迴圈 + 棧 
廣度優先: 迴圈 + 佇列