Project Euler Problem #27:

Problem #27 is one of the many problems on Project Euler that concerns prime numbers. Here is the question:

Project Euler Problem 27: Quadratic primes
Euler discovered the remarkable quadratic formula:

n^2 + n + 41

It turns out that the formula will produce 40 primes for the consecutive integer values 0 ≤ n ≤ 39. However, when

n = 40, 40^2 + 40 + 41 = 40(40+1) + 41 is divisible by 41, and certainly when n = 41, 41^2 + 41 + 41 is clearly divisible by 41.

The incredible formula n^2 - 79n + 1601 was discovered, which produces 80 primes for the consecutive values 0≤n≤79. The product of the coefficients, -79 and 1601, is -126479.

Considering quadratics of the form:

n^2 + an + b, where |a| < 1000 and |b| ≤ 1000
where |n| is the modulus/absolute value of n
e.g. |11| = 11 and |-4| = 4

Find the product of the coefficients, a and b, for the quadratic expression that produces the maximum number of primes for consecutive values of n, starting with n = 0.

With 1999 possible values of a and 2001 possible values of b, it would be quite difficult to check all possible pairs (a,b). Luckily, we don’t have to:

Solution #1: Sieve Approach

We can observe that if f(n) = n^2 + an + b, then f(0) = b and f(1) = 1 + a + b. Because we know the maximum chain of primes has at least 2 elements, we know that both b and (1+a+b) must be prime. b must be a prime between 2 and 1000 inclusive by the contents of the problem. Based on the value of b, a must be chosen such that (1+a+b) is a prime between 2 and 2000 inclusive. Given these facts, we can check every possible pair (a,b) that satisfies these specific conditions to greatly reduce the potential number of pairs.

To do this, we create a sieve of the primes from 2 to 1000 for the potential values of b and then a sieve of the primes from 2 to 2000 for the potential values of a. Here is an implementation of this method in Python 2.7:

'''
Author: Walker Kroubalkian
Sieve Approach to Project Euler Problem #27
'''

import time
from math import sqrt

def sieveEratosthenes(n):
    myPrimes = []
    primePossible = [True]*(n+1)
    primePossible[0] = False
    primePossible[1] = False

    for (i,possible) in enumerate(primePossible):
        if possible:
            for x in range(i*i, (n+1), i):
                primePossible[x] = False
            myPrimes.append(i)
    return myPrimes

def isPrime(n):
    if(n>1 and n<4):
        return True
    if(n%2==0 or n<=1):
        return False
    c = 3
    while(c<=sqrt(n)):
        if(n%c==0):
            return False
        c+=2
    return True

