Решение без грубой силы для проекта Эйлера 25

задача проекта Эйлера 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 thenFibonacci sequence toncontain 1000 digitsnis the", FirstTerm, "term."

8 ответов


вы можете написать функцию Фибоначчи, которая работает в линейном времени и с постоянным объемом памяти, вам не нужен список, чтобы сохранить их. Вот рекурсивная версия (однако, если 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).

вот версия, которая не будет ever 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)

использовать Бине формула. Это самый быстрый способ найти числа Фибоначчи, и он не использует рекурсию.


две вещи можно оптимизировать много с одним небольшим изменением в вашем коде. Вот эти две вещи:--6-->

  • вы вычисляете каждое число Фибоначчи, используя два других числа Фибоначчи, что приводит к экспоненциальной сложности (которая взрывается, даже если вы вычисляете только одно, но высокое число Фибоначчи).

  • вы не помните никакого предыдущего вычисленного числа Фибоначчи для вычисления следующего в вашем цикле.

просто помните все вычисленные числа Фибоначчи как частную деталь реализации в 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-е число Фибоначчи, только если вы уже знаете (и кэшируете) (N-1) - е число.

эта оптимизация работает, даже если вам нужно любой число Фибоначчи (для будущих задач), но в этом конкретном случае мы знаем, что нам нужно помнить только последние два числа, так как мы никогда не будем просить старые числа снова. Поэтому вам не нужен целый список чисел, а только два, которые вы "прокручиваете" для каждого шага в своем основном цикле. Что-то вроде

f1, f2 = f2, f1 + f2

внутри цикла и инициализации как

f1, f2 = 1, 1

по существу заменит вашу функцию Fibonacci и его проблемы с производительностью, но это ограничивает вас в этом случае использовать.


вы можете попробовать использовать метод приближения Ньютона on формула Бине. Идея состоит в том, чтобы найти касательную линию на графике и использовать X-перехват этой линии для аппроксимации значения нуля графика.


почему никто не использовал генераторы для этого? Это решение грубой силы, но оно очень быстрое:

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

генератор вычисляет последовательность и производит плане 1 на 1 и это решение работает почти немедленно.


вместо рекурсивного вычисления каждого члена каждый раз, сделать массив терминов, то вы можете вычислить термин, добавив термины[-1] и термины[-2]


вот версия 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;
}

вы можете использовать запоминание :

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)