Back to Trees & BST
Q8Question 8 of 25

Minimum Path Sum: cheapest path through a weighted grid

Key Concepts
  • dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]) for interior cells.
  • First row and first column are prefix sums (only one direction to arrive).
  • Greedy fails because locally cheap hops can lead to expensive overall routes.
  • Space reduces to O(n) with rolling row or O(1) by overwriting the input.
  • Works only with non-negative costs and restricted (right/down) movement.
  • Reconstructing the path requires backtracking through the DP table.

Definition

dp[i][j] equals the cell's own cost plus the minimum of the cost from above and the cost from the left.
  • Grid DP – dp[i][j] stores the minimum total cost to reach cell (i, j) from the top-left.
  • Transition – dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]) for interior cells.
  • Base cases – first row and first column are prefix sums (only one way to reach them).
  • Answer – dp[m-1][n-1] holds the minimum path sum.
  • Complexity – O(m * n) time, O(n) space with a rolling row or O(1) by overwriting the input.

A funds-routing engine at the National Bank of India sends money across a grid of correspondent banks, each charging a fee, moving only east or south. The cheapest way to reach any bank is its own fee plus the cheaper of arriving from the west or the north.

Understanding the Concepts

What is the Minimum Path Sum problem?

You have an m-by-n grid where each cell contains a non-negative cost. Starting at the top-left corner and moving only right or down, find the path to the bottom-right corner that minimises the total cost. The cost of a path is the sum of all cell values along it.

In banking, this is routing funds through a network of correspondent banks where each bank charges a fee, and you want the cheapest route.

How does the DP recurrence work?

At each cell (i, j), the robot arrives from either the cell above (i-1, j) or the cell to the left (i, j-1). The minimum cost to reach (i, j) is the cell's own cost plus the cheaper of the two incoming paths.

JAVA
package com.bank.service;

public class FundsRoutingService {

    public static int minimumPathSum(int[][] fees) {
        int m = fees.length, n = fees[0].length;
        int[][] dp = new int[m][n];

        dp[0][0] = fees[0][0];

        // First column: can only come from above
        for (int i = 1; i < m; i++) {
            dp[i][0] = dp[i - 1][0] + fees[i][0];
        }

        // First row: can only come from the left
        for (int j = 1; j < n; j++) {
            dp[0][j] = dp[0][j - 1] + fees[0][j];
        }

        // Interior cells: min of above and left
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = fees[i][j] + Math.min(dp[i - 1][j], dp[i][j - 1]);
            }
        }
        return dp[m - 1][n - 1];
    }

    public static void main(String[] args) {
        int[][] fees = {
            {1, 3, 1},
            {1, 5, 1},
            {4, 2, 1}
        };
        System.out.println("Minimum routing fee: " + minimumPathSum(fees));
    }
}
TEXT
Minimum routing fee: 7

Why does greedy fail here?

A greedy approach that always picks the cheapest next cell can miss a better overall route. The greedy choice is locally optimal but not globally optimal.

JAVA
// Grid:    1  3  1
//          1  5  1
//          4  2  1
// Greedy from (0,0): go right to 3? No, go down to 1.
// From (1,0): go right to 5? Or down to 4?
// Greedy picks 4 (down), then 2 (right), then 1 (right) = 1+1+4+2+1 = 9
// Optimal: 1 -> 3 -> 1 -> 1 -> 1 = 7
// Greedy fails because it avoids the 3 but hits the 4.

How do you optimise space?

Since each cell depends only on the cell above (previous row) and the cell to the left (current row), you can use a single rolling row. You can also overwrite the input grid in place for O(1) extra space if mutation is acceptable.

JAVA
public static int minimumPathSumOptimised(int[][] fees) {
    int m = fees.length, n = fees[0].length;
    int[] dp = new int[n];
    dp[0] = fees[0][0];

    // First row
    for (int j = 1; j < n; j++) dp[j] = dp[j - 1] + fees[0][j];

    // Remaining rows
    for (int i = 1; i < m; i++) {
        dp[0] += fees[i][0]; // first column: only from above
        for (int j = 1; j < n; j++) {
            dp[j] = fees[i][j] + Math.min(dp[j], dp[j - 1]);
            // dp[j] (before update) = value from above
            // dp[j-1] (already updated) = value from left
        }
    }
    return dp[n - 1];
}

How is this different from Unique Paths?