def projectEulerProblemTwentySeven(n):
    bPossible = sieveEratosthenes(n)
    aPossible = sieveEratosthenes(2*n)
    maximumSequence = 40
    maxA = 1
    maxB = 41
    for x in bPossible:
        for y in aPossible:
            if abs(y-x-1)>=n:
                break
            b = x
            a = y-x-1
            i = 2
            while(True):
                if(isPrime(i*i+a*i+b):
                    i+=1
                else:
                    break
            if(i>maximumSequence):
                maximumSequence = i
                maxA = a
                maxB = b
    return maxA*maxB

start = time.time()
print projectEulerProblemTwentySeven(1000)
print ("--- %s seconds ---" % (time.time()-start))

'''
Prints

-59231
--- 0.0769238471985 seconds ---

for input of n = 1000
'''

Once again, prime sieves appear to be very helpful in solving Project Euler questions.

That’s all for today, folks. Thanks for reading!

Project Euler Problem #26:

Problem #26 is the first Project Euler problem to feature repeating decimals. The question reads:

Project Euler Problem 26: Reciprocal cycles
A unit fraction contains 1 in the numerator. The decimal representation of the unit fractions with denominators 2 to 10 are given:

1/2 = 0.5
1/3 = 0.(3)
1/4 = 0.25
1/5 = 0.2
1/6 = 0.1(6)
1/7 = 0.(142857)
1/8 = 0.125
1/9 = 0.(1)
1/10 = 0.1

Where 0.1(6) means 0.166666..., and has a 1-digit recurring cycle. It can be seen that 1/7 has a 6-digit recurring cycle.

Find the value of d<1000 for which 1/d contains the longest recurring cycle in its decimal fraction part.

It should be clear that having prior mathematical knowledge about repeating decimals in base 10 is crucial to solving this problem. Here is my solution:

Solution #1: Mathematical Approach

First of all, it is worth determining for what d will 1/d end in a recurring cycle of digits. If we write d = p_1 * p_2 * p_3 * . . . * p_x (where p_1, p_2, . . . p_x are the not necessarily distinct prime factors of d), then we can also write 1/d = 1/(p_1) * 1/(p_2) * . . . * 1/(p_x). It is easy to see that if none of 1/(p_1), 1/(p_2), . . . 1/(p_x) end in a recurring cycle of digits, then 1/d will not end in a recurring cycle of digits either. Therefore, it makes sense to check when 1/p will end in a recurring cycle of digits for prime numbers p.

We can instantly notice that 1/2 = 0.5 and 1/5 = 0.2 do not end in a recurring cycle of digits. However, it appears that for all other prime numbers p, 1/p ends in a recurring cycle of digits. This makes sense, as if the decimal portion of a number terminates, then we should be able to write it as a/(10^x) for some integers a, x. 2 and 5 are the only prime numbers that could possibly divide 10^x, so all other primes cannot be in the denominator of a fraction that terminates. With this observation, we can determine that the length of the recurring cycle depends solely on the largest factor of d which is not divisibly by 2 or 5.

Next we can notice that the fraction a/(10^x – 1) = a/(10^x) + a/(10^(2x)) + a/(10^(3x)) + . . . using the formula for an infinite geometric series. It is clear that this fraction will have a recurring cycle of digits of length x as there are x digits between each fraction on the RHS of this equation. The only problem is if each repeating cycle of digits is composed of multiple cycles of a shorter repeating cycle of digits. Thus, it suffices to find the minimum value of x such that 1/d = a/(10^x – 1) for some integer (or terminating decimal) a.

With all of these observations, the method can be summarized as follows: First divide out all factors of 2 and 5 from d. Then find the smallest power of 10 which leaves a remainder of 1 when divided by d. (This is known as the order of 10 under the modulus d). After doing this for all possible values of d, we should be able to find the maximum repeating cycle. Here is an implementation of this method in Python 2.7:

 '''
 Author: Walker Kroubalkian
 Order of 10 Approach to Project Euler Problem #26
 '''

 import time

 def projectEulerProblemTwentySix(n):
     maxIndex = 1
     maxCycle = 0
     for i in range(2,n):
         temp = i
         while(temp%2==0):
             temp/=2
         while(temp%5==0):
             temp/=5
         if(temp!=1):
             e = 1
             p = 10
             while(p%temp!=1):
                 p*=10
                 p%=temp
                 e+=1
             if(e>maxCycle):
                 maxIndex = i
                 maxCycle = e
     return maxIndex

 start = time.time()
 print projectEulerProblemTwentySix(1000)
 print ("--- %s seconds ---" % (time.time()-start))

 '''
 Prints

 983
 --- 0.0106329917908 seconds ---

 for input of n=1000
 '''

And just like that, we’re done. As we get to the more complex problems on Project Euler, a background in math will become more and more important.

Thanks for reading! See you tomorrow.

Project Euler Problem #25:

Problem #25 is yet another problem where having a background in math makes the problem much easier. It is also another question that concerns the Fibonacci sequence. The question reads:

Project Euler Problem 25: 1000-digit Fibonacci number
The Fibonacci sequence is defined by the recurrence relation:

F_n = F_(n-1)+F_(n-2), where F_1 = 1 and F_2 = 1.

Hence, the first 12 terms will be:

F_1 = 1
F_2 = 1
F_3 = 2
F_4 = 3
F_5 = 5
F_6 = 8
F_7 = 13
F_8 = 21
F_9 = 34
F_10 = 55
F_11 = 89
F_12 = 144

The 12th term, F_12, is the first term to contain three digits.

What is the index of the first term in the Fibonacci sequence to contain 1000 digits?

There is a specific formula which makes this problem much easier. Using it, we get the following solution:

Solution #1: Logarithmic Approach

Binet’s Formula tells us that the nth Fibonacci number is equal to:

\[F_n=\frac{1}{\sqrt{5}}\left(\left(\frac{1+\sqrt{5}}{2}\right)^n-\left(\frac{1-\sqrt{5}}{2}\right)^n\right)\]

(Credits to Art of Problem Solving for the LaTeX) We can notice that as n gets really big, the second term ((1-sqrt(5))/2)^n gets really small, to the point where it hardly affects the value of the number. Thus, we can safely say that for large values of n, F_n is approximately (((1+sqrt(5))/2)^n)/sqrt(5). If we set this expression equal to the smallest 1000-digit number, 10^999 and solve for n, we can round up to get the index of the first 1000-digit Fibonacci number. Note that because this relies on an approximation, this method will not work for all numbers and will be off by one for some numbers.

At this point, the question essentially revolves around finding ceiling(log_((1+sqrt(5))/2) (sqrt(5)*10^999)). We can easily find this with the following Python 2.7 script:

 '''
 Author: Walker Kroubalkian
 Logarithmic Approach to Project Euler Problem #25
 '''

 import time
 import math

 def projectEulerProblemTwentyFive(n):
     golden = (1+math.sqrt(5))/2
     return int(math.ceil(math.log(math.sqrt(5),golden)+(n-1)*math.log(10, golden)))

 start = time.time()
 print projectEulerProblemTwentyFive(1000)
 print ("--- %s seconds ---" % (time.time() - start))

 '''
 Prints

 4782
 --- 2.121925354e-05 seconds ---

 for input of n = 1000
 '''

And with that, we’re done. As it turns out, Python 2.7 cannot store values greater than 10^999, so I had to use some properties of logarithms to make the solution more efficient.

And with that, we have solved 25 questions, less than 1/26th of all of the problems on Project Euler. I got the achievements “The Journey Begins” and “Easy Prey” for reaching Level 1 by solving 25 problems and solving 25 of the 50 easiest problems, respectively after solving this problem. Here’s a screenshot of me reaching Level 1:

Thanks for reading! I’ll see you tomorrow.

Project Euler Problem #24:

Problem #24 is similar to Problem #15 in that they both commonly appear on introductory math competitions such as the AMC 10/12 and Mathcounts. However, unlike Problem #15, this question is pretty annoying. The question reads:

Project Euler Problem 24: Lexicographic permutations
A permutation is an ordered arrangement of objects. For example, 3124 is one possible permutation of the digits 1, 2, 3 and 4. If all of the permutations are listed numerically or alphabetically, we call it lexicographic order. The lexicographic permutations of 0, 1, and 2 are:

012 021 102 120 201 210

What is the millionth lexicographic permutation of the digits 0, 1, 2, 3, 4, 5, 6, 7, 8, and 9?

I don’t know why, but I’ve always found this problem to be incredibly annoying as you are very susceptible to “off by one” errors while solving it. Here’s my solution:

Solution #1: Brute Force Approach

The key is to notice that there are precisely 9! (9 factorial) permutations that begin with 0, 9! that begin with 1, and so on, with each starting digit getting exactly 9! permutations. Once a starting digit is chosen, the problem reverts to a version with 1 less potential digit (but the index of the permutation decreases based on the starting digit).

A starting digit of 0 covers permutations 1 – 9!, a starting digit of 1 covers permutations 9!+1 – 2*9!, and so on. I found this pretty annoying, so I shifted my solution (and the problem) to a 0-based index where by decreasing the index (1,000,000) by 1, I could instead say that 0 covers permutations 0 – (9!-1), 1 covers permutations 9! – (2*9!-1), and so on. After making this change, I just repeatedly used the first observation to find each digit moving from left to right. Here is an implementation of this method in Python 2.7:

 '''
 Author: Walker Kroubalkian
 Brute Force Approach to Project Euler Problem #24
 '''

 import time

 def factorial(n):
     total = 1
     for c in range(2,n+1):
         total*=c
     return total

 def projectEulerProblemTwentyFour(n, x):
     myFactorials = []
     for i in range(x):
         myFactorials.append(factorial(x-1-i))

     myString = ""
     n-=1
     counter = 0

     possibleDigits = []
     for i in range(x):
         possibleDigits.append(str(i))

     while(counter<x and len(possibleDigits)>1):
         total = 0
         thisDigit = -1
         while(total<=n):
             total+=myFactorials[counter]
             thisDigit+=1
         myString += possibleDigits[thisDigit]
         n-=(total-myFactorials[counter])
         possibleDigits.pop(thisDigit)
         counter+=1

     myString+=possibleDigits[0]

     return myString

 start = time.time()
 print projectEulerProblemTwentyFour(1000000, 10)
 print ("--- %s seconds ---" % (time.time()-start))

 '''
 Prints

 2783915460
 --- 2.90870666504e-05 seconds ---

 for input of n = 1000000, x = 10
 '''

And with that, we’re done with this very annoying problem. It always takes me a bit of troubleshooting to solve this problem, so I’m glad I got it done.

Thanks for reading!

Project Euler Problem #23:

Problem #23 is similar to Problem #21 in that it concerns the sum of the proper divisors of numbers as well as abundant numbers. However, Problem #21 discussed amicable numbers which pair abundant numbers with non-abundant numbers while in this question, we are mostly concerned about abundant numbers. The question reads:

Project Euler Problem 23: Non-abundant sums
A perfect number is a number for which the sum of its proper divisors is exactly equal to the number. For example, the sum of the proper divisors of 28 would be 1 + 2 + 4 + 7 + 14 = 28, which means that 28 is a perfect number.

A number n is called deficient if the sum of its proper divisors is less than n and it is called abundant if this sum exceeds n.

As 12 is the smallest abundant number, 1 + 2 + 3 + 4 + 6 = 16, the smallest number that can be written as the sum of two abundant numbers is 24. By mathematical analysis, it can be shown that all integers greater than 28123 can be written as the sum of two abundant numbers. However, this upper limit cannot be reduced any further by analysis even though it is known that the greatest number that cannot be expressed as the sum of two abundant numbers is less than this limit.

Find the sum of all the positive integers which cannot be written as the sum of two abundant numbers.

If you read my solution to Problem #21, you can probably guess what I plan to do for this problem. Here’s my solution:

Solution #1: Sieve Approach

In this approach, we use a Sieve-like algorithm to find all of the abundant numbers less than 28123. Then we use another Sieve-like algorithm to find all numbers which are not the sum of two abundant numbers less than or equal to 28123. Here’s an implementation of this solution in Python 2.7:

 '''
 Author: Walker Kroubalkian
 Sieve Approach to Project Euler Problem #23
 '''

 import time

 def projectEulerProblemTwentyThree(n):
     sums = [0]*(n+1)
     for i in range(2,n/2+1):
         for c in range(2*i, n+1, i):
             sums[c]+=i

     abundant = []

     for i in range(n+1):
         if(sums[i]>i):
             abundant.append(i)

     possible = [True]*(n+1)
     possible[0] = False

     for a in abundant:
         for b in abundant:
             if(b>a or (a+b) > n):
                 break
             possible[a+b] = False

     total = 0

     for i in range(n+1):
         if(possible[i]):
             total+=i

     return total

 start = time.time()
 print projectEulerProblemTwentyThree(28123)
 print ("--- %s seconds ---" % (time.time()-start))

 '''
 Prints

 4179871
 --- 0.863159894943 seconds ---

 for input of n=28123
 '''

This solution is relatively inefficient as it takes almost a second to run even for small inputs such as 28123. I’d like to return to this question eventually to further optimize this solution.

Thanks for reading!

Project Euler Problem #22:

Problem #22 is yet another simple question that involves analyzing a large amount of data. The question reads:

Project Euler Problem 22: Names scores
Using names.txt (right click and 'Save Link/Target As...'), a 46K text file containing over five-thousand first names, begin by sorting it into alphabetical order. Then working out the alphabetical value for each name, multiply this value by its alphabetical position in the list to obtain a name score.

For example, when the list is sorted into alphabetical order, COLIN, which is worth 3 + 15 + 12 + 9 + 14 = 53, is the 938th name in the list. So, COLIN would obtain a score of 938 x 53 = 49714.

What is the total of all the name scores in the file?

We’ve seen just about every part of this problem in earlier problems (with the exception of calculating the position of a letter in the alphabet which can be done with ordinals). Here’s my solution:

Solution #1: Brute Force Approach

We begin by storing the names in a file that our script can read from. I stored the names in a file called “PE22Names.txt”. Then we store the names in an array, sort the array, and add up the value of every name in the list. Here is my solution:

 '''
 Author: Walker Kroubalkian
 Brute Force Approach to Project Euler Problem #22
 '''

 import time

 f = open("PE22Names.txt","r")

 if(f.mode == "r"):
     contents = f.readlines()
     realContents = []
     for x in contents:
         realContents.append(x.split(","))
 else:
     raise ValueError("Cannot read from file")

 finalList = realContents[0]

 def getValueString(myString):
     total = 0
     for x in myString:
         if("A"<=x<="Z"):
             total+=(ord(x)-ord("A")+1)
     return total

 def projectEulerProblemTwentyTwo(nameList):
     nameList = sorted(nameList)
     total = 0
     numberNames = len(nameList)
     for i in range(numberNames):
         total+=(i+1)*getValueString(nameList[i])
     return total

 start = time.time()
 print projectEulerProblemTwentyTwo(finalList)
 print ("--- %s seconds ---" % (time.time()-start))

 '''
 Prints

 871198282
 --- 0.00785303115845 seconds ---

 for input of given list of names
 '''

Unfortunately, this is yet another boring question near the start of Project Euler. Hopefully we get to some more interesting problems soon.

Thanks for reading!

Project Euler Problem #21:

Problem #21 features an obscure type of number known as an amicable number. The problem reads:

Project Euler Problem 21: Amicable numbers
Let d(n) be defined as the sum of proper divisors of n (numbers less than n which divide evenly into n).
If d(a) = b and d(b) = a, where a ≠ b, then a and b are an amicable pair and each of a and b are called amicable numbers.

For example, the proper divisors of 220 are 1, 2, 4, 5, 10, 11, 20, 22, 44, 55, and 110; therefore d(220) = 284. The proper divisors of 284 are 1, 2, 4, 71, and 142; so d(284) = 220.

Evaluate the sum of all the amicable numbers under 10000.

When I first attempted this problem, I instantly thought of using a sieve to find the sum of the proper divisors of every number less than 10,000. Unfortunately, this sieve is not perfect, as any amicable numbers with a sum greater than 10,000 will be missed. As a result, I used the following solution:

Solution #1: Semi-Sieve Approach

I used a sieve to find the sum of the proper divisors of every number less than 10,000 and stored each sum in an array. After this, I added any numbers which were guaranteed to be amicable based on my array to a running total. Any numbers with a sum greater than 10,000 would be added to an array of potential amicable numbers. Then I used a separate function made specifically to add the proper divisors of any given number to manually check each of the potential amicable numbers. Here is an implementation of this method in Python 2.7:

 '''
 Author: Walker Kroubalkian
 Semi-Sieve Approach to Project Euler Problem #21
 '''

 import time
 from math import sqrt

 def addProperDivisors(n):
     temp = n
     p = []
     e = []
     c = 2

     while(c<=sqrt(n)):
         if(n%c==0):
             t = 0
             while(n%c==0):
                 n/=c
                 t+=1
             p.append(c)
             e.append(t)
         c+=1

     if(n>1):
         p.append(n)
         e.append(1)

     total = 1
     powers = len(p)

     for x in range(powers):
         powerSum = 0
         for i in range(e[x]+1):
             powerSum+=(p[x]**i)
         total*=powerSum

     return total - temp

 def projectEulerProblemTwentyOne(n):
     sums = [0]*n
     i = 1

     while(i<=n/2):
         for c in range(2*i, n, i):
            sums[c]+=i
         i+=1

     total = 0
     potential = []

     for x in range(n):
         value = sums[x]
         if(value>=n):
             potential.append(x)
         elif(value>0 and value!=x and sums[value] == x):
             total+=x

     for x in potential:
         if(addProperDivisors(addProperDivisors(x)) == x):
             total+=x

     return total

 start = time.time()
 print projectEulerProblemTwentyOne(10000)
 print ("--- %s seconds ---" % (time.time()-start))

 '''
 Prints

 31626
 --- 0.0148520469666 seconds ---

 for input of n=10000
 '''

And with that, we’re done. The semi-sieve approach was efficient for a relatively small limit of 10,000. I may return to this problem to find a more efficient solution eventually.

Thanks for reading! See you tomorrow.

Project Euler Problem #20:

Problem #20 is quite surprisingly the first problem to feature the factorial. I’d explain it, but the question already does it for me. Here’s the question:

Project Euler Problem 20: Factorial digit sum
n! means n x (n-1) x . . . x 3 x 2 x 1

For example, 10! = 10 x 9 x . . . x 3 x 2 x 1 = 3628800,
and the sum of the digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27.

Find the sum of the digits in the number 100!

My solution for this problem is pretty boring. Here it is:

Solution #1: Brute Force Approach

We simply calculate 100! and add up its digits by converting it to a string and adding the integer associated with each character. I’m not sure if this saves time, but I divided the running product by 10 whenever it was a multiple of 10 to make the value smaller without changing the sum of the digits. Here’s an implementation of this method in Python 2.7:

 '''
 Author: Walker Kroubalkian
 Brute Force Approach to Project Euler Problem #20
 '''

 import time

 def projectEulerProblemTwenty(n):
     total = 1
     for i in range(2,n+1):
         if(total%10==0):
             total/=10
         total*=i
     a = str(total)
     final = 0
     for l in a:
         final+=int(l)
     return final

 start = time.time()
 print projectEulerProblemTwenty(100)
 print ("--- %s seconds ---" % (time.time()-start))

 '''
 Prints

 648
 --- 0.000115871429443 seconds ---

 for input of n=100
 '''

As shown above, Python is great even when dealing with massive numbers such as 100!. This problem is probably another good contender for simplest Project Euler problem looking back on it.

Thanks for reading. See you tomorrow!

Project Euler Problem #19:

Problem #19 is similar to Problem #17 in that it is very painful to implement unless you use an external library in whatever programming language you work in. The question reads:

You are given the following information, but you may prefer to do some research for yourself.

1 Jan 1900 was a Monday.

Thirty days has September,
April, June and November,
All the rest have thirty-one,
Saving February alone,
Which has twenty-eight, rain or shine,
And on leap years, twenty-nine.

A leap year occurs on any year evenly divisible by 4, but not on a century unless it is divisible by 400.

How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)?

