一魚多吃: 從 島嶼周長 理解 圖論演算法的本質

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

今天的官方每日一題是Island Perimeter島嶼周長,很有趣的一題。
題目非常直觀好懂。也很適合拿來作為多角度複習、回顧圖論演算法的好題目。


英文的題目敘述在這裡


題目敘述

題目會給我們一個二維陣列當作地圖,格子點為1代表陸地,格子點為0代表海洋

要求我們以四連通N4的方式拜訪這座島嶼,並且計算這座島嶼的周長

題目保證整張地圖只會有一座島嶼


測試範例

Example 1:

raw-image
Input: grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
Output: 16
黃色部分的長度代表這座島嶼的周長​
Explanation: The perimeter is the 16 yellow stripes in the image above.

Example 2:

Input: grid = [[1]]
Output: 4

Example 3:

Input: grid = [[1,0]]
Output: 4

約束條件

Constraints:

  • row == grid.length

row的總數 = 地圖的高度

  • col == grid[i].length

col的總數 = 地圖的寬度

  • 1 <= row, col <= 100

高度和寬度都介於 1~100之間。

  • grid[i][j] is 0 or 1.

每個格子點一定是零(代表海洋)或一(代表陸地)。

  • There is exactly one island in grid.

保證整張地圖只會有一座島嶼。


什麼時候會遇到島嶼的邊界?

當遇到陸地和海洋交界的時候。

當遇到地圖邊界的時候。

這時候剛好跨過島嶼的邊界,周長累加一


用DFS 探索相連的陸地


先找出的第一塊陸地的格子點,接著用N4 四連通 配合深度優先DFS探索
周圍相鄰的陸地格子點。

當我們遇到陸地和海洋的交界,或者我們遇到地圖邊界時,
這時候剛好跨過島嶼的邊界,周長累加一


時間複雜度O(H * W) 每個格子點最多拜訪一次。

空間複雜度O(H * W) 遞回深度最深是幾乎拜訪整張圖。


DFS 程式碼

class Solution:
def islandPerimeter(self, grid: List[List[int]]) -> int:

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

def dfs(r, c):

if not ( 0 <= r < H ) or not( 0 <= c < W) or grid[r][c] == 0:
return 1

if grid[r][c] == '#':
return 0

N4_offset = itertools.pairwise([0, 1, 0, -1, 0])

perimeter = 0
for dr, dc in N4_offset:
nr, nc = r+dr, c+dc

grid[r][c] = '#'
perimeter += dfs(nr, nc)

return perimeter

# ==================================

for r in range(H):
for c in range(W):
if grid[r][c] == 1:
return dfs(r, c)

return -1

用BFS 探索相連的陸地


先找出的第一塊陸地的格子點,接著用N4 四連通 配合廣度優先BFS探索
周圍相鄰的陸地格子點。

當我們遇到陸地和海洋的交界,或者我們遇到地圖邊界時,
這時候剛好跨過島嶼的邊界,周長累加一


時間複雜度O(H * W) 每個格子點最多拜訪一次。

空間複雜度O(H * W) Queue長度最長是幾乎拜訪整張圖。


BFS 程式碼

class Solution:
def islandPerimeter(self, grid: List[List[int]]) -> int:

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

perimeter = 0

N4 = list( itertools.pairwise([0, 1, 0, -1, 0]) )

for r in range(H):
for c in range(W):
if grid[r][c] == 1:

bfs_queue = deque([(r,c)])

while bfs_queue:

cur_r, cur_c = bfs_queue.popleft()

#print(f"at {cur_r}, {cur_c}")

if not( 0 <= cur_r < H ) or not( 0 <= cur_c < W ) or grid[cur_r][cur_c] == 0:
# update perimeter count
perimeter += 1
continue

if grid[cur_r][cur_c] == '#':
# Avoid repeated traversal
continue

# Mark current grid as visited
grid[cur_r][cur_c] = '#'

for dr, dc in N4:
nr, nc = cur_r + dr, cur_c + dc
bfs_queue.append( (nr, nc) )

return perimeter

return -1

依序掃描格子點的演算法


除了經典的DFS或者BFS探索演算法之外,我們也可以用掃描格子點的方式。

