我有一个有两百行的三角形,我必须找到从三角形的顶部到底部的最大距离。
5
9 8
5 4 6
9 7 3 4
这里,最短距离是5 + 8 + 4 + 3 = 20。最大距离为5 + 9 + 5 + 9 = 28。
我很清楚我想要实现的算法,但我很难将其转化为代码。
我的计划是:从第2行到最后一行开始,从底行添加最大可能路径,然后迭代到顶部。
例如,上面的三角形会变成:
28
23 19
14 11 10
9 7 3 4
这比暴力迫害效率高得多,但我有两个一般性问题:
使用暴力,如何列出从顶部到的所有可能路径 底部(只能移动到相邻点)?我试过用这个 (三角形是包含三角形的列表列表):
points=list(itertools.product(*triangle))
但是这包含了每行的所有可能组合,而不仅仅是 相邻成员。 Project Euler #18 - how to brute force all possible paths in tree-like structure using Python? 这有点解释了一种可能的方法,但我想使用 itertools和任何其他模块(尽可能pythonic)
我将如何重复添加每个最大值的策略 从前一行并迭代到顶部?我知道我必须这样做 实现一个嵌套循环:
for x in triangle:
for i in x:
i+=? #<-Not sure if this would even increment it
edit:
what I was thinking was:
triangle[y][x] = max([triangle[y+1][x],triangle[y+1][x+1]])
答案 0 :(得分:2)
回答你的第一个问题(如何在所有路径上进行暴力迭代):如果你从三角形的顶部开始并沿着一些随机路径向下移动,你必须做出向左或向右移动的决定你失望的水平。因此,不同路径的数量为2^(nrows-1)
。对于200行的问题,因此有8e59个不同的路径,这是以蛮力方式检查的方法。
对于一个小三角形,你仍然可以用暴力方式迭代所有可能的路径,例如:
In [10]: from itertools import product
In [11]: triangle = [[5], [9,8], [5,4,6], [9,7,3,4]]
In [12]: for decisions in product((0,1), repeat = len(triangle)-1):
...: pos = 0
...: path = [triangle[0][0]]
...: for lr, row in zip(decisions, triangle[1:]):
...: pos += lr # cumulative sum of left-right decisions
...: path.append(row[pos])
...: print path
[5, 9, 5, 9]
[5, 9, 5, 7]
[5, 9, 4, 7]
[5, 9, 4, 3]
[5, 8, 4, 7]
[5, 8, 4, 3]
[5, 8, 6, 3]
[5, 8, 6, 4]
这样做的方法是使用itertools.product
迭代nrows-1
左/右决定的所有可能组合,其中0表示向左走,1表示向右走(所以你更多或者少生成所有二进制数的位,直到2^(nrows-1)
)。如果将三角形存储为列表列表,则向左移动意味着在下一行中保持相同的索引,而向右移动意味着添加1.为了跟踪行中的位置,您只需计算所有的累积总和左/右决定。
回答你的第二个问题:首先,你的算法看起来非常好,你只需要在所有行上向后迭代一次,而你没有像蛮力解决方案那样检查指数数量的情况。我唯一要补充的是建立一个新的三角形,它在每一步都指示最大值是在左侧还是右侧。这对于重建之后的最佳路径很有用。所有这些都可以像这样实现:
mx = triangle[-1] # maximum distances so far, start with last row
directions = [] # upside down triangle with left/right direction towards max
for row in reversed(triangle[:-1]): # iterate from penultimate row backwards
directions.append([l < r for l, r in zip(mx[:-1], mx[1:])])
mx = [x + max(l, r) for x, l, r in zip(row, mx[:-1], mx[1:])]
print 'Maximum so far:', mx
print 'The maximum distance is', mx[0]
directions.reverse()
pos = 0
path = [triangle[0][0]]
for direction, row in zip(directions, triangle[1:]):
pos += direction[pos]
path.append(row[pos])
print 'The optimal path is', path
和以前一样,我使用了False = 0
和True = 1
来表示向左和向右的技巧。使用与以前相同的三角形,结果:
Maximum so far: [14, 11, 10]
Maximum so far: [23, 19]
Maximum so far: [28]
The maximum distance is 28
The optimal path is [5, 9, 5, 9]
答案 1 :(得分:2)
它不使用itertools,它是递归的,但我会记住结果,所以它仍然很快......
def memoize(function):
memo = {}
def wrapper(*args):
if args in memo:
return memo[args]
else:
rv = function(*args)
memo[args] = rv
return rv
return wrapper
@memoize
def getmaxofsub(x, y):
if y == len(triangle) or x>y: return 0
#print x, y
return triangle[y][x] + max(getmaxofsub(x, y+1), getmaxofsub(x+1, y+1))
getmaxofsub(0,0)
我多次阅读你的算法建议,你的“累积三角形”存储在memoized装饰器的memo
中,所以最后它非常相似。如果你想在通过三角形递归“向下调用”期间防止存在大堆栈,你可以通过调用getmaxofsub()
bottom - &gt;来填充memoize的缓存。起来。
for i in reversed(range(len(triangle))):
getmaxofsub(0, i), getmaxofsub(i//2, i), getmaxofsub(i, i)
print getmaxofsub(0,0)
修改强>
getmaxofsub
:这个功能如何运作?首先你必须知道,你不能在子三角形中划分三角形。我以你的三角形为例:
5
9 8
5 4 6
9 7 3 4
那是完整的。峰的“坐标”是x = 0,y = 0。
现在我提取峰x = 0,y = 1的子三角形:
9
5 4
9 7 3
或x = 1,y = 2
4
7 3
这就是我的算法的工作原理:整个三角形的峰值(x = 0,y = 0)询问其子三角形(x = 0,y = 1)和(x = 1,y = 1), “你到地面的最大距离是多少?”他们每个人都会问他们的子三角形,等等......
这将持续到函数到达地面/ y==len(triangle)
:地面条目想要询问它们的子三角形,但由于它们不是那些,它们得到答案0
。
在每个三角形调用它们的子三角形之后,它决定哪一个是更大的三角形,添加它们自己的值并返回这个总和。
现在你看,这个算法的原理是什么。这些算法称为recursive algorithms。你看,一个调用自身的函数非常标准......它可以工作......
所以,如果你考虑整个算法,你会看到很多子三角被调用几次,他们会问他们的子三角形等等...但每次它们返回相同的值。这就是我使用memorize
- 装饰器的原因:如果使用相同的参数x
和y
调用函数,装饰器将返回这些参数的最后一个计算值,并防止时间 - 消耗计算......这是一个简单的缓存......
这就是为什么这个函数像递归算法一样容易实现,并且像迭代一样快......