题目:
如图示,以上三角形由一连串的数字构成,求从顶点 2 开始走到最底部的最短路径。每次只能向当前节点下面的两个节点走,如 3 可以向 6 或 5 走,不能直接走到 7。
首先我们需要用一个二维数组来表示这个三个角形的节点,用二维数组显然可以做到, 第一行的 2 用 a[0] [0] 表示,第二行元素 3, 4 用 a[1] [0],a[1] [1],依此类推。
题是公众号上找的,所以图是拿过来的((
公众号上用了三种方法说是做DP的步骤 (我咋没发现有啥必然的联系。。因为我菜么),这里都用python实现下。(python都这么慢了还有优化的必要么🤣主要是学下想法)
-
是否能用递归解题?
def traverse(i,j): totalRow=4 if i>=totalRow-1:#到底了 return 0 #往左下节点走 leftSum=traverse(i+1,j)+triangle[i+1][j] #往右下节点走 rightSum=traverse(i+1,j+1)+triangle[i+1][j+1] return min(leftSum,rightSum) triangle=[[2,0,0,0],[3,4,0,0],[6,5,7,0],[4,1,8,3]] sum=traverse(0,0)+triangle[0][0] print(sum)
这里时间复杂度O(2N)呈指数增长。。这怎么行哪。
-
为了降低时间复杂度,剪一下枝。
关于HashMap:
HashMap是一个用于存储Key-Value键值对的集合。使用哈希表可以进行非常快速的查找操作,查找时间为常数,同时不需要元素排列有序;python的内建数据类型:字典,就是用哈希表实现的。
和list比较,dict有以下几个特点:
- 查找和插入的速度极快,不会随着key的增加而变慢;
- 需要占用大量的内存,内存浪费多。
而list相反:
- 查找和插入的时间随着元素的增加而增加;
- 占用空间小,浪费内存很少。
所以,dict是用空间来换取时间的一种方法。
def traverse(i,j): global HashMap str_key=str(i)+' '+str(j) if (str_key in HashMap):#若已有记录,则不需再走 return HashMap[str_key] totalRow=4 if i>=totalRow-1: return 0 leftSum=traverse(i+1,j)+triangle[i+1][j] rightSum=traverse(i+1,j+1)+triangle[i+1][j+1] result=min(leftSum,rightSum) HashMap[str_key]=result#添加路径信息:位于triangle[i][j]时的最短路 return result HashMap={} triangle=[[2,0,0,0],[3,4,0,0],[6,5,7,0],[4,1,8,3]] sum=traverse(0,0)+triangle[0][0] print(sum)
时间复杂度降低到O(N),空间复杂度上升为O(N)
-
改用自顶向上方法(递推),即DP
分析(公众号写的很好(~ ̄▽ ̄)~
水):要求节点 2 到底部边的最短路径,只要先求得节点 3 和 节点 4 到底部的最短路径值,然后取这两者之中的最小值再加 2 不就是从 2 到底部的最短路径了吗,同理,要求节点 3 或 节点 4 到底部的最小值,只要求它们的左右节点到底部的最短路径再取两者的最小值再加节点本身的值(3 或 4)即可。我们知道对于三角形的最后一层节点,它们到底部的最短路径就是其本身,于是问题转化为了已知最后一层节点的最小值怎么求倒数第二层到最开始的节点到底部的最小值了。还有个图例:
这就完了嘛,每步都更新下当前行的值,后边往上都一样推。
我们要求每个节点到底部的最短路径,于是 DP 状态 DP[i, j] 定义为 i, j 的节点到底部的最小值,DP状态转移方程定义如下:
DP[i,j] = min(DP[i+1, j], D[i+1, j+1]) + triangle[i,j]
代码如下:
def traverse(triangle):
Row=len(triangle)
mini=triangle[Row-1]#mini:最后一行的节点信息
for i in range(Row-2,-1,-1):#从倒第二行求起,因为底部那行的值已经定了
j=0#开始刷新第i行
while j<sum([triangle[i][x]!=0 for x in range(Row)]):
#这行的真正(非零)长度
mini[j]=triangle[i][j]+min(mini[j],mini[j+1])
#上面那行很妙啊,mini[0]变成倒第二行的成品了,却不影响后边的计算
j+=1
return mini[0]
triangle=[[2,0,0,0],[3,4,0,0],[6,5,7,0],[4,1,8,3]]
print(traverse(triangle))
每个节点到底部的最短路径只与它下一层的 D[i+1, j], D[i+1, j+1] 有关(这就是最优子结构),所以只要把每一层节点的 DP[i, j] 求出来保存到一个数组里(mini)即可。当自下而上遍历完成了,mini[0] 的值即为DP[0,0],即为节点2到底部的最短路径。当然,你也可以定义一个二维数组来保存所有的 DP[i, j],只不过多耗些空间罢了(这里就是二维降一维啊,搜噶)。
- 求解动态规划最重要的是要找出状态转移方程,这需要在自下而上的推导中仔细观察。