用格子點DP來解 三角形最小成本下降路徑 Triangle_Leetcode #120

小松鼠
發佈於演算法專欄 個房間
閱讀時間約 15 分鐘

題目敘述 Triangle

題目會給我們一個三角形的二維陣列triangle ,每個元素分別代表每個格子的成本,請問我們從最頂端到底部的下墜路徑的最小成本總和是多少?

每次下墜到下一排的時候,可以有兩種選擇:

1.往左下方的格子點移動。

2.往右下方的格子點移動。


測試範例

Example 1:

Input: triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
Output: 11
Explanation: The triangle looks like:
2
3 4
6 5 7
4 1 8 3
The minimum path sum from top to bottom is 2 + 3 + 5 + 1 = 11 (underlined above).

最小成本的下降路徑為: 2 -> 3 -> 5 -> 1

Example 2:

Input: triangle = [[-10]]
Output: -10

約束條件

Constraints:

  • 1 <= triangle.length <= 200

三角形陣列的長度介於1~200之間。

  • triangle[0].length == 1

三角形陣列的頂端只會有一個元素。

  • triangle[i].length == triangle[i - 1].length + 1

下一排的長度 = 前一排的長度+1

  • -10^4 <= triangle[i][j] <= 10^4

每個陣列元素值介於 負一萬~正一萬 之間。



進階提問

Follow up: Could you do this using only O(n) extra space, where n is the total number of rows in the triangle?

能不能想到一個只使用O(n)線性空間的演算法?


觀察 下墜規則

每次下墜到下一排的時候,可以有兩種選擇:

1.往左下方的格子點移動。

2.往右下方的格子點移動。


換句話說,當我們位在[row][col]這個格子點的時候,可以怎麼過來?
1.從左上方的格子點[row-1][col-1]掉下來
2.從右上方的格子點[row-1][col]掉下來


我們可以定義DP[row, col]代表從頂端下落到[row][col]的最小路徑成本


那麼,根據下墜規則,可以知道


DP[row, col]

=下落到[row][col]的最小路徑成本

=[row][col]自己的成本 + min{ 落到左上方格子點, 落到右上方格子點}

= triangle[row][col] + min{ DP[row-1][col-1], DP[row-1][col] }


什麼時候停下來?

回溯到最頂端的格子點的時候,也就是起點,可以停下來。

DP[row, col] = triangle[0][0] if (row, col) = (0, 0)


演算法 DP動態規劃

1.定義DP狀態

承接觀察點的思考,定義DP[row, col]代表從頂端下落到[row][col]的最小路徑成本


題目所求是從頂點落到最下層的最小路徑成本

題目所求 = min{ DP[row, col] where row == 最後一層的層數}


2.推導DP狀態轉移關係式

當我們位在[row][col]這個格子點的時候,可以怎麼過來?
1.從左上方的格子點[row-1][col-1]掉下來
2.從右上方的格子點[row-1][col]掉下來


那麼,根據下墜規則,可以知道


DP[row, col]

=下落到[row][col]的最小路徑成本

=[row][col]自己的成本 + min{ 落到左上方格子點, 落到右上方格子點}

= triangle[row][col] + min{ DP[row-1][col-1], DP[row-1][col] }


3.釐清DP初始狀態


什麼時候是最小規模的問題?

什麼時候往上回溯的過程可以停下來?


回溯到最頂端的格子點的時候,也就是起點,可以停下來。

DP[row, col] = triangle[0][0] if (row, col) = (0, 0)


程式碼 DP動態規劃

# O(n^2) aux space
# O(n^2) time
class Solution:
def minimumTotal(self, triangle: List[List[int]]) -> int:

INF = math.inf

# height of triangle
h = len(triangle)

dp = {}
def fall( row, col ):
if (row, col) in dp:
return dp[row, col]

if row == 0 and col == 0:
## Base case, top most element
dp[row, col] = triangle[0][0]
return triangle[0][0]

elif col < 0 or col > row:
## Base case, return infinity on out-of-boundary
dp[row, col] = INF
return INF

dp[row, col] = triangle[row][col] + min( fall(row-1, col-1), fall(row-1, col) )
return dp[row, col]
# ------------------------------------

# backtrack from bottom row to compute minimal cost of falling path
return min( fall(h-1, col) for col in range(h) )

複雜度分析

時間複雜度: O(n^2)

需要掃描過每個矩陣元素,去更新下墜路徑的最小成本,所需時間為O(n^2)。

空間複雜度: O(n^2)

DP table需要紀錄每個格子點的最小下墜路徑成本,所需空間為O(n^2)。


更進一步觀察,可以發現每一排只需要上一排的資訊。
也就是說,只要儲存目前 這一排 和 前一排 即可滿足計算最小成本的需求。

