合縱連橫: 從 路徑搜索 理解DFS背後的本質

小松鼠-avatar-img
發佈於演算法題目解析 個房間
更新於 發佈於 閱讀時間約 18 分鐘


這篇文章,會帶著大家複習以前學過的DFS框架

並且以圖論的應用題與概念為核心,

貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。


DFS 深度優先搜索框架

def dfs( parameter ):

# 邊界條件
if base case or stop condition:

#​ 更新結果
return

# 通則​
for each child node​:

# 更新參數,遞回下一層的節點
dfs( updated parameter)

return


如果特化成圖論中N4 四連通探索的DFS,往往具有下列形式

def dfs( parameter ):

if base case or stop condition:

#​ 更新結果
return

# 更新參數,遞回4連通相鄰的節點
mark current node as visited

# 分別是 左、右、上、下 四個鄰居​
dfs( updated parameter, and go left )
dfs( updated parameter, aad go right )
dfs( updated parameter, and go up )
dfs( updated parameter, and go down )

return

示意圖

raw-image

常見的用途:

深度優先探索,探索整張圖(也可以是樹、鏈結串列。)

檢測是否有滿足特定條件的路徑(存在性的判斷)


Word Search - LeetCode 搜尋特定的單字是否存在

這題給定一個矩陣,問我們這個矩陣中,是否存在一條N4 四連通的路徑,路徑上的字串恰好是某個特定的單字。

剛好就是我們剛剛提到的存在性判斷,檢測是否有滿足特定條件的路徑。

掃描整個矩陣,依序從每個格子點出發,依序拜訪四連通鄰居,一直往下走,看看有沒有一條路徑剛好可以拼出某個特定的單字。


實作上有一個細節要特別留意,為了避免重複搜索造成無窮循環,每個被我們拜訪過的格子點會先暫時標記成"#"代表已經走過,所有路徑都拜訪過之後,才會還原回來。


以DFS +N4探索框架的形式實現演算法,程式碼如下:

class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:

if not board:

# Quick response for empty board
return False

h, w = len(board), len(board[0])

# ------------------------------------------------------

def dfs_search(idx: int, x: int, y: int) -> bool:

if x < 0 or x == w or y < 0 or y == h or word[idx] != board[y][x]:
# Reject if out of boundary, or current grid cannot match the character word[idx]
return False

if idx == len(word) - 1:
# Accept when we match all characters of word during DFS
return True

cur = board[y][x]

# mark as '#' to avoid repeated traversal
board[y][x] = '#'

# visit next four neighbor grids
found = dfs_search(idx + 1, x + 1, y) or dfs_search(idx + 1, x - 1, y) or dfs_search(idx + 1, x, y + 1) or dfs_search(idx + 1, x, y - 1)

# recover original grid character after DFS is completed
board[y][x] = cur

return found

# ------------------------------------------------------

return any(dfs_search(0, x, y) for y in range(h) for x in range(w))

接著看進階延伸題,改成找辭典裡面的單字,把有找到的列出來

Word Search II - LeetCode 搜尋給定辭典中的單字

這題多了辭典,我們可以用之前學過前綴樹的技巧,先把辭典建成一顆Trie。

接著,同步在矩陣和Trie裡面搜索每個字元,假如在某一格可以在Trie中找到一個單字,就把這個單字加入到最終結果的清單中


以DFS +N4探索框架的形式實現演算法,程式碼如下:

class Solution:
def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:

# Build a trie from given words
trie = {}
for word in words:
curr = trie
for c in word:
if c not in curr:
curr[c] = {}
curr = curr[c]
curr['@'] = word

H, W = len(board), len(board[0])

res = []
# ===========================================
def dfs(r, c, trie):

# Skip out-of-board search
if not ( 0 <= r < H ) or not ( 0 <= c < W):
return

# Get current character on board
ch = board[r][c]

# Skip if current character does not exist in Trie
if ch not in trie:
return

curr = trie[ch]
word = curr.pop('@', None)
if word:
# We've found a word matched in Trie
res.append(word)

# Avoid repeated traversal
board[r][c] = '#'

dfs(r-1, c, curr)
dfs(r+1, c, curr)
dfs(r, c-1, curr)
dfs(r, c+1, curr)
board[r][c] = ch