依序從左到右,從上到下掃描每個格子點。

統計遇到的陸地格子點數目,再統計內部邊緣(就是陸地和陸地相鄰的邊緣)有幾條。

每個陸地格子點會貢獻四條邊緣
內部邊緣會被兩塊相鄰的陸地所共用,所以重複計算了兩次


最後,島嶼的周長

= 陸地格子點貢獻的邊緣數目 - 重複計算的內部邊緣

= 陸地格子點 * 4 - 內部邊緣總數 * 2


時間複雜度O(H * W) 每個格子點最多拜訪一次。

空間複雜度O(1) 只有使用固定尺寸的臨時變數,所需空間為常數級別。


依序掃描格子點的程式碼

class Solution:
def islandPerimeter(self, grid: List[List[int]]) -> int:

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

if h * w == 0:
# Quick response for simple case
return 0

land_cell, internal_edge = 0, 0

# scan each cell from top to bottom, from left to right
for y in range(h):
for x in range(w):

if grid[y][x] == 1:
# current cell is land
land_cell += 1

if y and grid[y-1][x]:
# current land cell share one internal edge with neighbor land cell on the top
internal_edge += 1

if x and grid[y][x-1]:
# current land cell share one internal edge with neighbor land cell of the left
internal_edge += 1


# each land cell contributes 4 edge
# each internal edge is repeatedly counted by 2 adjacent land cells
perimeter = land_cell * 4 - internal_edge * 2

return perimeter

關鍵知識點 找出共通模式


根據題意,找出島嶼邊緣的定義。


可以根據範例推敲發現:

當遇到陸地和海洋交界的時候。
當遇到地圖邊界的時候。
這時候剛好跨過島嶼的邊界,周長累加一

結語

條條大路通羅馬,萬變不離其宗。

一個題目可以有好多種解法,但是背後核心觀念都是類似的

只要掌握基本原理,根據題意,找出目標的明確定義,
接著採用合適的圖論演算法去計算所求(島嶼的周長)即可!


希望能幫助讀者、觀眾徹底理解基本圖論演算法BFS / DFS 的原理,

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


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

