DP on Grids

Find the cheapest route and count the routes across a grid that only moves right or down, by filling a 2D table row by row. Then shrink it to one row.

16 minLesson 3 of 6
On this page
  1. The five steps
  2. The code
  3. Counting routes, with one row of memory
  4. Common mistakes
  5. Check yourself
  6. What to practise next

A delivery robot crosses a warehouse floor laid out as a grid. It starts in the top-left corner and must reach the loading bay in the bottom-right. The aisles are one-way, so it can only move right or down. Each cell shows how many seconds it takes to cross, because some aisles are busier than others.

5 3 1 6 2 9 1 5 6 2 2 9 start end fastest: 21 s greedy: 26 s
Each number is the time to cross that cell. Taking the cheapest next cell each time (dashed) costs 26 seconds. The best route (solid) costs 21.

A robot that always steps into the cheaper next cell goes down to the 2 (cheaper than the 3), down again to the 6 (cheaper than the 9), and along the bottom: 5 + 2 + 6 + 2 + 2 + 9 = 26 seconds. The best route goes the other way and takes 21. With 3 rows and 4 columns there are only 10 routes, so you could try them all. On a 20 by 20 floor there are more than 35 billion. You need a table.

The five steps

This is the recipe from Introduction to Dynamic Programming with two indexes instead of one. The trick is the same as in 1D Dynamic Programming: ask about the very last move.

  1. State. dp[r][c] is the fastest time to get from the start to cell (r, c), counting both the start cell and this one.
  2. Recurrence. The robot entered (r, c) either from above or from the left. Whichever it was, it should have got there as fast as possible. So dp[r][c] = min(dp[r − 1][c], dp[r][c − 1]) + cost[r][c].
  3. Base cases. dp[0][0] = cost[0][0]. Cells in the top row can only be entered from the left, and cells in the left column only from above, so each of those has a single option.
  4. Order. Each cell reads the cell above and the cell to its left. Go row by row, left to right inside each row, and both are always ready.
  5. Answer. dp[rows − 1][cols − 1], the bottom-right cell.
Fill the fastest-time tableUse the arrows or ← → keys
for r from 0 to rows - 1:  for c from 0 to cols - 1:    if r == 0 and c == 0: dp[r][c] = cost[0][0]    else if r == 0: dp[r][c] = dp[r][c-1] + cost[r][c]    else if c == 0: dp[r][c] = dp[r-1][c] + cost[r][c]    else: dp[r][c] = min(dp[r-1][c], dp[r][c-1]) + cost[r][c]return dp[rows-1][cols-1]
  1. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 05
    row 1
    row 2
    cost
    5

    1Base case. The robot starts here, so the time to reach the top-left cell is just its own cost: dp[0][0] = 5.

  2. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 058
    row 1
    row 2
    cost
    3
    left
    5

    2Top row: nothing is above, so the only way in is from the left. 5 + 3 = 8.

  3. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 0589
    row 1
    row 2
    cost
    1
    left
    8

    3Top row: nothing is above, so the only way in is from the left. 8 + 1 = 9.

  4. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 058915
    row 1
    row 2
    cost
    6
    left
    9

    4Top row: nothing is above, so the only way in is from the left. 9 + 6 = 15.

  5. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 058915
    row 17
    row 2
    cost
    2
    up
    5

    5Left column: the only way in is from above. 5 + 2 = 7.

  6. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 058915
    row 1716
    row 2
    cost
    9
    up
    8
    left
    7

    6Arrive from above (8) or from the left (7). The faster is from the left, so 7 + 9 = 16.

  7. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 058915
    row 171610
    row 2
    cost
    1
    up
    9
    left
    16

    7Arrive from above (9) or from the left (16). The faster is from above, so 9 + 1 = 10.

  8. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 058915
    row 17161015
    row 2
    cost
    5
    up
    15
    left
    10

    8Arrive from above (15) or from the left (10). The faster is from the left, so 10 + 5 = 15.

  9. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 058915
    row 17161015
    row 213
    cost
    6
    up
    7

    9Left column: the only way in is from above. 7 + 6 = 13.

  10. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 058915
    row 17161015
    row 21315
    cost
    2
    up
    16
    left
    13

    10Arrive from above (16) or from the left (13). The faster is from the left, so 13 + 2 = 15.

  11. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 058915
    row 17161015
    row 2131512
    cost
    2
    up
    10
    left
    15

    11Arrive from above (10) or from the left (15). The faster is from above, so 10 + 2 = 12.

  12. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 058915
    row 17161015
    row 213151221
    cost
    9
    up
    15
    left
    12

    12Arrive from above (15) or from the left (12). The faster is from the left, so 12 + 9 = 21.

  13. dp (fastest time to reach each cell)
    col 0col 1col 2col 3
    row 058915
    row 17161015
    row 213151221
    answer
    21

    13The fastest route takes 21 seconds. Following the smaller neighbour back from the corner gives the green cells: 5, 3, 1, 1, 2, 9. Twelve cells, each filled once with at most one comparison.

