題目
給定一個三角形,找出自頂向下的最小路徑和。每一步只能移動到下一行中相鄰的結點上。
例如,給定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自頂向下的最小路徑和爲 11(即,2 + 3 + 5 + 1 = 11)。
說明:
如果你可以只使用 O(n) 的額外空間(n 爲三角形的總行數)來解決這個問題,那麼你的算法會很加分。
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/triangle
思路
題目要求每一步只能移動到下一行中相鄰的節點上。比如3只能移動到6或5;4只能移動到5或7。
我們先來思考下能否遞歸的解決,它內部是否有遞歸的結構。
我們自頂向下的思考問題,先考慮最上面的那一層(第0層,用(0,0)表示),然後向下移動一步,只能是3(1,0)或4(1,1)。假設我們已經知道3或4哪個是最小的。
那麼直接用當前路徑(2)加上經過3或4中路徑最小的即可。
這裏用(level,index)表示這顆樹上的某個節點,比如頂層(第0層)就是(0,0);第1層就是(1,0)和(1,1)。
我們這裏簡單的畫一下遞歸樹,可以看到只畫了三層就已經存在重疊子問題了。我們先用遞歸算法寫出來,然後用記憶化搜索來優化,最後改成動態規劃。
代碼
遞歸
class Solution(object):
def minimum(self,triangle,i,level,last_level):
if level == last_level:
return triangle[level][i]#如果是最底層,返回本身
#否則返回當前節點值加下層中最小值
return triangle[level][i] + min(
self.minimum(triangle,i,level+1,last_level),
self.minimum(triangle,i+1,level+1,last_level))
def minimumTotal(self, triangle):
"""
:type triangle: List[List[int]]
:rtype: int
"""
last_level = len(triangle) - 1 # 保存了最後一層的值
return self.minimum(triangle,0,0,last_level) #從(0,0)開始
整個遞歸思路是沒問題的,雖然耗時較長導致超時,但是我們可以基於此思路改寫成記憶化搜索的方式(參閱LeetCode刷題之動態規劃思想)。
記憶化搜索
class Solution(object):
def minimum(self,triangle,i,level,last_level,dp):
if level == last_level:
return triangle[level][i]
# 通過dp保存了(level,i)的計算值,防止重複計算
if (level,i) not in dp:
dp[(level,i)] = triangle[level][i] + min(
self.minimum(triangle,i,level+1,last_level,dp),
self.minimum(triangle,i+1,level+1,last_level,dp)) #否則返回當前節點值加下層
return dp[(level,i)]
def minimumTotal(self, triangle):
"""
:type triangle: List[List[int]]
:rtype: int
"""
dp = {}
last_level = len(triangle) - 1
return self.minimum(triangle,0,0,last_level,dp)
改寫的方式就比較直觀,這裏把dp
作爲參數進行遞歸傳遞。
很好,改成記憶化搜索的方式就可以通過了。
但是我們的最終目標是改成動態規劃的形式。
動態規劃
class Solution(object):
def minimumTotal(self, triangle):
"""
:type triangle: List[List[int]]
:rtype: int
"""
last_level = len(triangle) - 1
for level in range(last_level-1,-1,-1):#由底向上,從倒數第2層到第0層
for i in range(0,level+1):#歸納法,第level層,最多有level+1個節點
triangle[level][i] = triangle[level][i] + min(triangle[level+1][i],triangle[level+1][i+1])
return triangle[0][0]
這裏我們通過改寫原列表,可以省去了dp
這個字典結構。從倒數第二層開始更新最小路徑值,直到第0層,最終輸出(0,0)的值即可。