Problem Statement:

Given an n x n array of integers matrix, return the minimum sum of any falling paththrough matrix.

A falling path starts at any element in the first row and chooses the element in the next row that is either directly below or diagonally left/right. Specifically, the next element from position (row, col) will be (row + 1, col - 1), (row + 1, col), or (row + 1, col + 1).

Example 1:

Open: Screenshot 2024-06-14 at 11.23.24 PM.png

Input: matrix = [[2,1,3],[6,5,4],[7,8,9]] Output: 13 Explanation: There are two falling paths with a minimum sum as shown.

Example 2:

Input: matrix = [[-19,57],[-40,-5]] Output: -59 Explanation: The falling path with a minimum sum is shown.

Constraints:

  • n == matrix.length == matrix[i].length
  • 1 <= n <= 100
  • -100 <= matrix[i][j] <= 100

Solution:

1. Recursive and Memoized:

Remove the commented part and the dp part for recursive soln.

class Solution {
    public int minFallingPathSum(int[][] mat) {
        int nRows = mat.length;
        int nCols = mat[0].length;
 
        int[][] dp = new int[nRows][nCols];
        for(int[] mem: dp){
            Arrays.fill(mem, -1);
        }
 
        int min = Integer.MAX_VALUE;
        for(int sc = 0; sc < nCols; sc++){
            minFallingPathSumUtil(mat, 0, sc, nRows - 1, dp);
 
            // int minPathSum = minFallingPathSumUtil(mat, 0, sc, nRows - 1, dp);
            // min = Math.min(min, minPathSum);
        }
 
        for(int i = 0; i < dp[0].length; i++){
            min = Math.min(min, dp[0][i]);
        }
 
        return min;
    }
 
    public int minFallingPathSumUtil(int[][] grid, int sr, int sc, int dr, int[][] dp){
        if(sr < 0 || sc < 0 || sr > dr || sc >= grid[0].length){
            return Integer.MAX_VALUE;
        }
        if(sr == dr){
            return dp[sr][sc] = grid[sr][sc];
        }
        if(dp[sr][sc] != -1){
            return dp[sr][sc];
        }
 
        int down = minFallingPathSumUtil(grid, sr + 1, sc, dr, dp);
        int dright = minFallingPathSumUtil(grid, sr + 1, sc + 1, dr, dp); //diagonal right
        int dleft = minFallingPathSumUtil(grid, sr + 1, sc - 1, dr, dp); //diagonal left
 
        return dp[sr][sc] = grid[sr][sc] + Math.min(down, Math.min(dright, dleft));
    }
}
2. Tabulation:

TC: O(nm) SC: O(nm)

class Solution {
    public int minFallingPathSum(int[][] mat) {
        int nRows = mat.length;
        int nCols = mat[0].length;
 
        // Initialize dp array with the last row of the matrix
        int[][] dp = new int[nRows][nCols];
        for (int j = 0; j < nCols; j++) {
            dp[nRows - 1][j] = mat[nRows - 1][j];
        }
 
        // Fill the dp array from the bottom up
        for (int i = nRows - 2; i >= 0; i--) {
            for (int j = 0; j < nCols; j++) {
                int down = dp[i + 1][j];
                int dRight = (j + 1 >= nCols) ? Integer.MAX_VALUE : dp[i + 1][j + 1];
                int dLeft = (j - 1 < 0) ? Integer.MAX_VALUE : dp[i + 1][j - 1];
                dp[i][j] = mat[i][j] + Math.min(down, Math.min(dRight, dLeft));
            }
        }
 
        // The minimum path sum will be the minimum value in the first row of dp
        int min = Integer.MAX_VALUE;
        for (int j = 0; j < nCols; j++) {
            min = Math.min(min, dp[0][j]);
        }
 
        return min;
    }
3. Space Optimized.

we only need prev row to calculate curr row

TC: O(nm) SC: O(m)

class Solution {
    public int minFallingPathSum(int[][] mat) {
        int nRows = mat.length;
        int nCols = mat[0].length;
 
        int[] prev = new int[nCols];
        int[] curr = new int[nCols];
 
        for(int j = 0; j < nCols; j++){
            prev[j] = mat[nRows - 1][j];
        }
 
        for(int i = nRows - 2; i >= 0; i--){
            //curr = new int[nCols]; //re-assign or not doesn't matter
            for(int j = nCols - 1; j >= 0; j--){
                int down = prev[j];
                int dRight = (j + 1 >= nCols) ? Integer.MAX_VALUE : prev[j + 1];
                int dLeft = (j - 1 < 0) ? Integer.MAX_VALUE : prev[j - 1];
 
                curr[j] = mat[i][j] + Math.min(down, Math.min(dRight, dLeft));
            }
            /*
            The line `prev = curr` does not create a new copy of the `curr` array; instead, it assigns the reference of `curr` to `prev`. 
            This means that subsequent modifications to `curr` will also affect `prev`.
            */
            prev = curr.clone();
        }
 
        int min = Integer.MAX_VALUE;
        for(int i = 0; i < nCols; i++){
            min = Math.min(min, prev[i]);
        }
 
        return min;
    }
}