# Deleted empty trie branch
if not curr:
trie.pop(ch)

return
# =========================================

# Scan each position on board
for r in range(H):
for c in range(W):
dfs(r, c, trie)

return res

還有一個類似題,要求我們計算可以從起點走到終點,
而且可以拜訪所有空白格子點的路徑方法數

Unique Paths III - LeetCode 拜訪所有空白格子點的路徑方法數

其實也是類似的,我們可以先計算有幾個空白格子點。
接著從起點開始用DFS+N4拜訪整張棋盤,如果走到終點的時候,恰好拜訪過所有空白格子點,就把方法數累加一。最後的路徑方法數總數就是答案。


以DFS +N4探索框架的形式實現演算法,程式碼如下:

# Define grid state to make it more readable

from collections import namedtuple
GridState = namedtuple('GridState', 'start ending empty obstacle')
grid_state = GridState( start = 1, ending = 2, empty = 0, obstacle = -1)


class Solution:
def uniquePathsIII(self, grid):

h, w = len( grid ), len( grid[0] )

available_grid_count = 0
obstacle_count = 0

src_row, src_col = 0, 0

# record for valid path
# a path is valid if and only if it visits all available grid except for obstacles.
self.valid_path_count = 0

for y in range(h) :
for x in range(w):

if grid[y][x] == grid_state.obstacle:
# update counter of obstacle
obstacle_count += 1

elif grid[y][x] == grid_state.start:
# locate start point
src_row, src_col = y, x

# compute counting of all available grids
available_grid_count = h * w - obstacle_count

# ------------------------------------------------------

def dfs( cur_row, cur_col, depth):

if not ( 0 <= cur_row < h) or not ( 0 <= cur_col < w ):
# Skip out-of-board search
return
if grid[cur_row][cur_col] == grid_state.obstacle:

# Terminate DFS when bumping into an obstacle
return

elif grid[cur_row][cur_col] == grid_state.ending:

if depth == available_grid_count:

# If we reach end with visiting all available grids, update valid path count
self.valid_path_count += 1

# Terminate DFS when reach ending
return

else:

state_backup = grid[cur_row][cur_col]

# mark current grid as obstacle to avoid repeated visit
grid[cur_row][cur_col] = grid_state.obstacle

# DFS + N4 to discover each neighbor cell
dfs( cur_row-1, cur_col, depth + 1)
dfs( cur_row+1, cur_col, depth + 1)
dfs( cur_row, cur_col-1, depth + 1)
dfs( cur_row, cur_col+1, depth + 1)

# restore original grid state
grid[cur_row][cur_col] = state_backup

return

# ------------------------------------------------------

dfs(src_row, src_col, depth = 1)
return self.valid_path_count


結語

好,今天一口氣介紹了最精華的部分,

通用的DFS+N4探索框架,還有相關的路徑應用題與演算法建造,


希望能幫助讀者、觀眾徹底理解它的原理,

並且能夠舉一反三,洞察其背後的本質和了解相關的應用領域!


感謝收看囉! 我們下篇文章再見~