avatar-img
90會員
425內容數
由有業界實戰經驗的演算法工程師, 手把手教你建立解題的框架, 一步步寫出高效、清晰易懂的解題答案。 著重在讓讀者啟發思考、理解演算法,熟悉常見的演算法模板。 深入淺出地介紹題目背後所使用的演算法意義,融會貫通演算法與資料結構的應用。 在幾個經典的題目融入一道題目的多種解法,或者同一招解不同的題目,擴展廣度,並加深印象。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
題目敘述 題目會給定一顆二元樹的根結點, 要求我們在指定的層樹d,插入新的一層,節點值為v。 原本的左、右子樹,就成為新的那一層的左子樹、右子樹。 題目的原文敘述 測試範例 Example 1: Input: root = [4,2,6,3,1,5], val = 1, depth =
這篇文章,會帶著大家複習以前學過的DFS框架, 並且以圖論的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 DFS 深度優先搜索框架 def dfs( parameter ): # 邊界條件 if base case or stop cond
這篇文章,會帶著大家複習以前學過的BFS框架, 並且以圖論的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 BFS 框架 + 演算法 虛擬碼 # Queue 通常初始化成根結點,作為起點 BFS_queue = deque([root])​ # 先
這篇文章,會帶著大家複習以前學過的DFS框架 結合樹型DP, 並且以路徑和Path Sum的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 DFS 深度優先搜索框架 def dfs( parameter ): if base case or sto
這篇文章,會帶著大家複習以前學過的遞回框架, 並且鏈結串列的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 遞回框架 尋找共通模式(common pattern),對應到演算法的General case 確立初始條件(initial conditio
這篇文章,會帶著大家複習以前學過的二元搜尋樹(Binary Search Tree)框架, 並且以二分搜尋樹的概念與定義為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 二元搜尋樹(Binary Search Tree)的定義
題目敘述 題目會給定一顆二元樹的根結點, 要求我們在指定的層樹d,插入新的一層,節點值為v。 原本的左、右子樹,就成為新的那一層的左子樹、右子樹。 題目的原文敘述 測試範例 Example 1: Input: root = [4,2,6,3,1,5], val = 1, depth =
這篇文章,會帶著大家複習以前學過的DFS框架, 並且以圖論的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 DFS 深度優先搜索框架 def dfs( parameter ): # 邊界條件 if base case or stop cond
這篇文章,會帶著大家複習以前學過的BFS框架, 並且以圖論的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 BFS 框架 + 演算法 虛擬碼 # Queue 通常初始化成根結點,作為起點 BFS_queue = deque([root])​ # 先
這篇文章,會帶著大家複習以前學過的DFS框架 結合樹型DP, 並且以路徑和Path Sum的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 DFS 深度優先搜索框架 def dfs( parameter ): if base case or sto
這篇文章,會帶著大家複習以前學過的遞回框架, 並且鏈結串列的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 遞回框架 尋找共通模式(common pattern),對應到演算法的General case 確立初始條件(initial conditio
這篇文章,會帶著大家複習以前學過的二元搜尋樹(Binary Search Tree)框架, 並且以二分搜尋樹的概念與定義為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 二元搜尋樹(Binary Search Tree)的定義
你可能也想看
Google News 追蹤
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
【廣州.沙面島】 相關行程:《廣州三日兩夜自助遊.第一天》
Thumbnail
記錄一則不完整的原創小說構想,若有想法歡迎留言協助小說的完善。有一座小島,只要踏上島嶼,就會忘掉來這裡之前所經歷的一切事物--
Thumbnail
島嶼週記是作者參與朋友發起的一個充滿感動與美好回憶的活動,時隔多年發起人再次發起島嶼週記活動,構起許多島嶼的故事,想起我的第一次環島初體驗,是因為客戶要求,那年旅程由南到北,有許多特別而美好的回憶,也沒想過這次的環島意外成為實現客戶夢想的推手。
  島嶼外圍近似完整的圓,居民為此修整了一條平整的石磚平路,初始僅為了方便行走,但當商人提著擔子沿路叫賣時,便不經意地將散落各處的人匯集,造出島的貿易生態。而過往月中的滿月時分,月亮的樣態便完整地復刻了島的外圈,從高處打下時就像是強力的鎂光燈,照錄島人們一切的生活。   若這些島民知曉他們賴以維生
Thumbnail
在亂世變局的後疫情時代,海洋思維比大陸思維更能彈性適應、靈活應變。
Thumbnail
一、看封面想一想 •主角是誰?•在什麼地方?•發生什麼事? 二、聽故事 三、問題與討論 1.海裡有五顏六色的東西,那是什麼? 2.這些東西怎麼到海裡 3.這些東西對生物造成什麼影響 4.我們現在可以做什麼? 5.ORID討論法
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
【廣州.沙面島】 相關行程:《廣州三日兩夜自助遊.第一天》
Thumbnail
記錄一則不完整的原創小說構想,若有想法歡迎留言協助小說的完善。有一座小島,只要踏上島嶼,就會忘掉來這裡之前所經歷的一切事物--
Thumbnail
島嶼週記是作者參與朋友發起的一個充滿感動與美好回憶的活動,時隔多年發起人再次發起島嶼週記活動,構起許多島嶼的故事,想起我的第一次環島初體驗,是因為客戶要求,那年旅程由南到北,有許多特別而美好的回憶,也沒想過這次的環島意外成為實現客戶夢想的推手。
  島嶼外圍近似完整的圓,居民為此修整了一條平整的石磚平路,初始僅為了方便行走,但當商人提著擔子沿路叫賣時,便不經意地將散落各處的人匯集,造出島的貿易生態。而過往月中的滿月時分,月亮的樣態便完整地復刻了島的外圈,從高處打下時就像是強力的鎂光燈,照錄島人們一切的生活。   若這些島民知曉他們賴以維生
Thumbnail
在亂世變局的後疫情時代,海洋思維比大陸思維更能彈性適應、靈活應變。
Thumbnail
一、看封面想一想 •主角是誰?•在什麼地方?•發生什麼事? 二、聽故事 三、問題與討論 1.海裡有五顏六色的東西,那是什麼? 2.這些東西怎麼到海裡 3.這些東西對生物造成什麼影響 4.我們現在可以做什麼? 5.ORID討論法