【问题标题】:Non Brute Force Solution to Project Euler 25欧拉 25 项目的非暴力解决方案
【发布时间】:2014-01-15 12:31:39
【问题描述】:

Project Euler problem 25:

斐波那契数列由递归关系定义:

Fn = Fn−1 + Fn−2,其中 F1 = 1 和F2 = 1。因此前 12 项 将是 F1 = 1, F2 = 1, F3 = 2, F4 = 3, F5 = 5, F6 = 8, F7 = 13, F8 = 21, F9 = 34, F10 = 55, F11 = 89, F12 = 144

第 12 项 F12 是第一个包含三位数字的项。

斐波那契数列中包含 1000 的第一项是什么 数字?

我在 Python 中做了一个蛮力解决方案,但计算实际解决方案绝对需要很长时间。任何人都可以提出非暴力解决方案吗?

def Fibonacci(NthTerm):
    if NthTerm == 1 or NthTerm == 2:
        return 1 # Challenge defines 1st and 2nd term as == 1
    else:  # recursive definition of Fib term
        return Fibonacci(NthTerm-1) + Fibonacci(NthTerm-2)

FirstTerm = 0 # For scope to include Term in scope of print on line 13
for Term in range(1, 1000): # Arbitrary range
    FibValue = str(Fibonacci(Term)) # Convert integer to string for len()
    if len(FibValue) == 1000:
        FirstTerm = Term
        break # Stop there
    else:
        continue # Go to next number
print "The first term in the\nFibonacci sequence to\ncontain 1000 digits\nis the", FirstTerm, "term."

【问题讨论】:

  • 暴力解决方案不需要永远采用。是你的递归太慢了。只是谷歌更好的斐波那契算法
  • 你的蛮力复杂性太可怕了,你得到了 O(2^n)。尝试使用三个参数:要创建的项数和前两个结果。这就是你将得到的线性时间,而且它应该足够快。

标签: python fibonacci brute-force


【解决方案1】:

