Skip to content
dsa450for five year olds

Dynamic Programming · 27 of 60

Min Cost Path Problem

Cross a grid of toll squares from the top left to the bottom right paying as few coins as possible.

Simple way
time O(3^(m+n))
room O(m + n)
Clever way
time O(m * n)
room O(m * n)

The problem, simply

You get a grid cost with m rows and n columns. Each square has a price. You start on the top left square and must reach the bottom right square. From any square you may step right, down, or diagonally down-right.

You pay for every square you stand on, including the first and the last. Give back the smallest total you can pay.

A story to remember it

Picture it

gridcoins per square
123482153

Each square asks for that many coins. Start at the top left, end at the bottom right, moving right, down or diagonally down-right. The cheapest trip is 1 + 2 + 2 + 3 = 8.

  • answer

Examples

A normal case

In
[[1,2,3],[4,8,2],[1,5,3]]
Out
8

(0,0), right to (0,1), diagonal to (1,2), down to (2,2). 1 + 2 + 2 + 3 = 8.

The diagonal helps

In
[[1,3,1],[1,5,1],[4,2,1]]
Out
5

1, down to 1, diagonal to 2, right to 1. Without the diagonal move (LeetCode 64) the answer would be 7.

One row

In
[[1,3,1]]
Out
5

Only right moves are possible, so you pay for every square.

One square

In
[[5]]
Out
5

You start on the end square and pay for it once.

Way 1: the simple way

Ask the question backwards. The cheapest way to reach square (i, j) is its own price plus the cheapest way to reach one of the three squares that lead into it.

  1. best(0, 0) = cost[0][0].
  2. Squares outside the grid cost "infinity", so they are never chosen.
  3. best(i, j) = cost[i][j] + min(best(i-1, j), best(i, j-1), best(i-1, j-1)).
tree
2,21,20,20,10,01,10,11,00,00,00,12,11,12,01,01,01,1

Asking backwards from the end: square (2,2) needs the cheapest way to (1,2), (2,1) and (1,1), and so on. Gray circles are squares already asked about on another branch. The plain recursion does all of their work again: the full tree has 31 calls for only 9 squares.

  • seen
java
class Solution {
    public int minCost(int[][] cost) {
        return best(cost, cost.length - 1, cost[0].length - 1);
    }

    private int best(int[][] cost, int i, int j) {
        if (i < 0 || j < 0) return Integer.MAX_VALUE;      // off the grid, never pick it
        if (i == 0 && j == 0) return cost[0][0];           // the start square
        int up = best(cost, i - 1, j);
        int left = best(cost, i, j - 1);
        int diag = best(cost, i - 1, j - 1);
        return cost[i][j] + Math.min(up, Math.min(left, diag));
    }
}

How long it takes: three branches per call and up to m + n levels, O(3^(m+n)). Extra room: the waiting calls, O(m + n). The MAX_VALUE never gets added to, because every square except the start has at least one neighbor inside the grid.

Way 2: the clever way

Fill a table dp the same size as the grid, row by row, left to right. When you reach a square, the three squares it needs are already filled.

  1. dp[0][0] = cost[0][0].
  2. Top row: only from the left. Left column: only from above.
  3. Every other square: dp[i][j] = cost[i][j] + min(up, left, up-left).
  4. The answer is dp[m-1][n-1].

In the animation, the left grid shows the prices and the right grid is the table. Arrows point from the squares each answer reads. The brighter arrow source is the cheapest one.

Fill the cheapest cost square by square

01 / 10

gridcoins per square
123482153
gridcheapest cost to reach
1

The start square costs 1. Nothing comes before it.

  • looking now
  • checking
  • seen
  • answer
java
class Solution {
    public int minCost(int[][] cost) {
        int m = cost.length, n = cost[0].length;
        int[][] dp = new int[m][n];
        dp[0][0] = cost[0][0];
        for (int j = 1; j < n; j++) dp[0][j] = dp[0][j - 1] + cost[0][j];   // top row: from the left
        for (int i = 1; i < m; i++) dp[i][0] = dp[i - 1][0] + cost[i][0];   // left column: from above
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                int cheapest = Math.min(dp[i - 1][j], Math.min(dp[i][j - 1], dp[i - 1][j - 1]));
                dp[i][j] = cost[i][j] + cheapest;
            }
        }
        return dp[m - 1][n - 1];
    }
}

How long it takes: each square once, three looks each, O(m * n). Extra room: the table, O(m * n). You can keep only one row, or write the answers into the cost grid itself if you are allowed to change it, for O(n) or O(1).

Watch out for

  • Do not forget the price of the start square. The answer includes it.
  • Read the moves carefully. LeetCode 64 allows only right and down. This version also allows down-right.
  • Greedy (always step to the cheapest next square) fails. A cheap step can lead into an expensive corner.

What interviewers ask next

  • Print the path. Walk back from the end, each time to the neighbor with the smallest dp value.
  • What if you may also move up and left? Then squares can depend on each other in circles, so a table filled in one order does not work. Use Dijkstra's shortest path with a heap.
  • Count how many cheapest paths there are. Keep a second table of counts and add the counts of every neighbor that ties for the minimum.
  • What if some squares are walls? Treat them as infinity in the table.

Remember this

Grown-up words: the stones are cells of a matrix, the number written on each stone is the DP state, the three neighbors are the transitions, and the repeated questions in the recursion are overlapping subproblems.