Just by reading the facts this problem gives you, it should be clear that implementing it would be a pain in the neck. The amount of casework involving the different number of days in each month and the existence of leap years would be a massive pain to deal with. Luckily, Python has a library called datetime that is built specifically to ease this pain. Using the datetime library, we get the following solution:

Solution #1: Library Approach

The datetime library comes with two key object types for this sort of problem. The datetime object represents a specific instance in time whereas the timedelta object represents a span of time that can pass between any two datetimes. You could think of the datetime object as a definite point in space and the timedelta object as a vector. The datetime object has many attributes associated with it that make it perfect for this problem. It can recall the day of the week at any date in history, and it can return the day of the month. The timedelta object is useful for iterating through large spans of time which will be helpful for calculating the number of Sundays with this property.

My solution involved incrementing the start date until it was on a Sunday and then incrementing the start date a week at a time and counting the number of times it fell on the start of the month. I believed this would be simpler to implement than iterating through all of the months of each year and counting the number of Sundays. This resulted in the following implementation in Python 2.7:

 '''
 Author: Walker Kroubalkian
 Library Approach to Project Euler Problem #19
 '''

 import datetime
 import time

 def projectEulerProblemNineteen(m1, d1, y1, m2, d2, y2):
     start = datetime.datetime(y1, m1, d1)
     end = datetime.datetime(y2, m2, d2)
     day = datetime.timedelta(days = 1)
     week = datetime.timedelta(days = 7)

     while(start.weekday()!=6 and start<=end):
         start+=day

     total = 0
     while(start<=end):
         if start.day==1:
             total+=1
         start+=week

     return total

 start = time.time()
 print projectEulerProblemNineteen(1, 1, 1901, 12, 31, 2000)
 print ("--- %s seconds ---" % (time.time()-start))

 '''
 Prints

 171
 --- 0.000821828842163 seconds ---

 for input of start date = January 1, 1901, end date = December 31, 2000 
 '''

 

