简体   繁体   中英

Project Euler 104: Need help in understanding the solution

Project Euler Q104 ( https://projecteuler.net/problem=104 ) is as such:

The Fibonacci sequence is defined by the recurrence relation:

Fn = Fn−1 + Fn−2, where F1 = 1 and F2 = 1. It turns out that F541, which contains 113 digits, is the first Fibonacci number for which the last nine digits are 1-9 pandigital (contain all the digits 1 to 9, but not necessarily in order). And F2749, which contains 575 digits, is the first Fibonacci number for which the first nine digits are 1-9 pandigital.

Given that Fk is the first Fibonacci number for which the first nine digits AND the last nine digits are 1-9 pandigital, find k.

And I wrote this simple code in Python:

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

k = 0
fibG = fibGen()
while True:
    k += 1
    x = str(fibG.next())
    if (set(x[-9:]) == set("123456789")):
        print x #debugging print statement
        if(set(x[:9]) == set("123456789")):
            break

print k

However, it was taking well.. forever.

After leaving it running for 30 mins, puzzled, I gave up and checked the solution.

I came across this code in C#:

long fn2 = 1;
long fn1 = 1;
long fn;

long tailcut = 1000000000;

int n = 2;
bool found = false;

while (!found) {
    n++;
    fn = (fn1 + fn2) % tailcut;
    fn2 = fn1;
    fn1 = fn;

    if (IsPandigital(fn)) {
        double t = (n * 0.20898764024997873 - 0.3494850021680094);
        if (IsPandigital((long)Math.Pow(10, t - (long)t + 8)))
            found = true;
    }
}

Which.. I could barely understand. I tried it out in VS, got the correct answer and checked the thread for help.

I found these two, similar looking answers in Python then.

One here, http://blog.dreamshire.com/project-euler-104-solution/

And one from the thread:

from math import sqrt

def isPandigital(s):
    return set(s) == set('123456789')

rt5=sqrt(5)
def check_first_digits(n):
    def mypow( x, n ):
        res=1.0
        for i in xrange(n):
            res *= x
            # truncation to avoid overflow:
            if res>1E20: res*=1E-10
        return res
    # this is an approximation for large n:
    F = mypow( (1+rt5)/2, n )/rt5
    s = '%f' % F
    if isPandigital(s[:9]):
        print n
        return True

a, b, n = 1, 1, 1
while True:
    if isPandigital( str(a)[-9:] ):
        print a
        # Only when last digits are
        # pandigital check the first digits:
        if check_first_digits(n):
            break
    a, b = b, a+b
    b=b%1000000000
    n += 1

print n

These worked pretty fast, under 1 minute! I really need help understanding these solutions. I don't really know the meaning or the reason behind using stuff like log. And though I could easily do the first 30 questions, I cannot understand these tougher ones.

How is the best way to solve this question and how these solutions are implementing it?

These two solutions work on the bases that as fibonacci numbers get bigger, the ratio between two consecutive terms gets closer to a number known as the Golden Ratio , (1+sqrt(5))/2 , roughly 1.618. If you have one (large) fibonacci number, you can easily calculate the next, just by multiplying it by that number.

We know from the question that only large fibonacci numbers are going to satisfy the conditions, so we can use that to quickly calculate the parts of the sequence we're interested in.

In your implementation, to calculate fib(n) , you need to calculate fib(n-1) , which needs to calculate fib(n-2) , which needs to calculate fib(n-3) etc, and it needs to calculate fib(n-2) , which calculates fib(n-3) etc. That's a huge number of function calls when n is big. Having a single calculation to know what number comes next is a huge speed increase. A computer scientist would call the first method O(n^2)*: to calculate fib(n) , you need n^2 sub calculations. Using the golden mean, the fibonacci sequence becomes (approximately, but close enouigh for what we need):

(using phi = (1+sqrt(5))/2)

1
1*phi
1*phi*phi = pow(phi, 2)
1*phi*phi*phi = pow(phi, 3)
...
1*phi*...*phi = pow(phi, n)
 \  n times /

So, you can do an O(1) calculation: fib(n): return round(pow(golden_ratio, n)/(5**0.5))

Next, there's a couple of simplifications that let you use smaller numbers.

If I'm concerned about the last nine digits of a number, what happens further up isn't all that important, so I can throw anything after the 9th digit from the right away. That's what b=b%1000000000 or fn = (fn1 + fn2) % tailcut; are doing. % is the modulus operator , which says, if I divide the left number by the right, what's the remainder?

It's easiest to explain with equivalent code:

def mod(a,b):
    while a > b:
        a -= b
    return a

So, there's a quick addition loop that adds together the last nine digits of fibonacci numbers, waiting for them to be pandigital. If it is, it calculates the whole value of the fibonacci number, and check the first nine digits.

Let me know if I need to cover anything in more detail.

* https://en.wikipedia.org/wiki/Big_O_notation

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM