Facebook Pixel

2304. Minimum Path Cost in a Grid

Problem Description

You have a grid with m rows and n columns containing distinct integers from 0 to m * n - 1. You need to find the minimum cost path from any cell in the first row to any cell in the last row.

Movement Rules:

  • You can only move downward to the next row
  • From cell (x, y), you can move to any cell in the next row: (x + 1, 0), (x + 1, 1), ..., (x + 1, n - 1)
  • You cannot move from cells in the last row

Cost Calculation:

  • Each cell has a value (the number in the grid at that position)
  • Moving from one cell to another has an additional movement cost
  • The movement cost is given by a 2D array moveCost where:
    • moveCost[i][j] is the cost of moving from a cell with value i to column j of the next row
  • The total path cost = sum of all cell values visited + sum of all movement costs

Example: If you have a path that visits cells with values [a, b, c] and the movement costs are [cost1, cost2], then:

  • Total path cost = a + b + c + cost1 + cost2

The goal is to find the path with the minimum total cost starting from any cell in the first row and ending at any cell in the last row.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

This is a classic dynamic programming problem because we need to find the optimal path and the problem has overlapping subproblems.

Think about it this way: to reach any cell in row i, we must have come from some cell in row i-1. The key insight is that the minimum cost to reach a cell (i, j) depends only on the minimum costs to reach cells in the previous row.

Why? Because once we know the minimum cost to reach each cell in row i-1, we can calculate the minimum cost to reach any cell in row i by trying all possible moves from row i-1 and picking the cheapest option.

For each cell (i, j), we need to consider:

  • Which cell in the previous row should we come from?
  • We could come from any column k in row i-1
  • The cost would be: (minimum cost to reach (i-1, k)) + (movement cost from that cell to (i, j)) + (value of cell (i, j))

This naturally leads to a dynamic programming solution where:

  • f[i][j] = minimum cost to reach cell (i, j) from the first row
  • We build up the solution row by row
  • For each cell, we try all possible previous cells and take the minimum

Since we only need information from the previous row to calculate the current row, we can optimize space by using a rolling array - just keeping track of the costs for the previous row instead of the entire grid.

The final answer is the minimum value among all cells in the last row, since we can end at any cell in the last row.

Learn more about Dynamic Programming patterns.

Solution Approach

We implement the dynamic programming solution using a space-optimized approach with a rolling array.

Step 1: Initialize the DP array

  • Start with f = grid[0], which represents the minimum costs to reach each cell in the first row
  • These costs are just the cell values themselves since there's no movement cost to reach the first row

Step 2: Process each row iteratively For each row i from 1 to m-1:

  • Create a new array g of size n initialized with infinity to store the minimum costs for the current row
  • For each cell (i, j) in the current row:
    • Try all possible previous cells (i-1, k) where k ranges from 0 to n-1
    • Calculate the cost: f[k] + moveCost[grid[i-1][k]][j] + grid[i][j]
      • f[k]: minimum cost to reach cell (i-1, k)
      • moveCost[grid[i-1][k]][j]: cost to move from cell with value grid[i-1][k] to column j
      • grid[i][j]: value of the current cell
    • Update g[j] with the minimum cost found

Step 3: Update the rolling array

  • After processing all cells in row i, update f = g
  • This makes f now represent the minimum costs for the current row, ready for the next iteration

Step 4: Return the result

  • After processing all rows, f contains the minimum costs to reach each cell in the last row
  • Return min(f) to get the overall minimum path cost

State Transition Formula:

f[i][j] = min{f[i-1][k] + moveCost[grid[i-1][k]][j] + grid[i][j]} for all k in [0, n-1]

Time Complexity: O(m × n²) - For each of the m×n cells, we check n possible previous cells

Space Complexity: O(n) - We only maintain arrays for the current and previous rows

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Given:

  • Grid (3×3):
[5, 3, 2]
[1, 7, 8]
[4, 6, 9]
  • Movement costs (moveCost[value][column]):
moveCost[5][0]=1, moveCost[5][1]=2, moveCost[5][2]=3
moveCost[3][0]=4, moveCost[3][1]=1, moveCost[3][2]=2
moveCost[2][0]=2, moveCost[2][1]=3, moveCost[2][2]=1
moveCost[1][0]=3, moveCost[1][1]=2, moveCost[1][2]=1
moveCost[7][0]=1, moveCost[7][1]=3, moveCost[7][2]=2
moveCost[8][0]=2, moveCost[8][1]=1, moveCost[8][2]=3