您可以编写一个以线性时间运行并具有恒定内存占用的斐波那契函数,您不需要一个列表来保存它们。 这是一个递归版本(但是,如果 n 足够大,它将只是 stackoverflow

def fib(a, b, n):
    if n == 1:
        return a
    else: 
        return fib(a+b, a, n-1)


print fib(1, 0, 10) # prints 55

这个函数只调用自己一次(导致大约 N 调用参数 N),而您的解决方案调用自身两次(大约 2^N 调用参数 N)。

这是一个永远不会stackoverflow并使用循环而不是递归的版本:

def fib(n):
    a = 1
    b = 0
    while n > 1:
        a, b = a+b, a
        n = n - 1
    return a

print fib(100000)

这已经足够快了:

$ time python fibo.py 
3364476487643178326662161200510754331030214846068006390656476...

real    0m0.869s

但是在得到足够大的结果之前调用fib 并不完美:该系列的第一个数字被多次计算。 您可以计算下一个斐波那契数并在同一循环中检查其大小:

a = 1
b = 0
n = 1
while len(str(a)) != 1000:
    a, b = a+b, a
    n = n + 1
print "%d has 1000 digits, n = %d" % (a, n)

【讨论】:

    【解决方案2】:

    为什么没有人为此使用生成器?这是一个蛮力解决方案,但速度非常快:

    def fibo():
        a = 0
        b = 1
        while True:
            yield b
            a,b = b,a+b
    

    这给出了一个计算斐波那契数列的生成器。例如

    f = fibo()
    [next(f) for i in range(10)]
    

    生产

    [1,1,2,3,5,8,13,21,34,55]
    

    使用这个,我们可以像这样解决问题:

    f = enumerate(fibo())
    x = 0
    while len(str(x)) < 1000:
        i,x = next(f)
    
    print("The %d-th term has %d digits"%(i+1,len(str(x))))
    

    这会产生输出

    The 4782-th term has 1000 digits
    

    生成器计算序列并逐个生成项,此解决方案几乎立即运行。

    【讨论】:

      【解决方案3】:

      只需对代码稍作改动,就可以对两件事进行大量优化。这两件事是:

      • 您使用其他两个斐波那契数来计算每个斐波那契数,从而导致指数复杂度(即使您只计算一个但很高的斐波那契数也会爆炸)。

      • 您不记得任何以前计算的斐波那契数来计算循环中的下一个数。

      只需记住所有计算出的斐波那契数作为Fibonacci 中的私有实现细节,您就可以摆脱这两个性能问题。您可以使用一个简单的动态数组来执行此操作,如果之前没有缓存结果,您可以将结果添加到该数组中。

      伪代码(我不会说 Python,但这很容易实现):

      def Fibonacci(NthTerm):
          if (cache contains NthTerm)
              return cache[NthTerm]
          else
              FibValue = Fibonacci(NthTerm-1) + Fibonacci(NthTerm-2)
              cache[NthTerm] = FibValue
              return FibValue
      

      这将导致非常有限的递归,因为只有在您已经知道(并缓存)第 (N-1) 个数字时才计算第 N 个斐波那契数。

      即使您需要任何斐波那契数(用于将来的问题),此优化仍然有效,但在这种特殊情况下,我们知道我们只需要记住最后两个数字,因为我们永远不会再次询问旧号码。因此,您不需要完整的数字列表,而只需要两个,您可以在主循环中的每一步“循环”。类似的东西

      f1, f2 = f2, f1 + f2
      

      在循环内和类似的初始化

      f1, f2 = 1, 1
      

      本质上将替换您的函数Fibonacci 及其性能问题,但它将您限制在这个有限的用例中。

      【讨论】:

        【解决方案4】:

        您可以尝试在binet's formula 上使用newton's approximation method。这个想法是在图上找到一条切线,并使用该线的 x 截距来近似图的零值。

        【讨论】:

        【解决方案5】:

        不用每次都递归计算每个term,做一个term数组,然后可以通过terms[-1]和terms[-2]相加来计算一个term

        【讨论】:

        • 不是最好的选择,他不需要保留所有的斐波那契数,只需要保留最后两个
        • 好点!不过,我对事情的幕后工作方式并不十分熟悉……似乎需要更多操作才能摆脱旧变量。这会牺牲内存速度吗?
        • 您可以将它们保存在每次迭代时更新的 2 个变量中,速度不变,内存占用保持不变
        【解决方案6】:

        这是恒定空间和线性时间的Java版本:

            static int q24(){
            int index = 3;
            BigInteger fn_2 = new BigInteger("1");
            BigInteger fn_1 = new BigInteger("1");
            BigInteger fn   = fn_1.add(fn_2);
            while(fn.toString().length()<1000){
                fn_2 = fn_1;
                fn_1 = fn;
                fn = fn_2.add(fn_1);
                index++;
            }       
            return index;
        }
        

        【讨论】:

          【解决方案7】:

          你可以使用记忆:

          m={}
          
          def fub(n):
               if n not in m:
                  if n <= 2 :
                     m[n] =  1
                  else:
                     m[n] =  fub(n-1) + fub(n-2)
          
               return m[n]
          
          i=1
          while len(str(fub(i))) != 1000:
             i+=1
          print(i)
          

          【讨论】:

            【解决方案8】:

            如果您使用的是 Kotlin,这是一个非常简单的解决方案。

            import java.math.BigInteger
            val bound: BigInteger = BigInteger.TEN.pow(999)
            var index = 2
            var fib = BigInteger.ONE
            var prevFib = BigInteger.ONE
            while (fib < bound) {
                prevFib = fib.also { fib += prevFib }
                index++
            }
            println(index)
            

            【讨论】:

              猜你喜欢
              • 2016-10-30
              • 1970-01-01
              • 2019-11-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-09-22
              • 1970-01-01
              相关资源
              最近更新 更多