As shown above, Python libraries can save a lot of hassle when you need to solve a common problem that is difficult to implement.

Thanks for reading! See you tomorrow.

Project Euler Problem #18:

Problem #18 is one of the first problems on Project Euler to prominently feature DP (Dynamic Programming) and the first to explicitly be part of a series of similar problems. The problem reads:

Project Euler Problem 18: Maximum path sum I
By starting at the top of the triangle below and moving to adjacent numbers on the row below, the maximum total from top to bottom is 23.

   3
  7 4
 2 4 6
8 5 9 3

That is, 3 + 7 + 4 + 9 = 23.

Find the maximum total from top to bottom of the triangle below:

75
 95 64
 17 47 82
 18 35 87 10
 20 04 82 47 65
 19 01 23 75 03 34
 88 02 77 73 07 63 67
 99 65 04 28 06 16 70 92
 41 41 26 56 83 40 80 70 33
 41 48 72 33 47 32 37 16 94 29
 53 71 44 65 25 43 91 52 97 51 14
 70 11 33 28 77 73 17 78 39 68 17 57
 91 71 52 38 17 14 91 43 58 50 27 29 48
 63 66 04 68 89 53 67 30 73 16 69 87 40 31
 04 62 98 27 23 09 70 98 73 93 38 53 60 04 23

