931. Minimum Falling Path Sum

931. Minimum Falling Path Sum

Problem Solving - Day 73

ยท

5 min read

Hello, reader ๐Ÿ‘‹๐Ÿฝ ! Welcome to day 73 of the series on Problem Solving. Through this series, I aim to pick up at least one question everyday and share my approach for solving it.

Today, I will be picking up LeetCode's daily challenge problem: 931. Minimum Falling Path Sum.


๐Ÿค” Problem Statement

  • Given an n x n array of integers matrix, return the minimum sum of any falling path through 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).

  • E.g.:

    • matrix = [[2,1,3],[6,5,4],[7,8,9]] => 13

๐Ÿ’ฌ Thought Process - Memoization

  • This is similar to maze problems where we calculate the cost that occurs when we traverse through every cell.

  • We'll try to solve this problem from top-down. That is, we begin from all possible cells that could be the end cell and make our way up to the cells in the first row.

  • Since the end cell that in the last row, we will try to find the minimum path sum from all the cells in the last row and then try to move on till we reach the first row.

  • And at every cell in the matrix (except the first and last row), we could have three options:

    • Move to right diagonal in the previous row

    • Move to the left diagonal in the previous row

    • Move the cell of the same column in the previous row

  • Then we'd return the minimum of the three choices along with the current value of the cell.

  • This process is continued on until we reach any cell in the first row, when we just return the cell's value.

  • But since we have three choices and we'd be visiting a cell multiple times, we can memoize the result for every cell.

  • Whenever we reach a cell with row, col that was solved for before, we simply return the memoized answer.

  • This brings down the worst case from O(3^m*n) to O(m*n), with a space complexity of O(m*n).

๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ป Solution - Memoization

  • Below is the code for the approach for solving this problem using memoization.
class Solution {
    private int[][] memo;
    private int rows, cols;

    public int minFallingPathSum(int[][] matrix) {
        rows = matrix.length;
        cols = matrix[0].length;
        memo = new int[rows][cols];

        for(int[] row: memo) {
            Arrays.fill(row, Integer.MAX_VALUE);    
        }

        int min = Integer.MAX_VALUE;
        for(int col = 0; col < cols; col++) {
            min = Math.min(min, 
                  minPathSumHelper(rows-1, col, matrix));
        }

        return min;
    }

    private int minPathSumHelper(int row, int col, int[][] grid) {
        if(col >= cols || col < 0) {
            return Integer.MAX_VALUE;
        }
        if(row == 0) {
            return grid[row][col];
        }

        if(memo[row][col] != Integer.MAX_VALUE) {
            return memo[row][col];
        }

        int curr = grid[row][col];

        int diagLeft = minPathSumHelper(row-1, col-1, grid);
        int diagRight = minPathSumHelper(row-1, col+1, grid);
        int down = minPathSumHelper(row-1, col, grid);

        int min = Math.min(diagLeft, diagRight);
        return memo[row][col] = curr + Math.min(min, down);
    }
}
Time Complexity: O(m*n)
    - m = number of rows
    - n = number of cols
Space Complexity: O(m*n)
    - m - number of rows
    - n = number of cols

๐Ÿ’ฌ Thought Process - Tabulation

  • We can also solve this problem using DP bottom-up/ tabulation where we will solve for the minimum sum path from the smallest to the largest value.

  • We will use a 2D array that tabulates the values calculated for previous rows and then use it in the future rows. We'll work our way from first to the last row.

๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ป Solution - Tabulation

  • Below is the code for the approach for solving this problem using tabulation.
class Solution {    
    public int minFallingPathSum(int[][] matrix) {
        int rows = matrix.length;
        int cols = matrix[0].length;
        int[][] dp = new int[rows][cols];

        // the first row 
        for(int col = 0; col < cols; col++) {
            dp[0][col] = matrix[0][col];
        }

        for(int i = 1; i < rows; i++) {
            for(int j = 0; j < cols; j++) {
                dp[i][j] = Integer.MAX_VALUE;
            }
        }

        for(int row = 1; row < rows; row++) {
            for(int col = 0; col < cols; col++) {
                int curr = matrix[row][col];

                int diagLeft = Integer.MAX_VALUE;
                if((col - 1) >= 0) {
                    diagLeft = dp[row-1][col-1];
                }
                int diagRight = Integer.MAX_VALUE;
                if((col + 1) < cols) {
                    diagRight = dp[row-1][col+1];
                }
                int up = dp[row-1][col];

                int min = Math.min(diagLeft, diagRight);
                min = Math.min(min, up) + curr;
                dp[row][col] = Math.min(dp[row][col], min);
            }
        }

        int min = dp[rows-1][0];
        for(int i = 0; i<cols; i++) {
            min = Math.min(min, dp[rows-1][i]);
        }
        return min;
    }
}
Time Complexity: O(n)
Space Complexity: O(n)

๐Ÿ’ฌ Thought Process - Space optimisation

  • Since we only require the previous row's values, instead of caching all the n rows, we can use just a 1D array of size n to store the previous subproblem answers.

๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ป Solution - Space optimisation

  • Below is the code for the approach for solving this problem using space optimisation.
class Solution {
    public int minFallingPathSum(int[][] matrix) {
        if(matrix == null || matrix.length == 0) {
            return 0;
        }

        int n = matrix.length;        
        int[] prev = new int[n], curr = new int[n];

        // base case
        for(int j = 0; j<n; j++) prev[j] = matrix[0][j];

        for(int j = 0; j<n; j++) {
            curr[j] = Integer.MAX_VALUE;
        }

        for(int i = 1; i<n; i++) {
            for(int j = 0; j<n; j++) {
                int top = prev[j];
                int leftDiag = (j >= 1) ? prev[j-1] : 
                                          Integer.MAX_VALUE;
                int rightDiag = (j < n-1) ? prev[j+1] : 
                                            Integer.MAX_VALUE;
                int diagMin = Math.min(leftDiag, rightDiag);

                curr[j] = matrix[i][j] + Math.min(diagMin, top);
            }
            prev = curr.clone();
        }

        int min = Integer.MAX_VALUE;
        for(int i = 0; i<n; i++) min = Math.min(min, prev[i]);
        return min;
    }
}
Time Complexity: O(n)
Space Complexity: O(1)


Conclusion

That's a wrap for today's problem. If you liked my explanation then please do drop a like/ comment. Also, please correct me if I've made any mistakes or if you want me to improve something!

Thank you for reading!