Step 1: Initialize

  • f = [5, 3, 2] (costs to reach first row cells are just their values)

Step 2: Process Row 1 Calculate minimum cost to reach each cell in row 1:

For cell (1,0) with value 1:

  • From (0,0): cost = 5 + moveCost[5][0] + 1 = 5 + 1 + 1 = 7
  • From (0,1): cost = 3 + moveCost[3][0] + 1 = 3 + 4 + 1 = 8
  • From (0,2): cost = 2 + moveCost[2][0] + 1 = 2 + 2 + 1 = 5
  • Minimum: g[0] = 5

For cell (1,1) with value 7:

  • From (0,0): cost = 5 + moveCost[5][1] + 7 = 5 + 2 + 7 = 14
  • From (0,1): cost = 3 + moveCost[3][1] + 7 = 3 + 1 + 7 = 11
  • From (0,2): cost = 2 + moveCost[2][1] + 7 = 2 + 3 + 7 = 12
  • Minimum: g[1] = 11

For cell (1,2) with value 8:

  • From (0,0): cost = 5 + moveCost[5][2] + 8 = 5 + 3 + 8 = 16
  • From (0,1): cost = 3 + moveCost[3][2] + 8 = 3 + 2 + 8 = 13
  • From (0,2): cost = 2 + moveCost[2][2] + 8 = 2 + 1 + 8 = 11
  • Minimum: g[2] = 11

Update: f = [5, 11, 11]

Step 3: Process Row 2 Calculate minimum cost to reach each cell in row 2:

For cell (2,0) with value 4:

  • From (1,0): cost = 5 + moveCost[1][0] + 4 = 5 + 3 + 4 = 12
  • From (1,1): cost = 11 + moveCost[7][0] + 4 = 11 + 1 + 4 = 16
  • From (1,2): cost = 11 + moveCost[8][0] + 4 = 11 + 2 + 4 = 17
  • Minimum: g[0] = 12

For cell (2,1) with value 6:

  • From (1,0): cost = 5 + moveCost[1][1] + 6 = 5 + 2 + 6 = 13
  • From (1,1): cost = 11 + moveCost[7][1] + 6 = 11 + 3 + 6 = 20
  • From (1,2): cost = 11 + moveCost[8][1] + 6 = 11 + 1 + 6 = 18
  • Minimum: g[1] = 13

For cell (2,2) with value 9:

  • From (1,0): cost = 5 + moveCost[1][2] + 9 = 5 + 1 + 9 = 15
  • From (1,1): cost = 11 + moveCost[7][2] + 9 = 11 + 2 + 9 = 22
  • From (1,2): cost = 11 + moveCost[8][2] + 9 = 11 + 3 + 9 = 23
  • Minimum: g[2] = 15

Final: f = [12, 13, 15]

Step 4: Return Result The minimum cost path is min(12, 13, 15) = 12

The optimal path is: (0,2) → (1,0) → (2,0) with total cost 12.

Solution Implementation

