Project Euler 15: Routes through a 20×20 grid

Written by on 15 December 2010

Topics: Project Euler

The problem description in Problem 15 of Project Euler contains a figure, which I wont copy, so go ahead an read the full description at the Project Euler site. The problem can be understood without it though. The problem reads

Starting in the top left corner of a 2×2 grid, there are 6 routes (without backtracking) to the bottom right corner.

How many routes are there through a 20×20 grid?

My first question for many of the problems has been – Can it be brute forced? And my best answer to that is “probably”, but I cannot figure out how to generate all the routes. So instead I will give you two other approaches, which are both efficient. One is inspired by dynamic programming and the other gives an analytic solution using combinatorics.

Dynamic Programming

Dynamic programming is an optimization technique where you can minimize or maximize some function. Dynamic programming relies on an important property; the problem must have an optimal substructure . What it means is that you need to be generate a solution based on solving smaller problems. One example, if you want to find the shortest route , you can use dynamic programming, if you can find the shortest route of a part of the problem independent of how you got to the start point of the shorter distance you are trying to find.

Confused? Don’t worry too much, we will cover it all again in a later problem and you will hopefully get the hang of what I mean through this problem.  The problem we are currently trying to solve is not about optimization, but rather counting. However, the solution is still heavily inspired by the techniques in dynamic programming.

Example Grid 1 step from GoalInstead of solving the whole problem, lets solve the problem for the scenario where we stand in the point just to the left or above the end point as marked in the figure to the right, no matter how we got to that point, we only have only one possible path to follow to reach the goal.

Now that we have solved one subproblem, we can expand that by solving the problem where we stand two grid points to the left. That point gives us  one option to continue, no matter how we got to that grid point. From there we only have one option to get to the goal. Using that argument we can solve the problem for all bottom and  all the rightmost points in the grid.

So far it has been rather trivial with only one path to follow, but it gives a nice boundary condition for the algorithm as we shall see later on. But let us move inside the grid. If we stand in the green grid point on the figure below, we can move right – from where we already know that we have 1 option to complete the path, or we can move down where we also have 1 option to complete the path. So that gives us a total of 2 different paths to take from the green point.

Example Grid - Larger subproblemNow that we know we can make 2 paths from the green point, we can figure out how many paths we can make from the blue grid point.  If we move down, we have 1 path, and if we move right we have 2 options to complete the path. So in total we can choose between 3 paths to get from the blue grid point to the end.

Iteratively we can solve larger and larger sub problems once we have solve a smaller sub problem, since we can calculate the number of paths by adding the number of paths from the point below with the number of paths from the point to the right. Under the assumptions that we know the number of paths in these points.  The deducted boundary conditions ensures us that we can always find such a point, and by going through the whole grid, we can calculate routes all the way until we reach the top most point.

If we continue these calculations until we have filled the 2×2 grid as I have done in the figure to the left, we end up with 6 paths, just as the example in the problem formulation.

That should be rather easy to make an algorithm that does this for us. There is only one little note I want to make, even if it is a 20×20 grid, we have 21×21 grid points, so the algorithm looks like

const int gridSize = 20;
long[,] grid = new long[gridSize+1, gridSize+1];

//Initialise the grid with boundary conditions
for (int i = 0; i < gridSize; i++) {
    grid[i, gridSize] = 1;
    grid[gridSize,i] = 1;
}
for (int i = gridSize - 1; i >= 0; i--) {
    for (int j = gridSize - 1; j >= 0; j--) {
        grid[i, j] = grid[i+1, j] + grid[i, j+1];
    }
}

And the execution gives us

In a 20x20 grid there are 137846528820 possible paths.
Solution took 0 ms

It scales squared with side length of the grid, so it scales really well, however the number of paths explodes with large grids, so we will rather quickly face problems with using longs for storage. However that is another problem which can be circumvented using BigInteger as described in the answer to Problem 13.

Combinatorics

The kind of problem we solve here is treated in this book Notes on Introductory Combinatorics by George Polya et al. I haven’t read it but it has been recommended to me by a friend, when I said I wanted to know a bit more about combinatorics.

In order to pose the question as a combinatorics question, we must realise a few things. I have generalised the observations to an NxN grid.

  1. All paths can be described as a series of directions. And since we can only go down and right, we could describe the paths as a series of Ds and Rs. In a 2×2 grid all paths are 1) DDRR 2) DRDR 3) DRRD 4) RDRD 5) RDDR 6) RRDD.
  2. Based on the example we can see that all paths have exactly size 2N of which there are N Rs and N Ds.
  3. If we have 2N empty spaces and place all Rs, then the placement of the Ds are given

Once we have made these realisations, we can repose the question as

In how many ways can we choose N out of 2N possible places if the order does not matter?

And combinatorics gives us an easy answer to that. The Binomial Coefficient gives us exactly the tool we need to answer the above question. The question is usually posed as

And using the multiplicative formula we can express it as

We could also express it as a factorial expression, but that usually gives problems with very large numbers when we try to make the calculations.

Wikipedia has a suggested implementation for the multiplicative formula that I have used to get the result so the code looks like

const int gridSize = 20;
long paths = 1;

for (int i = 0; i < gridSize; i++) {
    paths *= (2 * gridSize) - i;
    paths /= i + 1;
}

and the result is

In a 20x20 grid there are 137846528820 possible paths.
Solution took 0 ms

This solution has a smaller implementation and will be significantly smaller in memory usage than the first approach. However both works really fast.

Wrapping up

