【问题标题】:How to improve this recursive function?如何改进这个递归函数?
【发布时间】:2020-05-04 13:17:30
【问题描述】:

如何改进这个递归函数,因为它太慢了。 本题取自project euler

问题:

从2×2网格的左上角开始,只能左右移动,到右下角正好有6条路线。

通过 20×20 的网格有多少条这样的路线?

MAX = 2
paths = 0

def a(x=0,y=0):
    if x==MAX and y==MAX:
        global paths
        paths+=1
        return
    if x>MAX or y>MAX:
        return
    a(x+1,y)
    a(x,y+1)
a()
print(paths)

它一直向下和向右测试,直到到达网格中的最后一个单元格。如果它与网格重叠,它将停止并移动到调用堆栈中的下一个函数

【问题讨论】:

标签: python recursion


【解决方案1】:

这个问题是使用记忆化应用动态规划原理的理想候选者,因为它包含重叠子问题最优子结构。您的递归解决方案需要很长时间因为它把大部分时间都花在了一遍又一遍地解决同一个问题上。

用途:

def find_paths(start, end, memo):
    if start == end:
        return 1
    elif start[0] > end[0] or start[1] > end[1]:
        return 0

    r_point, b_point = (start[0] + 1, start[1]), (start[0], start[1] + 1) 
    if not r_point in memo:
        memo[r_point] = find_paths(r_point, end, memo)

    if not b_point in memo:
        memo[b_point] = find_paths(b_point, end, memo)

    return memo[r_point] + memo[b_point]

调用函数:

print(find_paths((0, 0), (2, 2), {}))
print(find_paths((0, 0), (20, 20), {}))
print(find_paths((0, 0), (100, 100), {}))

打印出来:

6
137846528820
90548514656103281165404177077484163874504589675413336841320

【讨论】:

  • 尽管仍有更好的解决方案使用更优化的公式,但我认为这是用递归解决它的最佳方法。
  • 与斐波那契不同,我不明白我的代码如何一遍又一遍地解决相同的问题。
  • 假设从 (0, 0) 向下移动到 (0, 1),然后向右移动到 (1, 1)。同样在另一种情况下,如果您先向右移动 (1, 0),然后向下移动到 (1, 1)。在这两种情况下,您都将反复解决以 (1, 1) 为起点的子问题。
【解决方案2】:

这类似于帕斯卡的三角形。到达网格上的每个点需要上面和左侧位置的路径总和,向上到主对角线(帕斯卡级数),然后向下到目的地。

2x2

Pascal's   Rest
*--1--1    *--1--1
|  |  |    |  |  |
1--2--+    1--2--3
|  |  |    |  |  | 
1--+--+    1--3--6  ==> 6 paths

3x3

Pascal's      Rest
*--1--1--1    *--1--1--1 
|  |  |  |    |  |  |  |
1--2--3--+    1--2--3--4
|  |  |  |    |  |  |  |
1--3--+--+    1--3--6--10
|  |  |  |    |  |  |  |
1--+--+--+    1--4--10-20 ==> 20 paths

4x4

Pascal's       rest        
*--1--1--1--1  *--1--1--1--1
|  |  |  |  |  |  |  |  |  |
1--2--3--4--+  1--2--3--4--5
|  |  |  |  |  |  |  |  |  |
1--3--6--+--+  1--3--6--10-15 
|  |  |  |  |  |  |  |  |  |
1--4--+--+--+  1--4--10-20-35 
|  |  |  |  |  |  |  |  |  |
1--+--+--+--+  1--5--15-35-70 ==> 70 paths

此时,您可以做更多的数学运算,或者您可以实现一个高效的算法来计算结果:

N = 4
paths = [1]
for _ in range(N):
    paths = [ a+b for a,b in zip(paths,[0]+paths) ]+[1] # Pascal's
for _ in range(N):
    paths = [ a+b for a,b in zip(paths,paths[1:]) ]     # Rest
result = paths[0]

更多数学:如果将正方形扩大到 2N,您还会注意到结果是正好在主对角线中间的点。这是帕斯卡三角形第 2N 行的第 N 个值。

*--1--1--1--1··1··1··1··1  
|  |  |  |  |  :  :  :
1--2--3--4--5··+··+··8·· 
|  |  |  |  |  :  :
1--3--6--10-15·+··28··   
|  |  |  |  |  :
1--4--10-20-35·56·· 
|  |  |  |  |  
1--5--15-35-70··   <-- 70 is combinations of 4 in 8 
:  :  :  :    
1··+··+··56··
:  :  :    
1··+··28··
:  :    
1··8··
:   
1··

根据帕斯卡三角形的性质,这相当于2N个集合中N个值的组合数。

可以通过(2N)来计算! /N!^2:factorial(2*N)//factorial(N)**2

N=2 --> 4!/2!^2 --> 24/4 --> 6

N=3 --> 6!/3!^2 --> 720/36 --> 20

N=4 --> 8!/4!^2 --> 40320/576 --> 70

...

N=20 --> you do the math :)

【讨论】:

    【解决方案3】:

    有一个简单的数学解决方案。

    我们需要在我们拥有的 40 个位置中的任何一个位置上准确地向下移动 20 步(因为它总是向下 20+ 向右 20)

    因此,答案是 40 选择 20 = 137846528820

    更多问题信息here

    【讨论】:

    • 剧透警告:答案实际上是 137846528820
    • 已发布问题的答案适用于 20x20 网格。我知道这是正确的答案:)
    猜你喜欢
    • 2020-10-03
    • 1970-01-01
    • 1970-01-01
    • 2015-08-21
    • 2011-03-29
    • 2017-02-20
    • 1970-01-01
    相关资源
    最近更新 更多