1class Solution:
2    def minPathCost(self, grid: List[List[int]], moveCost: List[List[int]]) -> int:
3        # Get dimensions of the grid
4        rows, cols = len(grid), len(grid[0])
5      
6        # Initialize DP array with the first row values
7        # dp[j] represents minimum cost to reach position j in current row
8        dp = grid[0]
9      
10        # Process each row starting from the second row
11        for current_row in range(1, rows):
12            # Create new DP array for current row
13            new_dp = [float('inf')] * cols
14          
15            # For each column in current row
16            for current_col in range(cols):
17                # Try all possible previous columns from previous row
18                for prev_col in range(cols):
19                    # Calculate cost: previous cell cost + move cost + current cell value
20                    # grid[current_row - 1][prev_col] is the value of the cell we're moving from
21                    # moveCost[grid[current_row - 1][prev_col]][current_col] is the cost to move from that cell to current column
22                    # grid[current_row][current_col] is the value of current cell
23                    cost = dp[prev_col] + moveCost[grid[current_row - 1][prev_col]][current_col] + grid[current_row][current_col]
24                    new_dp[current_col] = min(new_dp[current_col], cost)
25          
26            # Update DP array for next iteration
27            dp = new_dp
28      
29        # Return minimum cost among all ending positions in the last row
30        return min(dp)
31
1class Solution {
2    public int minPathCost(int[][] grid, int[][] moveCost) {
3        int numRows = grid.length;
4        int numCols = grid[0].length;
5      
6        // Initialize dp array with the first row values
7        // dp[j] represents the minimum cost to reach column j in the current row
8        int[] dp = grid[0];
9      
10        // Define a large value to represent infinity
11        final int INFINITY = 1 << 30;
12      
13        // Process each row starting from the second row
14        for (int row = 1; row < numRows; ++row) {
15            // Create a new dp array for the current row
16            int[] currentRowDp = new int[numCols];
17            Arrays.fill(currentRowDp, INFINITY);
18          
19            // Calculate minimum cost for each column in the current row
20            for (int col = 0; col < numCols; ++col) {
21                // Try all possible previous columns from the previous row
22                for (int prevCol = 0; prevCol < numCols; ++prevCol) {
23                    // Calculate the cost: previous cell cost + move cost + current cell value
24                    // grid[row - 1][prevCol] is the value of the cell we're moving from
25                    // moveCost[grid[row - 1][prevCol]][col] is the cost to move from that cell to current column
26                    // grid[row][col] is the value of the current cell
27                    int totalCost = dp[prevCol] + moveCost[grid[row - 1][prevCol]][col] + grid[row][col];
28                    currentRowDp[col] = Math.min(currentRowDp[col], totalCost);
29                }
30            }
31          
32            // Update dp array for the next iteration
33            dp = currentRowDp;
34        }
35      
36        // Find the minimum value in the last row
37        int minPathCost = INFINITY;
38        for (int cost : dp) {
39            minPathCost = Math.min(minPathCost, cost);
40        }
41      
42        return minPathCost;
43    }
44}
45
1class Solution {
2public:
3    int minPathCost(vector<vector<int>>& grid, vector<vector<int>>& moveCost) {
4        int numRows = grid.size();
5        int numCols = grid[0].size();
6        const int INF = 1 << 30;  // Large value representing infinity
7      
8        // Initialize DP array with the first row values
9        // dp[j] represents minimum cost to reach column j in current row
10        vector<int> dp = grid[0];
11      
12        // Process each row starting from the second row
13        for (int row = 1; row < numRows; ++row) {
14            // Temporary array to store minimum costs for current row
15            vector<int> currentRowDP(numCols, INF);
16          
17            // Calculate minimum cost for each cell in current row
18            for (int currentCol = 0; currentCol < numCols; ++currentCol) {
19                // Try all possible previous columns from the previous row
20                for (int prevCol = 0; prevCol < numCols; ++prevCol) {
21                    // Calculate cost: previous cell cost + move cost + current cell value
22                    int totalCost = dp[prevCol] + 
23                                   moveCost[grid[row - 1][prevCol]][currentCol] + 
24                                   grid[row][currentCol];
25                  
26                    // Update minimum cost for current position
27                    currentRowDP[currentCol] = min(currentRowDP[currentCol], totalCost);
28                }
29            }
30          
31            // Move current row results to dp array for next iteration
32            dp = move(currentRowDP);
33        }
34      
35        // Return the minimum cost among all columns in the last row
36        return *min_element(dp.begin(), dp.end());
37    }
38};
39
1/**
2 * Finds the minimum path cost from the first row to the last row of a grid
3 * @param grid - 2D array representing the grid with values at each cell
4 * @param moveCost - 2D array where moveCost[i][j] represents the cost to move from value i to column j
5 * @returns The minimum total cost to reach the last row from the first row
6 */
7function minPathCost(grid: number[][], moveCost: number[][]): number {
8    const numRows: number = grid.length;
9    const numCols: number = grid[0].length;
10  
11    // Initialize dp array with the first row values
12    let dpPrevRow: number[] = grid[0];
13  
14    // Process each row starting from the second row
15    for (let currentRow = 1; currentRow < numRows; ++currentRow) {
16        // Initialize the dp array for the current row with infinity values
17        const dpCurrentRow: number[] = Array(numCols).fill(Infinity);
18      
19        // Calculate minimum cost for each cell in the current row
20        for (let currentCol = 0; currentCol < numCols; ++currentCol) {
21            // Try all possible previous columns from the previous row
22            for (let prevCol = 0; prevCol < numCols; ++prevCol) {
23                // Calculate total cost: previous cell cost + move cost + current cell value
24                const totalCost: number = dpPrevRow[prevCol] + 
25                                         moveCost[grid[currentRow - 1][prevCol]][currentCol] + 
26                                         grid[currentRow][currentCol];
27              
28                // Update minimum cost for current cell
29                dpCurrentRow[currentCol] = Math.min(dpCurrentRow[currentCol], totalCost);
30            }
31        }
32      
33        // Update the previous row dp array with current row values
34        dpPrevRow.splice(0, numCols, ...dpCurrentRow);
35    }
36  
37    // Return the minimum value from the last row
38    return Math.min(...dpPrevRow);
39}
40

