Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1368. Minimum Cost to Make at Least One Valid Path in a Grid - Leetcode Solution

Problem Description

You are given a m x n grid where each cell contains a value from 1 to 4. Each value represents a direction:

  • 1 - right
  • 2 - left
  • 3 - down
  • 4 - up
Starting from the top-left cell (0, 0), your goal is to reach the bottom-right cell (m-1, n-1) by following a path. You can move to an adjacent cell in the direction indicated by the current cell at no cost. If you want to move in a different direction, you must change the arrow in the current cell, incurring a cost of 1 for each change.

Find the minimum cost required to make at least one valid path from the top-left to the bottom-right cell.

Constraints:
  • You must make at least one valid path from (0,0) to (m-1,n-1).
  • Each cell's arrow can be changed any number of times, but each change costs 1.
  • You can only move to adjacent cells (up, down, left, right).

Thought Process

At first glance, you might consider brute-forcing all possible paths from the start to the end, changing directions as needed and accumulating the cost for each change. However, this approach quickly becomes infeasible as the grid grows in size due to the exponential number of possible paths.

Instead, notice that each move in the direction indicated by the cell is "free" (cost 0), while moving in another direction requires a "change" (cost 1). This suggests modeling the problem as a shortest path search with edge weights of 0 or 1 depending on whether you follow the arrow or need to change it.

The optimal approach is to use a modified BFS (Breadth-First Search) that accounts for edge weights, often called 0-1 BFS. This allows us to always expand the lowest-cost paths first, ensuring we find the minimum cost to reach the bottom-right cell.

Solution Approach

To solve the problem efficiently, we treat the grid as a graph where each cell is a node. Moving in the indicated direction has a cost of 0, while moving in any other direction has a cost of 1. We use a double-ended queue (deque) to perform a 0-1 BFS:

  1. Direction Mapping: Map each direction value (1-4) to its corresponding movement (right, left, down, up).
  2. Cost Array: Maintain a 2D array cost where cost[i][j] is the minimum cost to reach cell (i, j).
  3. 0-1 BFS: Use a deque to process cells. When moving in the direction indicated by the cell, add the new cell to the front of the queue (cost does not increase). When moving in another direction, add the new cell to the back of the queue (cost increases by 1).
  4. Early Exit: Stop processing as soon as you reach the bottom-right cell, since the BFS guarantees this is the minimum cost.
This approach guarantees that we always expand the lowest-cost paths first, efficiently finding the minimum cost required to create at least one valid path.

Example Walkthrough

Input:

    grid = [
      [1, 1, 3],
      [3, 2, 2],
      [1, 1, 4]
    ]
    
Step-by-step:
  1. Start at (0,0) where the arrow points right. Move to (0,1) at cost 0.
  2. At (0,1), arrow points right. Move to (0,2) at cost 0.
  3. At (0,2), arrow points down. Move to (1,2) at cost 0.
  4. At (1,2), arrow points left. The only way forward is to change the arrow to point down (cost 1) and move to (2,2).
  5. At (2,2), we have reached the bottom-right cell. The minimum cost is 1.
Explanation: The path that follows the arrows as much as possible, only changing direction when absolutely necessary, yields the minimum cost.

Time and Space Complexity

Brute-force Approach:

  • Time Complexity: Exponential in the number of cells, as all possible paths are explored.
  • Space Complexity: Exponential, due to recursion stack or path tracking.
Optimized 0-1 BFS Approach:
  • Time Complexity: O(m*n), since each cell is processed at most once per cost value, and there are m*n cells.
  • Space Complexity: O(m*n) for the cost array and queue.
This makes the optimized solution highly efficient and suitable for large grids.

Summary

The key insight is to treat the grid as a graph and use a shortest-path algorithm tailored for 0-1 edge weights (0-1 BFS). By always moving in the cell's indicated direction when possible, and only changing direction when necessary, we minimize the total cost. This approach is both time and space efficient, and elegantly solves the problem by leveraging graph traversal techniques.

Code Implementation

from collections import deque

