Problem 11 — Largest Product in a Grid

solved Tue, 8 Oct 2019, 15:26 · level 1 · difficulty 5% projecteuler.net github

In the $20 \times 20$ grid below, four numbers along a diagonal line have been marked in red.

08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08
49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00
81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65
52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91
22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80
24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50
32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70
67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21
24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72
21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95
78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92
16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57
86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58
19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40
04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66
88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69
04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36
20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16
20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54
01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48

The product of these numbers is $26 \times 63 \times 78 \times 14 = 1788696$.

What is the greatest product of four adjacent numbers in the same direction (up, down, left, right, or diagonally) in the $20 \times 20$ grid?


Problem Analysis

This problem has essentially no number theory in it - it is a search problem over a small, fixed two-dimensional array. The task is to find the largest product of four consecutive values along any of the eight compass directions in a 20x20 grid. The first key observation is that of those eight directions, only four are genuinely distinct: a run going left is the same set of numbers as the matching run going right, just multiplied in the opposite order, and multiplication is commutative. So I only need to examine horizontal, vertical, and the two diagonal orientations. This immediately halves the work.

The second observation is what keeps the code clean: every one of those four orientations can be anchored at a single cell and walked in a single forward direction. If I pick an anchor cell and restrict it to positions where a run of num_adjacent cells fits both rightward and downward, then all four runs stay inside the grid. This turns the whole problem into a brute-force search: enumerate every valid anchor, compute four products, take the maximum. For a grid of side N and a run length k there are (N - k + 1)^2 valid anchors, and at each anchor I do O(k) multiplications, giving an overall complexity of O((N - k + 1)^2 * k), which simplifies to roughly O(N^2 * k). For N = 20 and k = 4 that is only a few thousand operations - trivially fast, so there is no incentive to look for anything cleverer.

Solution Approach

Single-Pass Anchor Scan (Python and C)

Both files implement the identical algorithm; only the language differs. I iterate over every valid anchor cell (x, y), where "valid" means x and y each range from 0 to dim - num_adjacent inclusive. That bound is the crucial correctness detail: it guarantees that a run of length num_adjacent never walks off the right or bottom edge in any of the four directions.

For each anchor I maintain four running products, all initialised to 1, and update all of them inside a single loop over the offset i from 0 to num_adjacent - 1:

  • Horizontal: grid[x][y + i] - fixed row, advancing column.
  • Vertical: grid[x + i][y] - fixed column, advancing row.
  • Main diagonal: grid[x + i][y + i] - top-left to bottom-right.
  • Anti-diagonal: grid[x + num_adjacent - 1 - i][y + i] - starts at the bottom row of the run and walks upward as the column advances, covering the bottom-left to top-right direction.

The anti-diagonal needs the starting row offset of num_adjacent - 1, which is still in bounds precisely because x itself was capped at dim - num_adjacent. After the inner loop finishes, I compare all four products against the running best and keep the largest. The C version does this with explicit if (... > best) comparisons and four long long accumulators; the Python version expresses the same logic as a generator expression fed to the built-in max().

The most interesting language-specific difference is how Python handles the four accumulators inside a generator expression. Because a generator expression cannot contain assignment statements, I "reset" the four products by iterating once over a single-element tuple for horizontal, vertical, diagonal_1, diagonal_2 in ((1, 1, 1, 1),), and then re-bind those same names on each step of i by iterating over another single-element tuple holding the updated products. The C version simply declares four local variables and multiplies them in place - the more natural form in an imperative language.

workbench

Test cases

categoryinputanswer
main num_adjacent = 4 70600674

Results

solutioncategoryanswerverdict avg seconds cavg seconds py
p0011_s0 main 70600674 correct 0.000002 (84) 0.000263 (84)

Files

Tags & blogs