Project Euler 15: Routes through a 20×20 grid

本文介绍了解决Project Euler中关于寻找20x20网格中从左上角到右下角路径数量的问题。提供了两种解决方案:一种受到动态规划启发的方法,另一种则是基于组合数学的解析解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

The problem description in Problem 15 of Project Eulercontains a figure, which I wont copy, so go ahead an read the full description at theProject 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 anoptimal 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

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
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

\displaystyle \binom{2N}{N} = \binom{n}{k}

And using the multiplicative formula we can express it as

\displaystyle \binom{n}{k} = \frac{n(n-1)(n-2)\hdots(n-k 1)}{k(k-1)(k-2)\hdots1} = \prod_{i=1}^k \frac{n-k i}{i}

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

1
2
3
4
5
6
7
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

1
2
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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值