Grid Paths and Minimum Path Sum
28 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Some problems ask for a count or a best value over a grid. Many routes share the same cells, so a table that stores one answer per cell does the work once instead of again and again.
Count the routes with a table
Dynamic programming is a method that stores the answer to each smaller version of a problem in a table, so every answer is computed once and reused. It needs a state and a rule that builds a state from smaller ones.
A robot walks on a grid and may only step right or down. How many routes lead from the top-left cell to the bottom-right cell? Any cell is entered from above or from the left, so its count is the sum of those two counts.
def count_routes(rows, cols): ways = [[1] * cols for _ in range(rows)] for r in range(1, rows): for c in range(1, cols): ways[r][c] = ways[r - 1][c] + ways[r][c - 1] return ways[-1][-1]
print(count_routes(3, 3))Example 1 · Brute force: try every route
def routes(down, right): if down == 0 or right == 0: return 1 return routes(down - 1, right) + routes(down, right - 1)
print(routes(2, 2))6
What this code does
- routes(2, 2) means two steps down and two steps right. It splits into two smaller calls until one direction is used up.
- The same cell is solved again and again, so the time is about 2 to the power (down + right).
Example 2 · Remember answers with a cache
from functools import cache
@cachedef routes(down, right): if down == 0 or right == 0: return 1 return routes(down - 1, right) + routes(down, right - 1)
print(routes(16, 16))601080390
What this code does
@cachestores the result of each call, so a cell is solved once.- Only about 17 x 17 calls run. It is the same idea as the table, but top down.
Example 3 · The table version prints its rows
rows, cols = 3, 4ways = [[1] * cols for _ in range(rows)]for r in range(1, rows): for c in range(1, cols): ways[r][c] = ways[r - 1][c] + ways[r][c - 1]for row in ways: print(row)[1, 1, 1, 1] [1, 2, 3, 4] [1, 3, 6, 10]
What this code does
- Each inner cell is the sum of the cell above and the cell to its left.
- The bottom-right value, 10, is the number of routes on a 3 by 4 grid.
The first row and first column have exactly one route each. Every other cell is filled from two finished neighbours. The cost is O(rows x cols) time and space.
Build the grid the safe way
A grid in Python is a list of rows. Writing [[0] * cols] * rows looks right, but it repeats one row object many times. Change one cell and every row changes. A comprehension makes a fresh row each time.
bad = [[0] * 3] * 2good = [[0] * 3 for _ in range(2)]bad[0][1] = 5good[0][1] = 5print(bad)print(good)Example 4 · Copying a grid needs a copy of every row
grid = [[1, 2], [3, 4]]shallow = grid[:]deep = [row[:] for row in grid]grid[0][0] = 99print(shallow[0][0], deep[0][0])99 1
What this code does
grid[:]copies only the outer list. Both lists still share the same row objects.[row[:] for row in grid]copies every row, so later changes do not leak.
[0] * 3 is safe because integers cannot change. The trap is repeating a list that holds a list: the outer * copies the reference, not the row.
Minimum path sum
Each cell has a cost. Find the cheapest route from top-left to bottom-right, moving right or down. The best cost to reach a cell is its own cost plus the smaller of the best costs from above and from the left.
def min_path_sum(cost): rows, cols = len(cost), len(cost[0]) best = [row[:] for row in cost] for r in range(rows): for c in range(cols): if r or c: up = best[r - 1][c] if r else float("inf") left = best[r][c - 1] if c else float("inf") best[r][c] += min(up, left) return best[-1][-1]Example 5 · Minimum path sum and the path itself
cost = [[1, 3, 1], [1, 5, 1], [4, 2, 1]]rows, cols = 3, 3best = [row[:] for row in cost]for r in range(rows): for c in range(cols): if r or c: up = best[r - 1][c] if r else float("inf") left = best[r][c - 1] if c else float("inf") best[r][c] += min(up, left)print(best)r, c = rows - 1, cols - 1path = [(r, c)]while r or c: if r == 0: c -= 1 elif c == 0: r -= 1 elif best[r - 1][c] < best[r][c - 1]: r -= 1 else: c -= 1 path.append((r, c))print(path[::-1])[[1, 4, 5], [2, 7, 6], [6, 8, 7]] [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)]
What this code does
- The table holds the cheapest cost to reach every cell. The corner shows 7.
- To find the route, start at the corner and step back to the smaller of the two neighbours. Reverse the list to read it forwards.
row[:] copies each row, so the input grid stays unchanged. Missing neighbours on the edges count as infinity, so min ignores them.
Blocked cells and keeping one row
Row r needs only row r - 1 and itself. One list of length cols can hold the whole table and be updated in place. A blocked cell gets zero routes.
def routes_with_blocks(grid): cols = len(grid[0]) ways = [0] * cols ways[0] = 1 for row in grid: for c in range(cols): if row[c] == 1: ways[c] = 0 elif c > 0: ways[c] += ways[c - 1] return ways[-1]Example 6 · Routes with blocked cells in one row
def routes_with_blocks(grid): cols = len(grid[0]) ways = [0] * cols ways[0] = 1 for row in grid: for c in range(cols): if row[c] == 1: ways[c] = 0 elif c > 0: ways[c] += ways[c - 1] return ways[-1]
grid = [[0, 0, 0], [0, 1, 0], [0, 0, 0]]print(routes_with_blocks(grid))2
What this code does
- The centre cell is blocked, so only the route along the top and right edges and the route along the left and bottom edges remain.
- A blocked cell sets its count to zero, which also removes everything that depends on it.
Example 7 · Python integers never overflow
def count_routes(rows, cols): ways = [[1] * cols for _ in range(rows)] for r in range(1, rows): for c in range(1, cols): ways[r][c] = ways[r - 1][c] + ways[r][c - 1] return ways[-1][-1]
print(count_routes(50, 50))25477612258980856902730428600
What this code does
- The answer has 29 digits. A fixed-size integer in many languages would overflow, but a Python
intgrows as needed. - You only take a remainder when the problem statement asks for one.
Before an update, ways[c] still holds the cell above and ways[c - 1] already holds the cell to the left. Space falls from O(rows x cols) to O(cols).
Next step
Check your understanding
11 quick questions