Sunday, November 1, 2015

Problem 14 : Longest Collatz Sequence

Longest Collatz sequence

Problem 14

The following iterative sequence is defined for the set of positive integers:
n → n/2 (n is even)
n → 3n + 1 (n is odd)
Using the rule above and starting with 13, we generate the following sequence:
13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms. Although it has not been proved yet (Collatz Problem), it is thought that all starting numbers finish at 1.
Which starting number, under one million, produces the longest chain?
NOTE: Once the chain starts the terms are allowed to go above one million.
                                                                                                       (https://projecteuler.net/problem=14)
Problem 14 is about Collatz sequence.

 f(n) = \begin{cases} n/2 &\text{if } n \equiv 0 \pmod{2}\\ 3n+1 & \text{if } n\equiv 1 \pmod{2} .\end{cases}

Assuming the Collatz conjecture that this process will eventually reach the number 1, regardless of which positive integer is chosen initially, we will find the longest chain under one million.

This problem can be solved using something similar to the Sieve of Eratosthenes.

Basically, the computer is calculating and memorizing the information (storing the information into an array) and reusing it later to reduce the time, as there are going to be lots of repetitions and redundancies in calculation.

The first step is simple.

If you take any number under half of one million, or five hundred thousand, that number will not be the longest chain.

Why does that happen? Let's say you choose 40 and 80.

40 has 9 terms in its sequence. 80, which is double of 40, will go through extra n / 2 step in the beginning, and therefore will have 10 terms in its sequence.

Aha! So if any number within the range of 1 to 500,000 will have shorter chain length than its doubled value, as long as they are in the range also.

So here is our limit:

private static final int LIMIT = 1000000;

...

for (int i = LIMIT/2 + 1; i <= LIMIT; i++)
{...}

And keep in mind that we don't need to find the length of the longest chain, we just need the starting number that has the longest chain.

That's the reason why I'm not going to keep track of the actual length; I will just record the relative length of each chain.

We are going to make an array that will store the information.

private static int[] collatz = new int[LIMIT+1];

Now, let's look at the method that will store the information.

// fill the collatz array : memorization
public static void fillCollatz(int n)
{...}

We initialize the variables that will keep track of the chain length and the current value of calculation.

int count = 0;
int current = n;

If the number is even number, then we will divide it by 2.

if (current%2 == 0) current /= 2;

Once it is divided by 2, it will be smaller than our starting value.

If the current value is smaller than the starting value, then that means somewhere in the previous steps (in our for-loop) we already calculated the chain length for that current value as a starting value.

And here is the advantage of memorization; we just skip the entire step and save tons of time.

collatz[n] = collatz[current] + count;

Here is an example. Say we have 40 and 80, like we did before.

Let's say the computer already calculated the chain length for 40 already (9 terms) and stored in the array.

  step: value

     0: 40
     1: 20
     2: 10
     3: 5
     4: 16
     5: 8
     6: 4
     7: 2
     8: 1

And the computer has to calculate the chain length for 80 now.

The computer will divide 80 by 2, and get 40, and current chain length will be 1.

And it will recognize that it already calculated the chain length for 40 in the past.

So it will recall the number 9 from its array and add it to the current chain length.

  step: value

     0: 80
     1: 40 (from here, the calculation is identical to the former one.)
     2: 20 (we don't want to waste our time on calculating exactly same stuffs.)
     3: 10
     4: 5
     5: 16
     6: 8
     7: 4
     8: 2
     9: 1

Now, let's deal with the numbers that are not even numbers.

Because those odd numbers will be multiplied by 3, it will get really large number that int will not be able to hold.

So we need to set the limit as one-third of integer max_value, and use long instead:

private static final int MAX = Integer.MAX_VALUE/3;

// fill the collatz array : memorization
public static void fillCollatz(int n)
{

   ...

   else if (current >= MAX)
   {
       long cur = current;
       ...
   }

   ...

}

And notice that odd * 3 + 1 always become an even number, and we can reduce the step by immediately calculating (odd*3 + 1) / 2 and increasing the number of chain length by 2.

Math: new number = (odd*3 + 1) / 2 = odd + (odd + 1) / 2

if (cur%2 == 0) cur /= 2;
else
{
 cur += (cur + 1)/2;
 count++;
}

At the end, we will store the information by adding new calculation and previous memory.

collatz[n] = collatz[current] + count;

Integrated solution:

// fill the collatz array : memorization
public static void fillCollatz(int n)
{
    int count = 0;
    int current = n;
    for ( ; current >= n; count++)
    {
        if (current%2 == 0) current /= 2;
        else if (current >= MAX)
        {
            long cur = current;
            for ( ; cur >= MAX; count++)
            {
                if (cur%2 == 0) cur /= 2;
                else
                {
                    cur += (cur + 1)/2;
                    count++;
                }
            }
            current = (int)cur;
            count--;
        }
        else
        {
            current += (current + 1)/2;
            count++;
        }
    }
    collatz[n] = collatz[current] + count;
}

And our main body will iterate through the numbers until it reaches one million:

public static void run()
{
    int num = 0, maxLength = 0;
  
    collatz[1] = 1;
    for (int i = LIMIT/2 + 1; i <= LIMIT; i++)
    {
        fillCollatz(i);
        if (collatz[i] >= maxLength)
        {
            maxLength = collatz[i];
            num = i;
        }
    }
    System.out.println(num);
}


Answer is 837799
Execution time is 38.619629 ms
Source Code: https://github.com/Ainodyne/Project-Euler/blob/master/Problem014.java

Picture by: https://www.jasondavies.com/collatz-graph/

Wednesday, October 28, 2015

Problem 29 : Distinct Powers

Distinct powers

Problem 29

Consider all integer combinations of ab for 2 ≤ a ≤ 5 and 2 ≤ b ≤ 5:
22=4, 23=8, 24=16, 25=32
32=9, 33=27, 34=81, 35=243
42=16, 43=64, 44=256, 45=1024
52=25, 53=125, 54=625, 55=3125
If they are then placed in numerical order, with any repeats removed, we get the following sequence of 15 distinct terms:
4, 8, 9, 16, 25, 27, 32, 64, 81, 125, 243, 256, 625, 1024, 3125
How many distinct terms are in the sequence generated by ab for 2 ≤ a ≤ 100 and 2 ≤ b ≤ 100?
                                                                                                       (https://projecteuler.net/problem=29)
You know what. If you struggled with this problem, here is a cheat that you can follow.

It's not actual cheating, but there is no complicated algorithms used in this problem like my other solutions in this blog. I'll tell you one thing, and you're all-set.

In Java, there is a type of list called HashSet which only stores distinct values.

For example, I will add 1, 2, 2, 2, 4, 7, and 7 into an ArrayList.

index 0 1 2 3 4 5 6
data 1 2 2 2 4 7 7

Now, I will add 1, 2, 2247, and 7 into an HashSet.

index ? ? ? ?
data 1 2 4 7

We cannot directly know in what order the HashSet stores its data, yet we find that all the duplicate values were automatically eliminated, or not stored from the beginning.

And this is why it fulfills our goal: how many distinct terms are in the sequence.

Set<BigInteger> list1 = new HashSet<BigInteger>();
  
for (int a = 2; a <= 100; a++)
{
    for (int b = 2; b <= 100; b++)
    {
     list1.add(BigInteger.valueOf(a).pow(b));
    }
}
System.out.println(list1.size());

We will get 38.04139449846139 ms, or 0.038 seconds. Decent efficiency.

And I will tell you now that I lied. There is actually one more cheat that we can use here.

When we reach some numbers extremely large like ab where 2 ≤ a, b ≤ 100, they are large enough that each number becomes extremely unique numbers.

So do we need precision? Does it matter that 123456789 is different from 123456788?

... Not really. It's just large number that is about 1.23 x 108. That's all we need.

Therefore, I will simply use double instead of BigInteger.

I guarantee that double will provide enough precision for our calculation for this problem.

Set<Double> list = new HashSet<Double>();
  
for (int a = 2; a <= 100; a++)
{
    for (int b = 2; b <= 100; b++)
    {
        list.add(Math.pow(a, b));
    }
}
System.out.println(list.size());

Yay! 8.5 millisecond!

Answer is 9183
Execution time is 8.553895 ms
Source Code: https://github.com/Ainodyne/Project-Euler/blob/master/Problem029.java


Monday, October 12, 2015

Problem 23 : Non-Abundant Sums

Non-abundant sums

Problem 23

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.
                                                                                                       (https://projecteuler.net/problem=23)

A few days ago, we had a discussion about amicable numbers.

And I mentioned about 6, which was "amicable" to itself, and now it appears again.

As the problem explains, s(6) = 1 + 2 + 3 = 6, which means it is "amicable" number to itself, or we mathematically call it a perfect number.

Numbers like 12, where s(12) = 1 + 2 + 3 + 4 + 6 = 16 > 12, is called an abundant number.

 s(n)=sigma(n)-n>n,

Personally this problem was one of some tedious problems in Project Euler.

I couldn't come up with an efficient algorithm (even though I met the Project Euler standard efficiency: 1 minute), so I ended up using sieve, reducing redundancies, and half-cutting the limit in order to meet my personal premise in problem-solving (under 1 second).

Here is my algorithm.

The problem hints us that the limit is 28123, and that the smallest abundant number is 12, and therefore the sum of two abundant numbers is 24.

From now on, I will just call such integers that is the sum of two abundant numbers abby numbers.
(for convenience)

(This term is not a defined mathematical term, so don't use it anywhere else.)

We are going to find the sum of non-abby numbers.

Here is my algorithm for abundant number sieve:

public static boolean[] sieveAbundance(int limit)
{
    boolean[] abun = new boolean[limit + 1];
    for (int i = 12; i <= limit; i++)
    {
        if (!abun[i])
        {
            if (abun[i] = isAbundant(i))
            {
                for (int j = i; j <= limit; j += i)
                    abun[j] = true;
            }
        }
    }
    return abun;
}

Overall similar principle to Sieve of Eratosthenes (problem 10), yet I have one thing to point out.

Once the number is proven to be an abundant number, then its multiples are all abundant numbers.

The proof is simple. If the sum of the proper divisors of a certain number is already greater than itself, then doubling the number will produce twice as much proper divisors for the number (and the sum of the values will definitely exceeds the original number), and the number will clearly be an abundant number.

Next, we need to determine if the number is an abundant number.

public static boolean isAbundant(int n)
{
    int original = n;
    int sum = 1;
 
    if (n % 2 == 0)
    {
        int temp = 1;
        for (int mult = 2; n % 2 == 0; n /= 2, mult *= 2)
            temp += mult;
        sum *= temp;
    }
    for (int i = 3, end = (int)Math.sqrt(n); i <= end; i += 2)
    {
        if (n % i == 0)
        {
            int temp = 1;
            for (int mult = i; n % i == 0; n /= i, mult *= i)
                temp += mult;
            sum *= temp;
            end = (int)Math.sqrt(n);
        }
    }
    if (n != 1) sum *= 1 + n;
    return original + original < sum;
}

In this algorithm, I divided the division part into [2] and [odd numbers].
(By checking the divisibility by 2, I can skip rest of even numbers.)

And the logic behind the algorithm is written here:

Let's say our number is 36. Its proper divisors are 1, 2, 3, 4, 6, 9, 12, 18.

The sum is 1 + 2 + 3 + 4 + 6 + 9 + 12 + 18 = 55 > 36. It is an abundant number.

Prime factorization: 36 = 22 x 32.

With 22, we can produce 1, 2, and 4. Let's add them up. 1 + 2 + 4 = 7.

Now, we have 32. We can produce 1, 3, and 9. Let's add them up. 1 + 3 + 9 = 13.

Let's multiply those two sums. 7 * 13 = 91.

91 minus the original number 36 equals 91 - 36 = 55. The sum of the proper divisors.

What?! How could that happen??

Ok, here is the proof.

36 20 (A) 21 (B) 22 (C)
30 (D) 1 2 4
31 (E) 3 6 12
32 (F) 9 18 36

where each un-bolded numbers in the table is the multiplication of two elements.

For example, 18 in the box is the product of 21 x 32, or column x row.

Instead of adding all the element manually, we are going to use multiplication of polynomials in order to quickly get the sum of the factors. (including itself)

We want to get AD + AE + AF + BD + BE + BF + CD + CE + CF, which we can factor out into (A + B + C)(D + E + F). And this is (1 + 2 + 4)(1 + 3 + 9) = 91, which I did above.

Because this is the sum of every factor, we need to subtract the original number in order to get the sum of the proper factors.

Last step is to find abby number from the information on abundant numbers we calculated.

Because it tends to have abundant numbers in bigger numbers than small numbers, the algorithm will start finding the abundant pairs from the biggest to smallest. For example, if we want to check if 72 is abby number, we will divide it into half and start with (36, 36) pair, which is more likely than (1, 71) pair.

public static boolean isSumOfTwoAbundant(int n)
{
    for (int i = n / 2; i >= 12; i--)
        if (isAbundant[i] && isAbundant[n - i]) return true;
    return false;
}

and WE ARE FINALLY DONE!

int sum = 0;
for (int i = 1; i <= LIMIT; i++)
{
    if (!isSumOfTwoAbundant(i)) sum += i;
}
System.out.println(sum);


Answer is 4179871
Execution time is 28.948803 ms
Source Code: https://github.com/Ainodyne/Project-Euler/blob/master/Problem023.java


Wednesday, September 30, 2015

Problem 22 : Names Scores

Names scores

Problem 22

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 × 53 = 49714.
What is the total of all the name scores in the file?
                                                                                                       (https://projecteuler.net/problem=22)

Simple problem, simple algorithm, so let's directly get into the solution.

We will read in that 46K files, because I don't want to manually store the five thousand Strings into a giant array... I mean... really....

public static String[] read()
{
    String[] text = new String[5000];
    File file = new File(text022);
    try (BufferedReader br = new BufferedReader(new FileReader(file)))
    {
        String line = br.readLine();
        line = line.replace("\"", "");
        text = line.split(" ");
    }
    catch (IOException e)
    {
        System.err.println(e);
    }
    return text;
}

After that, we sort the array, as the problem stated, in alphabetical order.

I used java.util.Arrays to use the function sort() to put them in order.

If we want to create our own solution for sorting, (and if so, I have to talk about either one of Quicksort, Mergesort, or heapsort, which are considered the fastest sorting algorithms out there), yet most programmers will use the built-in functions to quickly solve the problem.

import java.util.Arrays;

...

public class Problem022 implements textFile.Texts
{
    private static String[] NAME = read();
 
    public static void run()
    {
        Arrays.sort(NAME);
  
        ...
    }
 
}

For each name, we will get the value for each char in the String.

Here, I will use the trick I used in the problem 20, where I used the intrinsic value of the primitive data types stored in Java:

for (int i=0; i < NAME.length; i++)
{
    sum = 0;
    char[] arr = NAME[i].toCharArray();
    for (char c : arr) sum += c - 'A' + 1;
    score += sum * (i+1);
}

The logic behind this algorithm is simple.

The character 'A' will have some value in computer. And character 'B' will also have some value in computer, but no matter what value that is, its value will be 1 bigger than the value of 'A' because the computer stores the values in chronological order.

So value('B') - value('A') = 1.

Because the problem counts the characters from 1 starting with 'A', we will simply add +1 at the end.

(Look at the code above.)

Combined solution:

Arrays.sort(NAME);
  
int score = 0, sum = 0;
  
for (int i=0; i < NAME.length; i++)
{
    sum = 0;
    char[] arr = NAME[i].toCharArray();
    for (char c : arr) sum += c - 'A' + 1;
    score += sum * (i+1);
}
System.out.println(score);


Answer is 871198282
Execution time is 27.967796 ms
Source Code: https://github.com/Ainodyne/Project-Euler/blob/master/Problem022.java


Thursday, September 10, 2015

Problem 21 : Amicable Numbers

Amicable numbers

Problem 21

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.
                                                                                                       (https://projecteuler.net/problem=21)
Kudos to the one who found out amicable numbers!

Studying amicable numbers allows you to explore the deeper side of the nature of numbers, and you will be amazed at their intrinsic properties involving factors.

If we denote that for an amicable pair (m, n):

s(m)=n

s(n)=m,

where

                  s(n)=sigma(n)-n

where  sigma(n) is the divisor function.

And therefore we can derive the fact that:

                 sigma(m)=sigma(n)=s(m)+s(n)=m+n,

(Learn more about amicable numbers.)

As the problem stated, the most well-known amicable pair is (220, 284), as they are the first amicable numbers and the only numbers under 1000.

s(220)=sum{1,2,4,5,10,11,20,22,44,55,110}

=284

s(284)=sum{1,2,4,71,142}

=220.

Let's get into the problem-solving.

The most important function will be finding the sum of the proper divisors.

public static int sumFactor(int n) {...}

As always, we need the base case.

if (n == 0) return 0;

Let's initialize the sum as 1, for the sum of the proper divisors will always be at least 1 (prime numbers will have the sum of the proper divisors of 1):

int sum = 1;

As discussed in the problem 3, our limit for the division will be the square root of the number:

int sqrt = (int) Math.sqrt(n);

Starting with 2, we will check the divisibility and add BOTH the divisor and paired divisors (for example, if 36 is divisible by 3, then we add 3 and its paired divisor 12, or 36 divided by 3):

for (int i = 2; i <= sqrt; i++)
{
    if (n%i == 0) sum += i + n/i;
}

Last step is to check if the number is a perfect square. So if the number is 25, then in the previous step we added 5 twice as the divisor is 5 and its paired divisor is also 5. We need to get rid of this redundancy):

if (n == sqrt*sqrt) sum -= sqrt;

Combined algorithm:

public static int sumFactor(int n)
{
    if (n == 0) return 0;
    int sum = 1;
    int sqrt = (int) Math.sqrt(n);
    for (int i = 2; i <= sqrt; i++)
    {
        if (n%i == 0) sum += i + n/i;
    }
    if (n == sqrt*sqrt) sum -= sqrt;
    return sum;
}


Now, we want to know if the number is amicable number.

The one thing I want to point out is that an amicable pair cannot contain a duplicate number.

For example, 6 has proper divisors of 1,2, and 3, so s(6) = 1 + 2 + 3  = 6.

6 is an "amicable number" of itself, which is against the definition of amicable number.

So we need the condition that a != s(a)

public static boolean isAmicable(int a)
{
    int b = sumFactor(a);
    return (a != b) && (a == sumFactor(b));
}

We are all set! Let's iterate through the integers until we reach 10,000:

int sum = 0;
for (int i = 1; i < 10000; i++)
    if (isAmicable(i)) sum += i;
System.out.println(sum);


Answer is 31626
Execution time is 8.740898 ms
Source Code: https://github.com/Ainodyne/Project-Euler/blob/master/Problem021.java


Sunday, August 30, 2015

Problem 20 : Factorial Digit Sum

Factorial digit sum

Problem 20

n! means n × (n − 1) × ... × 3 × 2 × 1
For example, 10! = 10 × 9 × ... × 3 × 2 × 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!
                                                                                                       (https://projecteuler.net/problem=20)

This one looks like a factorial problem, and it is, but more importantly, it is BigInteger again!

Factorial is really fun mathematical operator, as it will make a growth sharper than exponential function after a certain point.

So if you graph the function into a calculator, you will see that:



where red graph is y = ex exponential graph,
and purple graph is y = x! factorial graph.

If we zoom in closer for 0 < x:



They meet each other at (5.29, 198.406), and after that point, factorial has much greater growth rate.

So, that being said, we definitely need BigInteger to get the value.


Starting with 1, up to 100, we use for-loop to multiply BigInteger values to get factorial 100!:

int ans = 0;
BigInteger num = BigInteger.ONE;
for (int i=1; i <= 100; i++) num = num.multiply(BigInteger.valueOf(i));

Just to let you know, there are default constants of BigInteger.ONE, BigInteger.ZERO, and BigInteger.TEN for convenience; there is multiply() function that you can use for multiplication; and you initialize the value using valueOf() function.

After that, we use the great advantage of Java BigInteger: the fact that it uses String to store the info.

So I will convert the String into char array, and use the char values to get each digit and their sum:

char[] arr = num.toString().toCharArray();
for (char c : arr) ans += c - '0';

System.out.println(ans);

And we will have the answer.

Answer is 648
Execution time is 2.634032 ms
Source Code: https://github.com/Ainodyne/Project-Euler/blob/master/Problem020.java

Graph Calculated from: https://www.desmos.com/calculator


Wednesday, July 15, 2015

Problem 19 : Counting Sundays

Counting Sundays

Problem 19

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)?
                                                                                                       (https://projecteuler.net/problem=19)
Now we are on the problem with calendar counting.

I have always hoped that our current calendar becomes simpler and more uniform (without any confusing leap years and varying month lengths, ... etc), but, whatever.

At least it makes calendar counting problems more fun and exciting, so I'm ok with it now.

We are dating back to January 1st, 1901, when 20th century began.

Here are some important events in 1901.

Ok. Let's start writing our algorithm.

First method that I want is to determine if the year is leap year or not.

Because leap years are divisible by 4, but centuries don't count, but 400-multiple centuries do count, it seems tedious, and we need several steps.

public static boolean isLeap(int y)
{...}

So first filter is whether the year is multiple of 4; if it is, then that year can be leap year possibly; if not, then no chance.

if (y%4 == 0)
{
    ...
}
return false;

Second, if the year is divisible by 4, then we check if the year is multiple of 400; if it is, then it is a leap year; if not, then we'll see.

if (y%4 == 0)
{
    if (y%400 == 0) return true;
    else ...
}

Third, if the year isn't multiple of 400, then we check if the year is multiple of 100; if it is. then it is not a leap year; if not, it is.

if (y%4 == 0)
{
    if (y%400 == 0) return true;
    else if (y%100 == 0) return false;
    return true;
}

When we combine them:

public static boolean isLeap(int y)
{
    if (y%4 == 0)
    {
        if (y%400 == 0) return true;
        else if (y%100 == 0) return false;
        return true;
    }
    return false;
}

After making that method, I worked on how to easily find out Sundays without using complicated algorithms involving arrays or Strings.

So here is the system that I designed, and it worked perfectly:

S M T W T F S
0 1 2 3 4 5 6

Using divisibility by 7, we can get the reminders from 0-6.

if the day count divided by 7 gives us 1, then we know that it is Monday.

For example, starting with Monday, we want to know what day it is after 24 days.

Because Monday is 1 in the chart, and 1 + 24 = 25, we divide it by 7, and we will get 4 as a reminder.

And 4 is Thursday, according to the chart I made. Ah! It is Thursday after 24 days.

My algorithm is the Modulus Representation of Days (Modulo 7).

From now on, it becomes really easy implementation.

We start with 0 Sunday count, and day 2 (which is Tuesday, according to the chart I made):

int count = 0, day = 2;

Year from 1901 to 2000 and month from 1 to 12:

for (int y = 1901; y <= 2000; y++) // years
{
    for (int m = 1; m <= 12; m++) // months
    {
        ...
    }
    ...
}

If months are April, June, September, and November, there are 30 days always:

if (m == 4 || m == 6 || m == 9 || m == 11)
    day += 30;

If February, then we check leap-year-ity of the year:

else if (m == 2)
{
    if (isLeap(y)) day += 29;
    else day += 28;
}

Rest of the months are 31 days:

else day += 31;

After that, we divide by 7 and get the reminder. If the reminder is 0 on the first day of each month, we know that it is Sunday:

day %= 7;
if (day == 0) count++; // if it is sunday

Combining all that:

int count = 0, day = 2; // modulus representation of days (modulo 7) : SMTWTFS == 0123456
  
for (int y = 1901; y <= 2000; y++) // years
{
    for (int m = 1; m <= 12; m++) // months
    {
        if (m == 4 || m == 6 || m == 9 || m == 11)
            day += 30;
        else if (m == 2)
        {
            if (isLeap(y)) day += 29;
            else day += 28;
        }
        else day += 31;
  
        day %= 7;
        if (day == 0) count++; // if it is sunday
    }
}
System.out.println(count);


Answer is 171
Execution time is .796564 ms
Source Code: https://github.com/Ainodyne/Project-Euler/blob/master/Problem019.java


Wednesday, July 1, 2015

Problem 18 : Maximum Path Sum I

Maximum path sum I

Problem 18

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
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 routes, 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)
                                                                                                       (https://projecteuler.net/problem=18)

Problem 18 is awesome. It's simply amazing.

Personally, I found out dynamic programming while solving this problem, and it is one of the most powerful algorithms that I've ever learned.

As my brute force method took more than 5 minutes on the first attempt, the drastic improvement of efficiency down to 3 milli-second was astonishing advancement and was done solely by dynamic programming.

(Note that the solution will be same for Problem 67).

Anytime there is a large set of data, we are going to use read-file method to store the data.

private static int[][] triangle = read();

public static int[][] read()
{
    int[][] text = new int[15][];
    File file = new File(text018);
    try (BufferedReader br = new BufferedReader(new FileReader(file)))
    {
        String line;
        for (int i=0; (line = br.readLine()) != null; i++)
        {
            String[] temp = line.split(" ");
            text[i] = new int[i+1];
            for (int j=0; j <= i; j++)
                text[i][j] = Integer.parseInt(temp[j]);
        }
    }
    catch (IOException e)
    {
        System.err.println(e);
    }
    return text;
}

Instead of starting from the top and coming downward, I will start from the bottom and go upward.

Let's look at the bottom-left region, where a triplet of 63, 04, and 62 are located.

   63
04  62

I will call 63 the parent number and 04 and 62 the daughter numbers.

   63
04  62

We are going to modify our pyramid by adding the larger daughter number to the parent number.

   63
04  62

62 is greater than 04, so I will add the number to the top.

  125
04  62

If we do the same thing for the second pair, then:

   66
62  98

  164
62  98

We are going to do the same thing for all the pairs in the first floor, and then we will have the second floor with the greatest possible values of sums.

After finishing the second floor, then we will step up and select two pairs from the second floor and form the greatest possible values of sums in the third floor, and so on.

And because this is repetitive process, we can tell the program just once how to process the numbers, and after that computer will just follow through the logic until it reaches the last floor, or the top number, which will then become the largest possible sum of the adjacent numbers.

We call this dynamic programming.

Here is the implementation of the logic:

for (int i=triangle.length-1; i >= 1; i--)
{
    for (int j=0; j < i; j++)
    {
        triangle[i-1][j] += Math.max(triangle[i][j], triangle[i][j+1]);
    }
}

System.out.println(triangle[0][0]);


Answer is 1074
Execution time is 3.042578 ms
Source Code: https://github.com/Ainodyne/Project-Euler/blob/master/Problem018.java


Thursday, April 16, 2015

Problem 17 :Number Letter Counts

Number letter counts

Problem 17

If the numbers 1 to 5 are written out in words: one, two, three, four, five, then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total.
If all the numbers from 1 to 1000 (one thousand) inclusive were written out in words, how many letters would be used?

NOTE: Do not count spaces or hyphens. For example, 342 (three hundred and forty-two) contains 23 letters and 115 (one hundred and fifteen) contains 20 letters. The use of "and" when writing out numbers is in compliance with British usage.
                                                                                                       (https://projecteuler.net/problem=17)

Personally, Problem 17 was really fun.

It doesn't involve numerical analysis specifically, yet it requires a clever algorithm to solve.

English has a lot of irregular expressions, and number is one. (11 is not ten-one, it is eleven.)

Having many Chinese friends, I found that Chinese has really formalized number expression which doesn't have any exceptions. In China, people count 12 as ten-two, and 21 as two-ten-one.

Anyway, because English has its own unique way of expressing numbers into its language, we will need several steps in our algorithms to solve this problem.

We need three main sets of English words:

private static final String[] UNIT = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine"};
private static final String[] TENTH = {"twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"};
private static final String[] TEN = {"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"};

And three main keywords:

private static final String THOUSAND = "thousand";
private static final String HUNDRED = "hundred";
private static final String AND = "and";

My algorithm was starting with unit digit numbers and gradually increasing the level to two-digit and three-digit numbers.

First, get the sum of unit digit numbers from "one" to "nine":

// 1 to 9
int unitSum = 0;
for (String n : UNIT) unitSum += n.length();

Once we get this sum, we will reuse it A LOT as "one" to "nine" appears every ten times in counting.

Second, get the sum of the numbers from "one" to "ninety nine" (without space):

// 1 to 99
int twoDigit = unitSum*9;
for (String n : TEN) twoDigit += n.length();
for (String n : TENTH) twoDigit += n.length()*10;

Notice that unitSum was added 9 times, TENTH was added once, and TEN was added 10 times.

("one" to "nine" appears in 1~9, skips 11~19, appears in 21~29, 31~39, ... 91~99)
(For the skipped 11~19, we add each element of TENTH)
(From 21~99, we only added unit digits so far, so we will add each element of TEN 10 times. For example, "twenty" appears 10 times from 20 ~ 29)

Third, we will get the sum from "one" to "Nine Hundred And Ninety Nine":

// 1 to 999
int threeDigit = 0;

threeTemp is 1~99 right now.

int threeTemp = twoDigit;

Then we are going to add "Hundred and" to each number from 1~99.

So it's going to be "Hundred and one," "Hundred and two," "Hundred and ninety nine,"...

without the beginning numbers. (So it's not a complete number like "One Hundred and One.")

threeTemp += (HUNDRED.length() + AND.length())*99;
threeTemp += HUNDRED.length();

Lastly, we are going to add those beginning numbers to complete the 3-digit numbers.

for (String u : UNIT)
   threeDigit += u.length()*100 + threeTemp;

So by now, our variable threeDigit has all the letter countings from 100~999.

The only thing we need to do is to add 1~99 counting to threeDigit complete 1~999 counting.

threeDigit += twoDigit; // 1 to 99 (without any hundredth digit)

Because the question asked for 1~1000, we are going to add the "One Thousand" to the sum.

// 1000
int fourDigit = UNIT[0].length() + THOUSAND.length(); // one thousand

System.out.println(fourDigit + threeDigit);

Answer is 21124
Execution time is 2.075885 ms
Source Code: https://github.com/Ainodyne/Project-Euler/blob/master/Problem017.java