1 / 13

The table tells you the best time, not the route. To get the route, start at the bottom-right and repeatedly step to whichever of "above" or "left" holds the smaller number, as the last step of the tracer does. Reverse the cells you visited and you have the path.

The code

Fastest route, full table
function fastestRoute(cost) {
  const rows = cost.length;
  const cols = cost[0].length;
  const dp = Array.from({ length: rows }, () => new Array(cols).fill(0));
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      if (r === 0 && c === 0) dp[r][c] = cost[0][0];
      else if (r === 0) dp[r][c] = dp[r][c - 1] + cost[r][c];
      else if (c === 0) dp[r][c] = dp[r - 1][c] + cost[r][c];
      else dp[r][c] = Math.min(dp[r - 1][c], dp[r][c - 1]) + cost[r][c];
    }
  }
  return dp[rows - 1][cols - 1];
}
 
fastestRoute([[5, 3, 1, 6], [2, 9, 1, 5], [6, 2, 2, 9]]); // 21
def fastest_route(cost):
    rows, cols = len(cost), len(cost[0])
    dp = [[0] * cols for _ in range(rows)]
    for r in range(rows):
        for c in range(cols):
            if r == 0 and c == 0:
                dp[r][c] = cost[0][0]
            elif r == 0:
                dp[r][c] = dp[r][c - 1] + cost[r][c]
            elif c == 0:
                dp[r][c] = dp[r - 1][c] + cost[r][c]
            else:
                dp[r][c] = min(dp[r - 1][c], dp[r][c - 1]) + cost[r][c]
    return dp[rows - 1][cols - 1]
 
fastest_route([[5, 3, 1, 6], [2, 9, 1, 5], [6, 2, 2, 9]])  # 21
ApproachTimeSpaceWhy
Try every routeExponentialO(R + C)Each route is R + C − 2 moves, each one right or down, so there are up to about 2R+C routes: 10 on this floor, over 35 billion on a 20 × 20 one.
Full tableO(R × C)O(R × C)One cell per grid square, one comparison per cell.
One rowO(R × C)O(C)Each cell only reads the row above, so keep one row and overwrite it.

Counting routes, with one row of memory

Now some cells hold shelves and cannot be entered at all, and the question changes: how many different routes reach the bay? The five steps barely move.

  • State: ways[r][c] is the number of routes from the start to (r, c).
  • Recurrence: every route into a cell arrives from above or from the left, never both, so ways[r][c] = ways[r − 1][c] + ways[r][c − 1]. A shelf cell is 0.
  • Base case: ways[0][0] = 1, the route of standing still. Off-grid neighbours count as 0.
  • Order and answer are as before.

Here is a 3 by 4 floor with one shelf in the middle of row 1, marked ■. Each number is the count of routes to that cell:

col 0col 1col 2col 3
row 01111
row 11■ 012
row 21124

Without the shelf there would be 10 routes. The shelf removes 6 of them, leaving 4.

Each row only reads the row above it and itself. So keep a single array ways of length cols. When you reach column c in a new row, ways[c] still holds the value from the row above, and ways[c − 1] already holds the value for the current row. Adding them is exactly the recurrence.

Counting routes in O(cols) memory
// grid[r][c] is 1 for a shelf, 0 for open floor.
function countRoutes(grid) {
  const cols = grid[0].length;
  const ways = new Array(cols).fill(0);
  ways[0] = 1;
  for (let r = 0; r < grid.length; r++) {
    for (let c = 0; c < cols; c++) {
      if (grid[r][c] === 1) ways[c] = 0; // no route ends on a shelf
      else if (c > 0) ways[c] += ways[c - 1]; // above + left
    }
  }
  return ways[cols - 1];
}
 
countRoutes([[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 0]]); // 4
# grid[r][c] is 1 for a shelf, 0 for open floor.
def count_routes(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  # no route ends on a shelf
            elif c > 0:
                ways[c] += ways[c - 1]  # above + left
    return ways[cols - 1]
 
count_routes([[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 0]])  # 4

Common mistakes

Check yourself

Quick check

In the fastest-time table, dp[1][1] has 8 above it and 7 to its left, and the cell costs 9. What is dp[1][1]?

An open 3 by 3 grid with no shelves. How many right-or-down routes go from the top-left to the bottom-right?

In the one-row version of countRoutes, just before ways[c] += ways[c - 1] runs, what does ways[c] hold?

What to practise next

Grids are not always floors. In Knapsack the rows are items and the columns are amounts of space left, and in DP on Strings the rows and columns are letters of two words. The fill pattern you just used, above and to the left, carries straight over. To practise this lesson, try Snowplow to the Road for the cheapest route and Roadworks on the Grid for counting routes around blocked cells.