class Solution:
    def minCost(self, grid):
        m, n = len(grid), len(grid[0])
        dirs = [(0,1), (0,-1), (1,0), (-1,0)]  # right, left, down, up
        cost = [[float('inf')] * n for _ in range(m)]
        cost[0][0] = 0
        dq = deque()
        dq.append((0, 0))

        while dq:
            x, y = dq.popleft()
            for i, (dx, dy) in enumerate(dirs):
                nx, ny = x + dx, y + dy
                if 0 <= nx < m and 0 <= ny < n:
                    # If moving in the direction of the arrow, cost is 0; else 1
                    ncost = cost[x][y] + (0 if grid[x][y] == i + 1 else 1)
                    if ncost < cost[nx][ny]:
                        cost[nx][ny] = ncost
                        if grid[x][y] == i + 1:
                            dq.appendleft((nx, ny))
                        else:
                            dq.append((nx, ny))
        return cost[m-1][n-1]
      
#include <vector>
#include <deque>
#include <climits>
using namespace std;

class Solution {
public:
    int minCost(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        vector<vector<int>> cost(m, vector<int>(n, INT_MAX));
        vector<pair<int, int>> dirs = {{0,1}, {0,-1}, {1,0}, {-1,0}};
        deque<pair<int, int>> dq;
        cost[0][0] = 0;
        dq.push_back({0,0});

        while (!dq.empty()) {
            auto [x, y] = dq.front(); dq.pop_front();
            for (int i = 0; i < 4; ++i) {
                int nx = x + dirs[i].first, ny = y + dirs[i].second;
                if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                    int ncost = cost[x][y] + (grid[x][y] == i+1 ? 0 : 1);
                    if (ncost < cost[nx][ny]) {
                        cost[nx][ny] = ncost;
                        if (grid[x][y] == i+1)
                            dq.push_front({nx, ny});
                        else
                            dq.push_back({nx, ny});
                    }
                }
            }
        }
        return cost[m-1][n-1];
    }
};
      
import java.util.*;

class Solution {
    public int minCost(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        int[][] cost = new int[m][n];
        for (int[] row : cost) Arrays.fill(row, Integer.MAX_VALUE);
        int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
        Deque<int[]> dq = new ArrayDeque<>();
        cost[0][0] = 0;
        dq.offerFirst(new int[]{0,0});

        while (!dq.isEmpty()) {
            int[] cell = dq.pollFirst();
            int x = cell[0], y = cell[1];
            for (int i = 0; i < 4; ++i) {
                int nx = x + dirs[i][0], ny = y + dirs[i][1];
                if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                    int ncost = cost[x][y] + (grid[x][y] == i+1 ? 0 : 1);
                    if (ncost < cost[nx][ny]) {
                        cost[nx][ny] = ncost;
                        if (grid[x][y] == i+1)
                            dq.offerFirst(new int[]{nx, ny});
                        else
                            dq.offerLast(new int[]{nx, ny});
                    }
                }
            }
        }
        return cost[m-1][n-1];
    }
}
      
/**
 * @param {number[][]} grid
 * @return {number}
 */
var minCost = function(grid) {
    const m = grid.length, n = grid[0].length;
    const dirs = [[0,1],[0,-1],[1,0],[-1,0]];
    const cost = Array.from({length: m}, () => Array(n).fill(Infinity));
    cost[0][0] = 0;
    const deque = [];
    deque.push([0, 0]);

    while (deque.length) {
        const [x, y] = deque.shift();
        for (let i = 0; i < 4; ++i) {
            const nx = x + dirs[i][0], ny = y + dirs[i][1];
            if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                const ncost = cost[x][y] + (grid[x][y] === i + 1 ? 0 : 1);
                if (ncost < cost[nx][ny]) {
                    cost[nx][ny] = ncost;
                    if (grid[x][y] === i + 1)
                        deque.unshift([nx, ny]);
                    else
                        deque.push([nx, ny]);
                }
            }
        }
    }
    return cost[m-1][n-1];
};