DEV Community

Dev Cookies
Dev Cookies

Posted on

πŸ“– Blog 3: Mastering 2D Grid DP (Matrix Problems)

Dynamic Programming on 2D grids is one of the most popular DP patterns. Many real-world problems like pathfinding, robot navigation, game boards, obstacle courses, and resource collection can be mapped into grid-based DP problems.

The key idea:

  • Imagine you are on a grid (like a chessboard).
  • At each cell, you make decisions (move right, move down, sometimes diagonally).
  • The DP state keeps track of the best solution up to that cell (minimum cost, maximum coins, number of ways, etc.).
  • By filling the grid step by step, you solve the entire problem.

πŸ”‘ Core Concepts for Grid DP

  1. State β†’ What does dp[i][j] mean?
  • It usually represents the "best" (or number of ways, or cost) to reach cell (i, j).
  1. Transition β†’ How do we reach dp[i][j]?
  • Common moves: from top (i-1, j) or left (i, j-1), sometimes diagonally.
  1. Base Cases β†’ Usually the starting cell (0,0) is initialized (like starting point of a robot).

  2. Answer β†’ Usually found at dp[m-1][n-1] (bottom-right cell).


🧩 Example 1: Unique Paths (Count All Ways)

Problem: A robot can only move right or down on an m x n grid. How many unique paths are there to reach the bottom-right corner?

Approach:

  • If you’re at (i, j), you could only have come from above (i-1, j) or left (i, j-1).
  • Add those ways together.

Code:

public class UniquePaths {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];

        // Initialize first row and first column (only 1 way to move straight)
        for (int i = 0; i < m; i++) dp[i][0] = 1;
        for (int j = 0; j < n; j++) dp[0][j] = 1;

        // Fill rest of grid
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }

        return dp[m-1][n-1];
    }
}
Enter fullscreen mode Exit fullscreen mode

🧩 Example 2: Unique Paths with Obstacles

Problem: Same as above, but some cells are blocked (1 = obstacle).

Approach:

  • If there’s an obstacle, set dp[i][j] = 0.
  • Otherwise, sum up paths from top and left.

Code:

public class UniquePathsWithObstacles {
    public int uniquePathsWithObstacles(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        int[][] dp = new int[m][n];

        // If starting point is blocked β†’ no path
        if (grid[0][0] == 1) return 0;
        dp[0][0] = 1;

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) { // obstacle
                    dp[i][j] = 0;
                } else {
                    if (i > 0) dp[i][j] += dp[i-1][j];
                    if (j > 0) dp[i][j] += dp[i][j-1];
                }
            }
        }
        return dp[m-1][n-1];
    }
}
Enter fullscreen mode Exit fullscreen mode

🧩 Example 3: Minimum Path Sum

Problem: Each cell has a cost. Find the minimum cost to reach the bottom-right.

Approach:

  • At (i, j), take the minimum of coming from top or left, plus the current cell’s cost.

Code:

public class MinPathSum {
    public int minPathSum(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        int[][] dp = new int[m][n];

        dp[0][0] = grid[0][0];

        // First row
        for (int j = 1; j < n; j++) dp[0][j] = dp[0][j-1] + grid[0][j];
        // First column
        for (int i = 1; i < m; i++) dp[i][0] = dp[i-1][0] + grid[i][0];

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
            }
        }

        return dp[m-1][n-1];
    }
}
Enter fullscreen mode Exit fullscreen mode

🧩 Example 4: Maximum Path Sum (Collecting Maximum Coins)

Problem: Each cell has some coins. Collect the maximum coins while moving from top-left to bottom-right.

Approach:

  • Same as minimum path sum, but take the maximum instead of minimum.

Code:

public class MaxPathSum {
    public int maxPathSum(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        int[][] dp = new int[m][n];

        dp[0][0] = grid[0][0];

        for (int j = 1; j < n; j++) dp[0][j] = dp[0][j-1] + grid[0][j];
        for (int i = 1; i < m; i++) dp[i][0] = dp[i-1][0] + grid[i][0];

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]) + grid[i][j];
            }
        }

        return dp[m-1][n-1];
    }
}
Enter fullscreen mode Exit fullscreen mode

🧩 Example 5: Minimum Falling Path Sum (Diagonal Allowed)

Problem: From the top row, move down to the last row. At each step, you can go down, down-left, or down-right. Find the minimum path sum.

Approach:

  • Each cell depends on three possible parents.

Code:

public class MinFallingPathSum {
    public int minFallingPathSum(int[][] matrix) {
        int n = matrix.length;
        int[][] dp = new int[n][n];

        // Copy first row
        for (int j = 0; j < n; j++) dp[0][j] = matrix[0][j];

        for (int i = 1; i < n; i++) {
            for (int j = 0; j < n; j++) {
                int minPrev = dp[i-1][j];
                if (j > 0) minPrev = Math.min(minPrev, dp[i-1][j-1]);
                if (j < n-1) minPrev = Math.min(minPrev, dp[i-1][j+1]);

                dp[i][j] = matrix[i][j] + minPrev;
            }
        }

        int ans = Integer.MAX_VALUE;
        for (int j = 0; j < n; j++) ans = Math.min(ans, dp[n-1][j]);
        return ans;
    }
}
Enter fullscreen mode Exit fullscreen mode

🧩 Example 6: Cherry Pickup (Two Robots Collecting Maximum)

Problem: Two robots start at (0,0) and (0, n-1) and move down to the last row. Each cell has cherries. If both robots pick from the same cell, count it once. Find maximum cherries collected.

Approach:

  • State β†’ (row, col1, col2) (row number and positions of both robots).
  • Transition β†’ Robots move in 9 combinations (left, down, right).

Code (recursive + memoization):

public class CherryPickup {
    int[][][] memo;
    public int cherryPickup(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        memo = new int[m][n][n];
        for (int[][] layer : memo) 
            for (int[] row : layer) 
                Arrays.fill(row, -1);
        return dfs(grid, 0, 0, n-1);
    }

    private int dfs(int[][] grid, int row, int col1, int col2) {
        int m = grid.length, n = grid[0].length;
        if (col1 < 0 || col1 >= n || col2 < 0 || col2 >= n) return 0;
        if (row == m) return 0;

        if (memo[row][col1][col2] != -1) return memo[row][col1][col2];

        int cherries = grid[row][col1];
        if (col1 != col2) cherries += grid[row][col2];

        int max = 0;
        for (int d1 = -1; d1 <= 1; d1++) {
            for (int d2 = -1; d2 <= 1; d2++) {
                max = Math.max(max, dfs(grid, row+1, col1+d1, col2+d2));
            }
        }

        return memo[row][col1][col2] = cherries + max;
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ”₯ Key Takeaways

  1. Grid DP is about building solutions cell by cell.
  2. Always think β†’ β€œHow can I reach this cell?” (from top, left, diagonal, or multiple agents).
  3. Base cases: first row/col, or blocked cells.
  4. Space optimization β†’ Instead of full dp[][], you can use 1D arrays if only previous row/column is needed.
  5. Applications go beyond games β†’ logistics, robotics, shortest/cheapest route problems, even AI pathfinding.

Top comments (0)