留言
avatar-img
留言分享你的想法!
小松鼠-avatar-img
發文者
2024/09/11
從Python來學圖論Graph與DFS深度優先探索提及了這篇文章,趕快過去看看吧!
小松鼠-avatar-img
發文者
2024/05/29
avatar-img
小松鼠的演算法樂園
95會員
427內容數
由有業界實戰經驗的演算法工程師, 手把手教你建立解題的框架, 一步步寫出高效、清晰易懂的解題答案。 著重在讓讀者啟發思考、理解演算法,熟悉常見的演算法模板。 深入淺出地介紹題目背後所使用的演算法意義,融會貫通演算法與資料結構的應用。 在幾個經典的題目融入一道題目的多種解法,或者同一招解不同的題目,擴展廣度,並加深印象。
2024/09/26
Leetcode 729. My Calendar I 給定一個行事曆的class定義和行程安排的介面interface。 請完成下列function 1.建構子MyCalendar() 初始化MyCalendar物件 2.boolean book(int start, int end) 插入新行程
Thumbnail
2024/09/26
Leetcode 729. My Calendar I 給定一個行事曆的class定義和行程安排的介面interface。 請完成下列function 1.建構子MyCalendar() 初始化MyCalendar物件 2.boolean book(int start, int end) 插入新行程
Thumbnail
2024/09/10
Insert Greatest Common Divisors in Linked List 題目給定一個鏈結串列, 請在兩兩節點之間加入一個新節點,新節點的值為兩者之間的最大公因數。 最後返回新串列的head node作為答案。
Thumbnail
2024/09/10
Insert Greatest Common Divisors in Linked List 題目給定一個鏈結串列, 請在兩兩節點之間加入一個新節點,新節點的值為兩者之間的最大公因數。 最後返回新串列的head node作為答案。
Thumbnail
2024/09/09
2326. Spiral Matrix IV 題目給定一個Linked list和對應的矩陣高度m、寬度n。 請依照順時針的拜訪順序, 從左上角出發,依照次序把Linked List的內容填到矩陣裡。 如果有剩餘不足的空位,就填補-1。 最後將填補好的矩陣返回作為答案。
Thumbnail
2024/09/09
2326. Spiral Matrix IV 題目給定一個Linked list和對應的矩陣高度m、寬度n。 請依照順時針的拜訪順序, 從左上角出發,依照次序把Linked List的內容填到矩陣裡。 如果有剩餘不足的空位,就填補-1。 最後將填補好的矩陣返回作為答案。
Thumbnail
看更多
你可能也想看
Thumbnail
「欸!這是在哪裡買的?求連結 🥺」 誰叫你太有品味,一發就讓大家跟著剁手手? 讓你回購再回購的生活好物,是時候該介紹出場了吧! 「開箱你的美好生活」現正召喚各路好物的開箱使者 🤩
Thumbnail
「欸!這是在哪裡買的?求連結 🥺」 誰叫你太有品味,一發就讓大家跟著剁手手? 讓你回購再回購的生活好物,是時候該介紹出場了吧! 「開箱你的美好生活」現正召喚各路好物的開箱使者 🤩
Thumbnail
介紹朋友新開的蝦皮選物店『10樓2選物店』,並分享方格子與蝦皮合作的分潤計畫,註冊流程簡單,0成本、無綁約,推薦給想增加收入的讀者。
Thumbnail
介紹朋友新開的蝦皮選物店『10樓2選物店』,並分享方格子與蝦皮合作的分潤計畫,註冊流程簡單,0成本、無綁約,推薦給想增加收入的讀者。
Thumbnail
當你邊吃粽子邊看龍舟競賽直播的時候,可能會順道悼念一下2300多年前投江的屈原。但你知道端午節及其活動原先都與屈原毫無關係嗎?這是怎麼回事呢? 本文深入探討端午節設立初衷、粽子、龍舟競渡與屈原自沉四者。看完這篇文章,你就會對端午、粽子、龍舟和屈原的四角關係有新的認識喔。那就讓我們一起解開謎團吧!
Thumbnail
當你邊吃粽子邊看龍舟競賽直播的時候,可能會順道悼念一下2300多年前投江的屈原。但你知道端午節及其活動原先都與屈原毫無關係嗎?這是怎麼回事呢? 本文深入探討端午節設立初衷、粽子、龍舟競渡與屈原自沉四者。看完這篇文章,你就會對端午、粽子、龍舟和屈原的四角關係有新的認識喔。那就讓我們一起解開謎團吧!
Thumbnail
這篇文章,會帶大家快速回顧DFS+回溯法框架(還沒看過或想複習的可以點連結進去)。 用DFS+回溯法框架,解開 直線排列Permutations 的全系列題目。 幫助讀者鞏固DFS+回溯法框架這個重要的知識點。 回顧 DFS+回溯法框架 白話的意思 # 列舉所有可能的情況,遞迴展開所有分
Thumbnail
這篇文章,會帶大家快速回顧DFS+回溯法框架(還沒看過或想複習的可以點連結進去)。 用DFS+回溯法框架,解開 直線排列Permutations 的全系列題目。 幫助讀者鞏固DFS+回溯法框架這個重要的知識點。 回顧 DFS+回溯法框架 白話的意思 # 列舉所有可能的情況,遞迴展開所有分
Thumbnail
這篇文章,會帶大家快速回顧DFS+回溯法框架(還沒看過或想複習的可以點連結進去)。 用DFS+回溯法框架,解開 組合數之和 Combination Sum 的全系列題目。 幫助讀者鞏固DFS+回溯法框架這個重要的知識點。 回顧 DFS+回溯法框架 白話的意思 # 列舉所以可能的情況,
Thumbnail
這篇文章,會帶大家快速回顧DFS+回溯法框架(還沒看過或想複習的可以點連結進去)。 用DFS+回溯法框架,解開 組合數之和 Combination Sum 的全系列題目。 幫助讀者鞏固DFS+回溯法框架這個重要的知識點。 回顧 DFS+回溯法框架 白話的意思 # 列舉所以可能的情況,
Thumbnail
這篇文章,會帶著大家複習以前學過的 區間DP框架, 並且以回文子字串、回文子序列的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 回文字串的基本定義 s = s[::-1] 也就是說字串s的正序 和 逆序完全相同。 回文字串的基本結構 空字串"
Thumbnail
這篇文章,會帶著大家複習以前學過的 區間DP框架, 並且以回文子字串、回文子序列的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 回文字串的基本定義 s = s[::-1] 也就是說字串s的正序 和 逆序完全相同。 回文字串的基本結構 空字串"
Thumbnail
這篇文章,會帶著大家複習以前學過的DFS框架, 並且以圖論的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 DFS 深度優先搜索框架 def dfs( parameter ): # 邊界條件 if base case or stop cond
Thumbnail
這篇文章,會帶著大家複習以前學過的DFS框架, 並且以圖論的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 DFS 深度優先搜索框架 def dfs( parameter ): # 邊界條件 if base case or stop cond
Thumbnail
這篇文章,會帶著大家複習以前學過的格子點DP框架, 並且以移動路徑Unique Path的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 格子點DP框架 依循題目的定義和規則,找出格子點移動的共同模式。 以本篇文章的例題為例,每一步可以選擇往右走一個
Thumbnail
這篇文章,會帶著大家複習以前學過的格子點DP框架, 並且以移動路徑Unique Path的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 格子點DP框架 依循題目的定義和規則,找出格子點移動的共同模式。 以本篇文章的例題為例,每一步可以選擇往右走一個
Thumbnail
這篇文章,會帶著大家複習以前學過的DFS框架 結合樹型DP, 並且以路徑和Path Sum的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 DFS 深度優先搜索框架 def dfs( parameter ): if base case or sto
Thumbnail
這篇文章,會帶著大家複習以前學過的DFS框架 結合樹型DP, 並且以路徑和Path Sum的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 DFS 深度優先搜索框架 def dfs( parameter ): if base case or sto
Thumbnail
這篇文章,會帶著大家複習以前學過的遞回框架, 並且鏈結串列的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 遞回框架 尋找共通模式(common pattern),對應到演算法的General case 確立初始條件(initial conditio
Thumbnail
這篇文章,會帶著大家複習以前學過的遞回框架, 並且鏈結串列的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 遞回框架 尋找共通模式(common pattern),對應到演算法的General case 確立初始條件(initial conditio
Thumbnail
這篇文章,會帶著大家複習以前學過的區間DP框架, 並且以區間DP的概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個實用的演算法框架。 基本的區間DP框架(限制條件: 相鄰的兩項不允許同時選擇) 在House Robbery這題中,我們學會了一種基本的區間DP框架。
Thumbnail
這篇文章,會帶著大家複習以前學過的區間DP框架, 並且以區間DP的概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個實用的演算法框架。 基本的區間DP框架(限制條件: 相鄰的兩項不允許同時選擇) 在House Robbery這題中,我們學會了一種基本的區間DP框架。
Thumbnail
這篇文章,會帶著大家複習以前學過的二分搜尋法(Binary Search)框架, 並且以二分搜尋法的概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個實用的演算法框架。 Binary search 二分搜尋法框架 用途: 在已經排序好的數列中尋找目標值。
Thumbnail
這篇文章,會帶著大家複習以前學過的二分搜尋法(Binary Search)框架, 並且以二分搜尋法的概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個實用的演算法框架。 Binary search 二分搜尋法框架 用途: 在已經排序好的數列中尋找目標值。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News