Time and Space Complexity

Time Complexity: O(m × n²)

The code contains three nested loops:

  • The outer loop runs m-1 times (from row 1 to row m-1)
  • The middle loop runs n times (for each column j in the current row)
  • The inner loop runs n times (for each column k in the previous row)

For each iteration of the three loops, we perform constant time operations: accessing array elements, computing the minimum, and addition. Therefore, the total time complexity is O((m-1) × n × n) = O(m × n²).

Space Complexity: O(n)

The space complexity is determined by the additional space used:

  • Array f stores the minimum costs for the previous row, using O(n) space
  • Array g stores the minimum costs for the current row, using O(n) space

Since we only maintain two arrays of size n at any time (reusing them by reassigning f = g), the total space complexity is O(n). The input arrays grid and moveCost are not counted in the space complexity analysis as they are given inputs.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Incorrect Movement Cost Indexing

The Problem: A frequent mistake is using the wrong indices when accessing the movement cost array. Developers often confuse:

  • Using row/column indices directly instead of cell values
  • Using the destination cell's value instead of the source cell's value

Incorrect Implementation:

# WRONG: Using row/column indices
cost = dp[prev_col] + moveCost[current_row - 1][current_col] + grid[current_row][current_col]

# WRONG: Using destination cell value
cost = dp[prev_col] + moveCost[grid[current_row][current_col]][current_col] + grid[current_row][current_col]

Correct Implementation:

# CORRECT: Use the VALUE of the source cell as the first index
cost = dp[prev_col] + moveCost[grid[current_row - 1][prev_col]][current_col] + grid[current_row][current_col]

Pitfall 2: Forgetting to Include Cell Values in Path Cost

The Problem: Another common error is calculating only the movement costs without including the actual cell values in the total path cost. The problem requires both the sum of cell values AND movement costs.

Incorrect Implementation:

# WRONG: Only considering movement costs
cost = dp[prev_col] + moveCost[grid[current_row - 1][prev_col]][current_col]

Correct Implementation:

# CORRECT: Include both movement cost and current cell value
cost = dp[prev_col] + moveCost[grid[current_row - 1][prev_col]][current_col] + grid[current_row][current_col]

Pitfall 3: Improper DP Initialization

The Problem: Failing to properly initialize the DP array for the first row. Since there's no movement cost to reach the first row, the initial costs should be just the cell values themselves.

Incorrect Implementation:

# WRONG: Initializing with zeros
dp = [0] * cols

# WRONG: Initializing with infinity
dp = [float('inf')] * cols

Correct Implementation:

# CORRECT: Initialize with first row values
dp = grid[0]  # or dp = grid[0].copy() if you want to be explicit

Complete Corrected Solution:

class Solution:
    def minPathCost(self, grid: List[List[int]], moveCost: List[List[int]]) -> int:
        rows, cols = len(grid), len(grid[0])
      
        # Initialize with first row values (no movement cost to reach first row)
        dp = grid[0].copy()
      
        for current_row in range(1, rows):
            new_dp = [float('inf')] * cols
          
            for current_col in range(cols):
                for prev_col in range(cols):
                    # Correct indexing: moveCost[cell_value][destination_column]
                    source_value = grid[current_row - 1][prev_col]
                    movement_cost = moveCost[source_value][current_col]
                    current_cell_value = grid[current_row][current_col]
                  
                    # Total cost includes: previous path cost + movement cost + current cell value
                    total_cost = dp[prev_col] + movement_cost + current_cell_value
                    new_dp[current_col] = min(new_dp[current_col], total_cost)
          
            dp = new_dp
      
        return min(dp)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More