NOTE: As there are only 16384 routs, it is possible to solve this problem by trying every route. However, Problem 67 is the same challenge with a triangle containing one-hundred rows; it cannot be solved by brute force, and requires a clever method! ;o)

This time, the problem explicitly encourages us not to check every route and instead to search for an efficient solution. Luckily, we can find a more efficient solution with the use of DP:

Solution #1: Dynamic Approach

The boring part of this problem is figuring out how to store the triangle. I decided to use a jagged 2D array to store the numbers in each row, and I filled the array by reading from a text file which I labeled “PE18Triangle.txt”. I have worked with programming languages such as TI-nspire Basic which do not support jagged arrays as they interpret 2D arrays as matrices. Unfortunately, my method of storing the triangle probably will not work in those languages. Once we’ve stored the triangle, it’s time for the fun part:

Instead of proceeding from the top of the triangle to the bottom, we instead go from the bottom up, recording the maximum path to the bottom that enters each number in the triangle. This is quite difficult to explain with words, so here is my implementation of this method in Python 2.7:

 '''
 Author: Walker Kroubalkian
 Dynamic Approach to Project Euler Problem #18
 '''

 import time

 f = open("PE18Triangle.txt","r")

 if(f.mode == "r"):
     contents = f.readlines()
     realContents = []
     for x in contents:
         realContents.append(map(int,x.split()))
 else:
     raise ValueError("Cannot read from file")

 def projectEulerProblemEighteen(triangle):
     rows = len(triangle)
     currentRow = triangle[rows-1]
     c = (rows-2)
     while(c>=0):
         temp = []
         for i in range(c+1):
             temp.append(triangle[c][i] + max(currentRow[i],currentRow[i+1]))
         currentRow = temp
         c-=1
     return currentRow[0]

 start = time.time()
 print projectEulerProblemEighteen(realContents)
 print ("--- %s seconds ---" % (time.time()-start))

 '''
 Prints
 1074
 --- 5.19752502441e-05 seconds ---
 for input of triangle = given triangle
 '''

And with that, we’re done. This problem was one of my first experiences with DP, and as a result, it remains one of my favorite computer programming questions to this day.

Thanks for reading! See you tomorrow.

Design a site like this with WordPress.com
Get started