给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[
[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)的值即可。