Problem 82 — Path Sum: Three Ways

solved Fri, 18 Jul 2025, 15:23 · level 3 · difficulty 10% projecteuler.net github

NOTE: This problem is a more challenging version of Problem 81.

The minimal path sum in the $5$ by $5$ matrix below, by starting in any cell in the left column and finishing in any cell in the right column, and only moving up, down, and right, is indicated in red and bold; the sum is equal to $994$.

$$ \begin{pmatrix} 131 & 673 & \color{var(--st-red)}{234} & \color{var(--st-red)}{103} & \color{var(--st-red)}{18}\\ \color{var(--st-red)}{201} & \color{var(--st-red)}{96} & \color{var(--st-red)}{342} & 965 & 150\\ 630 & 803 & 746 & 422 & 111\\ 537 & 699 & 497 & 121 & 956\\ 805 & 732 & 524 & 37 & 331 \end{pmatrix} $$

Find the minimal path sum from the left column to the right column in matrix.txt (right click and "Save Link/Target As..."), a 31K text file containing an $80$ by $80$ matrix.


Problem Analysis

We must find the cheapest path through an 80x80 grid of integers, beginning at any cell in the leftmost column and ending at any cell in the rightmost column, where each move steps right, up, or down (but never left). This generalises Problem 81, where movement was restricted to right and down only. The freedom to move both up and down within a column changes the structure of the problem: while in a column we may detour vertically by any number of cells before stepping right, paying for every cell we traverse on the way. That vertical freedom defeats a naive two-direction sweep, because the optimal route into a given cell may arrive from far above or far below it.

The right tool here is dynamic programming (DP) - a method that solves a problem by breaking it into overlapping sub-problems, solving each once, and reusing the stored results. The sub-problem I define is: "given a cell, what is the minimum total cost to reach the right edge starting from that cell?" The observation that makes the problem tractable is that a vertical detour within a single column is just a range sum over consecutive cells of that column. This converts the 2-D, three-direction path problem into a sequence of independent 1-D minimisations, one per column. Processing columns from right to left, each column-reduction step costs O(n^2) (for every destination row, scan every target row with the range sum folded in), and there are n columns, giving an overall time complexity of O(n^3). For n=80 that is roughly 512,000 operations - trivially fast.

Solution Approach

Column-Reduction Dynamic Programming (Solution 0)

Both the C and Python files implement the same algorithm, which I call "column reduction." The matrix begins holding raw cell values. After I process column c, every entry in column c holds the minimum total cost to travel from that cell rightward to any cell on the right edge. I sweep columns from right to left, so by the time I reach a column, the column immediately to its right is already fully solved.

The core routine is reduce_column(col), which reads the already-solved column col and writes results into column col - 1. For each destination row row in the left column, I consider every possible target row target in the right column. To travel from (row, col-1) to (target, col), I must traverse vertically through every cell between row and target within column col-1, then step right. The cost is therefore the range sum of the cells in col-1 from min(row, target) to max(row, target) inclusive, plus the already-stored best cost at (target, col). I take the minimum of that cost over all target rows and store it as the new value at (row, col-1).

A subtlety worth noting: the range sum always includes the cell (row, col-1) itself, so the starting cell is counted exactly once, and a self-target (row == target) correctly contributes just that single cell. Once all columns have been reduced down to column 0, the answer is simply the minimum value over column 0, because each entry there already encodes the full optimal cost from that starting cell to the right edge.

The C version stores the grid in a static matrix[MAX_N][MAX_N] and recomputes the range sum with an inner loop over the cell indices, accumulating into range_sum. The Python version expresses the same logic with nested generator expressions inside min() and sum(). Both compute every new column value into a temporary buffer (new_entries in both) and only then write them back into the matrix - this avoids corrupting values still needed within the same pass. The two implementations are algorithmically identical; they differ only in language idiom and runtime.

workbench

Test cases

categoryinputanswer
dev file_url = 994
main file_url = https://projecteuler.net/resources/documents/0082_matrix.txt 260324

Results

solutioncategoryanswerverdict avg seconds cavg seconds py
p0082_s0 dev 994 correct 0.000001 (63) 0.000057 (63)
p0082_s0 main 260324 correct 0.007403 (63) 0.618654 (63)

Files

Tags & blogs