I gave you two different possible solutions which aren’t really related at first view. However, Samual Jack over at Functional Fun, more or less connects the two approaches. From the grid, he deducts pascal’s triangle, and from there it is fairly easy to take it the last step to binomial coefficient and the multiplicative formula.

As usually the source code is available.

17 Comments For This Post I'd Love to Hear Yours!

  1. Ricardo says:

    I’v been doing these problems in order, and this one definitely stands out as the toughest. It’s very hard to wrap your head around how to find the paths. I’m still not quite sure how this works, but I will keep reviewing your solution until I do.

    Thanks again for a great blog!

  2. Kristian says:

    Thanks again. I think both of these solutions are pretty good, but they require some fundamental insights into dynamic programming and combinatorics. But don’t be shy to ask questions if there are things that aren’t explained well enough.

  3. am says:

    Hi! I am not able to get my head around this part!

    //Initialise the grid with boundary conditions
    for (int i = 0; i < gridSize; i++) {
    grid[i, gridSize] = 1;
    grid[gridSize,i] = 1;
    }

    I could identify that the problem needed dynamic programming but failed to recognize this init step. Can you please explain what that for loop means? Does it mean there is atleast one path from the destination to all other points in the lattice?

  4. Kristian says:

    It means that then you are on the lower or the right boundary, there is exactly one path to the goal. You can only continue along the border you are on.

  5. jyoti says:

    this is the best explanation , i agree this is the most difficult to understand if you start sequentially . i solved this recursively – https://gist.github.com/dekajp/5227749

  6. Andy says:

    I managed to solve this problem instantly and without the need for any programming. This little pattern helped me: https://en.wikipedia.org/wiki/Pascal's_triangle#Overall_patterns_and_properties

    heh, kind of cheating, but you cant argue with the efficiency ;)

  7. Kristian says:

    Nope, not cheating. It is called thinking, and that prvides the best solution.

  8. Chiraag says:

    Is it possible to solve this using binary trees?

  9. Kristian says:

    I honestly have no idea :)

  10. vikas says:

    When considering the possible paths (tracing them out with your finger), you might whisper “Up, right, up, right…”.

    Why not write those thoughts down? Using “u” and “r” we can write out a path:

    r r r r r r r u u u u

  11. Kristian says:

    Well I was writing it down but with D and R instead?

  12. Andrew says:

    Kristian,
    I was wondering how to scale this. If I was wanting to do a 27X27 grid how would that change the code?
    Thanks,
    Andrew

  13. Kristian says:

    All you needed to change is the gridsize constant. The last method wont change very much. You might run into problems with integer overflow, I am a bit uncertain of that.
    The first method will get a bit longer run time, but not much.

  14. Frank says:

    The brute force solution of this problem is by using recursion.
    My code (in Python):

    def find_route(n,route):
    global nroute
    (x,y) = route[len(route)-1]
    p1 = (x+1,y)
    p2 = (x,y+1)
    route1 = route + [p1]
    route2 = route + [p2]
    if (p1 == (n,n)):
    nroute = nroute + 1
    elif (p2 == (n,n)):
    nroute = nroute + 1
    else:
    if (x+1 <= n): find_route(n,route1)
    if (y+1 <= n): find_route(n,route2)

    def problem_15(n):
    global nroute
    bt = time.time()
    route = [(0,0)]
    nroute = 0
    find_route(n,route)
    print "In a "+str(n)+"*"+str(n)+" grid there are "+str(nroute)+" paths."
    et = time.time()
    print "Solutions took "+str(et-bt)+" sec."

    On my computer it works up to around n = 12, thereafter it takes far to long.
    Thank you for your very interesting blog!

  15. Conan says:

    What is the point of the “gridSize+1″ under “initializing”? You start counting from 0. Also, you never seem to go over the value of gridSize in the loops. Thanks for the awesome help! Keep it up!

  16. Kristian says:

    That is because a 2×2 grid has 3 nodes in each dimension. That is why I do it this way. I then initialise the grid in the “gridSize” point, and since I am starting to count from 0, this is the 21st point in the array.

  17. vasa says:

    Hi Kristian, first of all, i love your posts about the project euler problems as these provide an alternative thinking to the approaches i choose to use myself, secondly wonder if you have any tips on how to reach the effecient solutions that you have reached in many of your posts.

    Btw here is my “brute force” solution which is to slow to solve the problem within any reasonable amount of time(you simply set the arguments to 0)

    
    private static int gridSize = 5;
    
    public static int searchGrid(int positionX, int positionY){
      int currentMaxed = 0;
    
      if(positionY &gt;= gridSize &amp;&amp; positionX &gt;= gridSize){
    	return 1;
      }
    
      if(positionX &lt; gridSize){
        currentMaxed += searchGrid(positionX+1, positionY);
      }
    
      if(positionY &lt; gridSize){
        currentMaxed += searchGrid(positionX, positionY+1);
      }
    
      return currentMaxed;
    }
    

Trackbacks For This Post

  1. Solving Project Euler with Functional C# – Problem 15 | Eldie Turner

Leave a Comment Here's Your Chance to Be Heard!

You can use these html tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

You can use short tags like: [code language="language"][/code]

The code tag supports the following languages: as3, bash, coldfusion, c, cpp, csharp, css, delphi, diff,erlang, groovy, javascript, java, javafx, perl, php, plain, powershell, python, ruby, scala, sql, tex, vb, xml

You can use [latex]your formula[/latex] if you want to format something using latex

Notify me of comments via e-mail. You can also subscribe without commenting.