如何在代码中更好地实现递归关系?

时间:2017-10-19 19:22:49

标签: python algorithm recursion optimization mathematical-optimization

我已经在这个问题上苦苦挣扎了好几天了,我仍然是Python新手和更多的数学密集编码,所以任何帮助都会受到赞赏,只需指出我正确的方向:)

所以问题就像:

  

你有一个电影通行证,对 N 天有效。您可以以任何方式使用它,除了 连续3天或更长时间。

     

所以基本上,你既可以在某一天使用你的传球,也可以选择不传球,这意味着 2提升到N 的可能性。然后,声明通行证的有效方式是 2提升到N - invalidCases

     

您必须找到有效案例的数量%(10 ^ 9 + 7)

我找到了一个看似

的无效案例的递归关系
  

invalidCases(at_N)= 2 ^(n-4)+ 2 * invalidCases(at_N-1) - invalidCases(at_n-4)

所以我的第一个冲动就是简单地使用递归:

def invalidCases(n):
    if(n<3):
        return 0;
    elif(n==3):
        return 1;
    else:
        return 2**(n-4)+ 2*invalidCases(n-1)- invalidCases(n-4)

非常低效,但我的等式似乎是正确的。 我的下一次尝试,我尝试了memoization,但我在N = 1006时遇到了错误。 所以我改变了递归限制。

我目前的尝试(记忆和增加的递归限制)

import sys
sys.setrecursionlimit(10**6)
T=int(input());
#2**(n-4) + 2*ans(n-1)-ans(n-4)
memo={0:0,1:0,2:0,3:1,4:3,5:8,6:20,7:47} #
def ans(n):
    #complete this function
    if n not in memo:

        memo[n]=(2**(n-4) + 2*ans(n-1)-ans(n-4));



    return memo[n];

modulo = 10**9 + 7;
print((2**n-ans(n))%modulo);

最后,我的问题。 我需要这段代码才能用于n = 999999。

如何将最坏情况降至最低? 任何指针或提示都会很棒。

2 个答案:

答案 0 :(得分:0)

这是一个完整的解决方案,它基于以下观察:三天或三天以上的有效解决方案必须以下列之一开始:

0
10
110

其中1表示当天使用通行证,0表示不通行。

第一种形式有效(n-1)种可能性,第二种形式有效(n-2)种可能性,第三种形式有效(n-3)种可能性。

然后再次发生:

  

有效(n)=有效(n-1)+有效(n-2)+有效(n-3)

基本情况有效(0)= 1,有效(1)= 2,有效(2)= 4.重要的是要注意有效(0)是1,而不是零。这是因为当n = 0时恰好有一个解,即空序列。这不仅在数学上是正确的,而且还需要重现才能正常工作。

代码执行三项操作以使其快速运行:

  1. 它使用缓存来缓存结果(memoization),正如您所做的那样。
  2. 它不会存储完整的结果,而是首先应用模数,大大减少了数值范围。
  3. 它预加载缓存,从0开始并上升到所需的值。这会将最大递归深度减少到一个。
  4. 以下是代码:

    cache = {}
    modulus = 10**9 + 7
    
    def valid(n):
        if n in cache:
            return cache[n]
    
        if n == 0:
            v = 1
        elif n == 1:
            v = 2
        elif n == 2:
            v = 4
        else:
            v = valid(n-1) + valid(n-2) + valid(n-3)
    
        v %= modulus
    
        cache[n] = v
        return v
    
    def main():
        # Preload the cache
        for n in range(1000000):
            valid(n)
    
        print(valid(999999))
    
    main()
    

    这是输出:

    746580045
    

    它在我的系统上运行不到2秒。

    更新:这是一个最小的迭代解决方案,受到MFisherKDX使用的方法的启发。种子值的构建方式不需要特殊套管(初始v2有效(0)):

    modulus = 10**9 + 7
    
    def valid(n):
        v0, v1, v2 = 0, 1, 1
    
        for i in range(n):
            v0, v1, v2 = v1, v2, (v0 + v1 + v2) % modulus
    
        return v2
    
    print(valid(999999))
    

    这个解决方案可能会尽可能快。它在使用它们后会丢弃中间结果,如果你只调用一次这个函数就没问题。

答案 1 :(得分:0)

这是我的答案。自下而上的解决方案。与汤姆的回答相比,回答是自上而下的,同样有效。在carthage update的每一天,它会跟踪使用j次传递的可能性数量,以及j和{{1}使用传递的可能性数量}。

j

结果为j-1,我的系统需要400毫秒。