程式碼 DP動態規劃 空間優化


# O(n) aux space, optimization in space
# O(n^2) time
class Solution:
def minimumTotal(self, triangle: List[List[int]]) -> int:

## DP Table
# Key: row
# Value: minimal falling cost
dp = {}
def downfall(row):

## Look-up DP table
if row % 2 in dp:
return dp[row%2]

## Base case
if row == 0:
dp[row] = triangle[[0][0]]
return triangle[[0][0]]

## General case:
prevRow = downfall(row-1)

head = triangle[row][0] + prevRow[0]

curRow = []
for col in range(1, row):
curRow.append( triangle[row][col] + min(prevRow[col], prevRow[col-1]) )

tail = triangle[row][row] + prevRow[-1]

dp[ row%2 ] = [head] + curRow + [tail]
return dp[ row%2 ]
# ---------------------------------

return min( downfall( row= len(triangle)-1 ) )

複雜度分析

時間複雜度: O(n^2)

需要掃描過每個矩陣元素,去更新下墜路徑的最小成本,所需時間為O(n^2)。

空間複雜度: O(n)

DP table需要紀錄兩排格子點的最小下墜路徑成本,所需空間為O(2n)=O(n)。


再更進一步,發現其實計算順序都是從上往下,從左到右,爾且更新後不會回頭更改
因此可以直接拿掉DP table,直接in-place update在triangle陣列即可

# O(n) aux space, optimization in space
# O(n^2) time
class Solution:
def minimumTotal(self, triangle: List[List[int]]) -> int:

def downfall(row):

## Base case
if row == len(triangle):
# Already reach final level
return

## General case:
triangle[row][0] += triangle[row-1][0]

for col in range(1, row):
triangle[row][col] += min(triangle[row-1][col], triangle[row-1][col-1])

triangle[row][row] += triangle[row-1][row-1]

# down fall to next level
downfall(row+1)
return
# ---------------------------------
downfall( row=1 )

return min( triangle[-1] )

複雜度分析

時間複雜度: O(n^2)

需要掃描過每個矩陣元素,去更新下墜路徑的最小成本,所需時間為O(n^2)。

空間複雜度: O(n)

雖然沒有DP Table,但是遞迴呼叫還是需要run-time call stack,
所需空間為O(n)=O(n)。


既然都in-place update,那遞迴也可以打開成等價的for loop迭代,
節省遞迴stack空間,這樣空間就被優化成O(1),不需要額外的DP table和遞迴呼叫

# O(1) aux space, optimization in space, in-place update
# O(n^2) time
class Solution:
def minimumTotal(self, triangle: List[List[int]]) -> int:

height = len(triangle)

for row in range(1, height ):

## General case:
triangle[row][0] += triangle[row-1][0]

for col in range(1, row):
triangle[row][col] += min(triangle[row-1][col], triangle[row-1][col-1])

triangle[row][row] += triangle[row-1][row-1]

return min( triangle[height-1] )

複雜度分析

時間複雜度: O(n^2)

需要掃描過每個矩陣元素,去更新下墜路徑的最小成本,所需時間為O(n^2)。

空間複雜度: O(1)

In-place udpate,原位更新,不需要DP table,也不需要遞迴呼叫。

其他的臨時變數都是固定尺寸,所需空間為O(1)常數級別。


關鍵知識點 分析下墜規則

當我們位在[row][col]這個格子點的時候,可以怎麼過來?
1.從左上方的格子點[row-1][col-1]掉下來
2.從右上方的格子點[row-1][col]掉下來


那麼,根據下墜規則,可以知道


DP[row, col]

=下落到[row][col]的最小路徑成本

=[row][col]自己的成本 + min{ 落到左上方格子點, 落到右上方格子點}

= triangle[row][col] + min{ DP[row-1][col-1], DP[row-1][col] }


強烈建議複習高度相關的

一魚多吃 用DP解最小成本的下墜路徑和 Minimum Falling Path Sum_Leetcode #931

鞏固知識點,加深印象!


Reference

[1] Triangle - LeetCode


回到DP特訓班目錄 和 學習路徑