| Aspect | Minimum Path Sum | Unique Paths |
|---|---|---|
| Cell values | Each cell has a cost | All cells cost 1 (or free) |
| Objective | Minimise total cost | Count total paths |
| Transition | min(above, left) + cost | above + left (addition) |
| Answer | dp[m-1][n-1] is a cost | dp[m-1][n-1] is a count |

What are the edge cases?

  • 1x1 grid – the answer is the single cell's value.
  • Single row – only one path, the sum of all cells.
  • Single column – only one path, the sum of all cells.
  • All zeros – the minimum path sum is 0.
  • Very large values – can overflow int if the grid is large; use long.
JAVA
// Edge case: 1x1 grid
int[][] single = {{42}};
System.out.println(minimumPathSum(single)); // 42

// Edge case: single row
int[][] row = {{1, 2, 3}};
System.out.println(minimumPathSum(row)); // 6 (only path)

What are common mistakes?

The most common mistake is not initialising the first row and column as prefix sums.

JAVA
// WRONG: leaving first row/column as grid values
dp[0][0] = fees[0][0];
// dp[0][1] should be dp[0][0] + fees[0][1], not just fees[0][1]
// Because the first row has only one way to arrive: from the left

// CORRECT: prefix sums for edges
for (int j = 1; j < n; j++) dp[0][j] = dp[0][j - 1] + fees[0][j];

What is the time and space complexity?

Time is O(m * n) because every cell is visited once with O(1) work. Space is O(m * n) for the full table, O(n) with a rolling row, or O(1) if overwriting the input. For a banking routing grid of 1000 x 1000, this runs in milliseconds.

What should a senior engineer know?

A senior engineer should know that this simple DP works only because costs are non-negative and movement is restricted to right and down. If costs could be negative, cycles could reduce the total infinitely, requiring Bellman-Ford. If movement were unrestricted (up, down, left, right), Dijkstra's algorithm would be needed.

They should also know how to reconstruct the actual path by backtracking through the DP table, following the direction (above or left) that contributed the smaller value at each cell.

Visual Overview

DIAGRAM
  Grid (fees):          DP Table:
  ┌───┬───┬───┐        ┌───┬───┬───┐
  │ 1 │ 3 │ 1 │        │ 1 │ 4 │ 5 │
  ├───┼───┼───┤        ├───┼───┼───┤
  │ 1 │ 5 │ 1 │        │ 2 │ 7 │ 6 │
  ├───┼───┼───┤        ├───┼───┼───┤
  │ 4 │ 2 │ 1 │        │ 6 │ 8 │ 7 │
  └───┴───┴───┘        └───┴───┴───┘

  dp[1][1] = 5 + min(dp[0][1]=4, dp[1][0]=2) = 5 + 2 = 7
  dp[2][2] = 1 + min(dp[1][2]=6, dp[2][1]=8) = 1 + 6 = 7

  Optimal Path: (0,0)->( 0,1)->(0,2)->(1,2)->(2,2)
  Cost: 1 + 3 + 1 + 1 + 1 = 7

  Rolling Row Optimisation:
  ┌─────────────────────────────────────────┐
  │ Single dp[] array of width n            │
  │ dp[j] before update = from above        │
  │ dp[j-1] after update = from left        │
  │ dp[j] = cost + min(dp[j], dp[j-1])     │
  └─────────────────────────────────────────┘

Key Takeaway

Minimum Path Sum is a grid dynamic programming problem where dp[i][j] stores the minimum total cost to reach cell (i, j) from the top-left, moving only right or down. The base cases are the first row and first column as prefix sums, because along an edge there is only one way to arrive. For every interior cell, you add its cost to the cheaper of the two incoming directions.

Greedy fails because a locally cheap next step can lead to an expensive overall route. The DP considers all paths implicitly by building the optimal cost to every cell, and the answer sits in the bottom-right corner. Space reduces to a single rolling row, or even O(1) by overwriting the input.

At the National Bank of India, a funds-routing engine passed money through a grid of correspondent banks, each charging a fee. A greedy next-cheapest-hop choice missed better overall routes, so Rajesh used this DP to compute the true minimum fee to every bank. The rolling-row version handled large grids efficiently.

The senior insight is that this simple DP works only because costs are non-negative and movement is restricted. Negative costs could create beneficial cycles (requiring Bellman-Ford), and unrestricted movement would need Dijkstra. Reconstructing the actual cheapest path requires backtracking through the DP table.

Remember one sentence: dp[i][j] = cost + min(from above, from left), with first row and column as prefix sums, giving the cheapest route in O(m * n) time.

---

Share
Use ← → arrow keys to navigate