52會員
339內容數
由有業界實戰經驗的演算法工程師, 手把手教你建立解題的框架, 一步步寫出高效、清晰易懂的解題答案。 著重在讓讀者啟發思考、理解演算法,熟悉常見的演算法模板。 深入淺出地介紹題目背後所使用的演算法意義,融會貫通演算法與資料結構的應用。 在幾個經典的題目融入一道題目的多種解法,或者同一招解不同的題目,擴展廣度,並加深印象。
留言0
查看全部
發表第一個留言支持創作者!
你可能也想看
用英文叫別人「別那麼急/別那麼激動」遛狗結果狗狗在路上暴走、孩子看到遊樂園等不及要衝了、朋友說話太嗨讓你快要跟不上。用三句英文讓對方或你的毛小孩「消消火」。
Thumbnail
avatar
你的英日語自學導師 譯難忘  ོꦿ༄꧁꧂
2023-12-12
用一個英文單字講人有多麼「跩」「唉呦很跩喔」、「很『秋』哦」,中文的跩和台語的秋,可以形容人很得意有自信又有點霸氣。就這麼剛好英文的own這個字可以傳達出這種fu。想知道怎麼使用嗎?這篇用超白話讓你秒懂。
Thumbnail
avatar
你的英日語自學導師 譯難忘  ོꦿ༄꧁꧂
2023-10-24
工業用格子 市場: 規制要因、競争戦略、市場分割 市場の現状と将来展望に関する包括的な洞察を提供する、自動車照明市場2023年調査報告書がリリースされました。当レポートでは、業界の市場動向、成長促進要因、課題、機会などの詳細な分析に加え、競争環境と市場主要企業の市場シェア分析についても徹底検証しています。https://www.cienciared
avatar
ajay devgan
2023-07-18
(金曲34最佳台语专辑&歌后)听郑宜农如何用专辑《水逆》打破语言隔阂,坦诚真实唱出沟通的美妙与困境经历了为期两年的制作过程,携同制作人Chunho和配唱制作人ciacia何欣穗,郑宜农终于带着全新专辑《水逆》与听众见面了。这是一张关于沟通的专辑,而有趣的是,这也是她个人第一张全台语专辑。一起来听听郑宜农如何用这张专辑《水逆》来打破语言隔阂,坦诚真实唱出沟通的美妙与困境:
Thumbnail
avatar
英子
2023-07-02
「格子趣」-以STEAM知識概念及培養創意思考的遊戲「格子趣」-在日常生活中輕鬆簡單的運用STEAM概念及創意思考的遊戲,開發孩子大腦 格子? 什麼格子啊 #STEAM教育#培養孩子發揮創造力習慣 #開發創意潛能 #創造力教養
Thumbnail
avatar
Evelyn怡怡
2022-09-01
職場霸凌故事: 骨子裡的寒意,隔著面具也透光。你曾經也遭受過這樣對待嗎?好友在保險業多年,算一算有十八年了。可說是江湖老鳥。 中年後,決定從原先極為擅長的IT領域,轉換擔任保險業務。 這一決定掀起了家庭革命。反對的,意外地,不是床邊的另一半,而是先生的父母。 反對的,不是保險業務單單需要去拉人,而是原來穩定的薪資加上十八年的資歷以及未來能預想到的退休俸。 就是得請假。
Thumbnail
avatar
毛氈帽
2022-06-29
同樣引入LikeCoin,為何方格子沒有出現洗幣風潮?當年Matters整合LikeCoin鍵之後,版上一時出現洗幣的風氣,大量充斥為了領幣而寫的文章,跟當初封測時有天壤之別。然而這種現象,為什麼在方格子似乎沒有看見呢?我想主要原因有二個:1、方格子首頁曝光是由編輯挑選或是演算法推薦;2、方格子不會幫用戶註冊LikeCoin…
Thumbnail
avatar
Davina Shi
2022-05-01
刻進骨子裡的永續DNA 退休從農用自然農法種出一片天在花蓮吉安鄉,有一處能自行儲水、發電、緊鄰5甲稻田,自給自足的「方舟」。這座綠建築的擁有者謝景貴,為了家人以及這塊土地的未來,從金融業提早退休,搬到花蓮實踐「永續的生活態度」。這和他過去二十年的國際賑災經驗中,看到太多生離死別有關。
avatar
鄭群騰
2021-09-27
Noam | 電影《聽見歌 再唱》心疼方雅各這個角色:看似很敢,骨子卻是乖乖牌以下可能牽涉劇透,請斟酌閱讀。 看完電影後,朋友問:「你印象最深刻的是什麼?」我說:「方雅各這個角色。他給我感覺好衝突,教育上受過傷、卻成為深愛孩子的老師;看似衝勁很敢,骨子卻是乖乖牌;一直給出能量,卻也不停承接別人的情緒跟責任。而且他的情緒好平、好違和,像是一種壓抑跟害怕。」
Thumbnail
avatar
Noam
2021-05-19
8 個格子:如何運用內容表達模式圖建構簡報內容內容表達的架構發展至今已經有許多可行方案,例如:空雨傘、黃金圈、三幕式、準備/發展/整合(教學用)等模式。這些模式的底層邏輯類似,因此本文提出的方案是:將這些模式整合為一套「內容表達模式圖」,讓新手可以依照表格,快速的完成簡報的內容表達架構!
Thumbnail
avatar
南瓜太郎
2018-11-27