二维/矩阵类DP

矩阵类的题目能用DFS解答的都要想到memoization来优化,Tree则不能用memo优化

Maximal Square

  • 典型的矩阵类DP

  • 求最大的都包含1的matrix并返回面积

  • dp[i][j] means edge length of maximal square ending with matrix[i][j] as bottom-right corner

  • if matrix[i][j] == 1, state: dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1

class Solution {
    /*
    dp[i][j] means edge length of maximal square ending with matrix[i][j] as bottom-right corner
    if matrix[i][j] == 1 && matrix[i - 1][j] == 1 && matrix[i][j - 1] == 1, dp[i][j] = dp[i - 1][j - 1] + 1
    state: dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
    res:
    res = max(res, dp[i][j] * dp[i][j])
    */
    public int maximalSquare(char[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return 0;
        int rows = matrix.length, cols = matrix[0].length;
        int[][] dp = new int[rows][cols];
        int res = 0;
        //initialization
        for (int i = 0; i < rows; i++) {
            if (matrix[i][0] == '1') {
                dp[i][0] = 1;
                res = 1;
            }
        }
        for (int j = 0; j < cols; j++) {
            if (matrix[0][j] == '1') {
                dp[0][j] = 1;
                res = 1;
            }
        }
        for (int i = 1; i < rows; i++) {
            for (int j = 1; j < cols; j++) {
                if (matrix[i][j] == '1') {
                    dp[i][j] = Math.min(Math.min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1]) + 1;
                    res = Math.max(res, dp[i][j] * dp[i][j]);
                }
            }
        }
        return res;
    }
}

Count Square Submatrices with All Ones

  • maximal Square的升级版, 用的是同样的dp[i][j]的定义

  • dp[i][j] means edge length of maximal square ending with matrix[i][j] as bottom-right corner

  • 这里有个小trick, dp[i][j] also means number of squares with matrix[i][j] as bottom-right corner

  • 所以最后cnt = sum(dp[i][j])

class Solution {
    /*
    dp[i][j] means edge length of largest square with matrix[i][j] as bottom-right corner
    dp[i][j] also means number of squares with matrix[i][j] as bottom-right corner
    if matrix[i][j] == 1, state: dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
    res:
    cnt = sum(dp[i][j])
    */
    public int countSquares(int[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return 0;
        int rows = matrix.length, cols = matrix[0].length;
        int[][] dp = new int[rows][cols];
        int res = 0;
        //initialization
        for (int i = 0; i < rows; i++) {
            if (matrix[i][0] == 1) {
                dp[i][0] = 1;
                res += dp[i][0];
            }
        }
        for (int j = 0; j < cols; j++) {
            if (matrix[0][j] == 1) {
                dp[0][j] = 1;
                res += dp[0][j];
            }
        }
        if (matrix[0][0] == 1) res--;
        //state
        for (int i = 1; i < rows; i++) {
            for (int j = 1; j < cols; j++) {
                if (matrix[i][j] == 1) {
                    dp[i][j] = Math.min(Math.min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1]) + 1;
                    res += dp[i][j];
                }
            }
        }
        return res;
    }
}

Triangle

  • 二维matrix DP的入门题

  • 正常思维应该是首先想到DFS解法,traverse 或者D&C

  • 然后分析DFS的时间复杂度,发现是指数级,并且画一下过程图,发现可以用memo优化成多项式级别

  • 非常经典的题目来比较DFS, DFS with memo, DP的不同思路

  • DFS with memo是top-dwon, O(n^2) space

  • 可以用bottom-up DP来优化成O(n) space

class Solution {
    /*
    nums[x,y]
    nums[x+1, y], nums[x + 1, y + 1]
    可以看成矩阵类问题
    traverse the matrix 
    D&C: Time: O(2^n)
    D&C with memo Time: O(n^2), Space: O(n^2)
    DP: bottom-up, O(n) space
    */
    public int minimumTotal(List<List<Integer>> triangle) {
        //return minPathSum(triangle, 0, 0, new HashMap<>());
        return dp(triangle);
    }
    //traverse
    int min = Integer.MAX_VALUE;
    private void traverse(List<List<Integer>> triangle, int x, int y, int pathSum) {
        if (x == triangle.size()) {
            min = Math.min(min, pathSum);
            return;
        }
        traverse(triangle, x + 1, y, triangle.get(x).get(y) + pathSum);
        traverse(triangle, x + 1, y + 1, triangle.get(x).get(y) + pathSum);
    }
    
    //divide & conquer
    private int minPathSum(List<List<Integer>> triangle, int x, int y) {
        if (x == triangle.size()) {
            return 0;
        }
        int left = minPathSum(triangle, x + 1, y);
        int right = minPathSum(triangle, x + 1, y + 1);
        int pathSum = Math.min(left, right) + triangle.get(x).get(y);
        return pathSum;
    }
    
    //divide & conquer with memo
    //Time: O(n^2)
    //Space: O(n^2)
    private int minPathSum(List<List<Integer>> triangle, int x, int y, Map<Integer, Integer> memo) {
        if (x == triangle.size()) {
            return 0;
        }
        //fetch from memo
        int key = x * triangle.size() + y;
        if (memo.containsKey(key)) {
            return memo.get(key);
        }
        int left = minPathSum(triangle, x + 1, y, memo);
        int right = minPathSum(triangle, x + 1, y + 1, memo);
        int pathSum = Math.min(left, right) + triangle.get(x).get(y);
        memo.put(key, pathSum);
        return pathSum;
    }
    
    //bottom-up DP solutin, O(n) space
    //minSum[j] -> min(minSum[j], minSum[j + 1]) + triangle[i][j]
    //initialize for loop last row of triangle: minSum[j] == triangle[n - 1][j]
    //dp from second-to-last row, bottom up
    //return minSum[0]
    private int dp(List<List<Integer>> triangle) {
        if (triangle == null || triangle.size() == 0 || triangle.get(0).size() == 0) return 0;
        int n = triangle.size();
        int[] minSum = new int[n];
        for (int j = 0; j < n; j++) {
            minSum[j] = triangle.get(n - 1).get(j);
        }
        for (int i = n - 2; i >= 0; i--) {
            for (int j = 0; j <= i ; j++) {
                minSum[j] = Math.min(minSum[j], minSum[j + 1]) + triangle.get(i).get(j);
            }
        }
        return minSum[0];
    }
}

Minimum Path Sum

  • 最简单的二维坐标DP

  • 可以用滚动数组优化空间成O(min(n, m))

class Solution {
    /*
    brute force, DFS-> combination problem-> O(2^N)
    有方向性-> DP to improve
    m*n matrix problem, so need O(m*n) extra space
    
    state: f[i][j] means min path sum from grid[0][0] to grid[i][j]
    
    function: f[i][j] = min(f[i - 1][j], f[i][j -1]) + grid[i][j]
    
    result: return f[m - 1][n - 1]
    
    initial:
    f[0][0] = grid[0][0]
    f[0][j] = f[0][j - 1] + grid[0][j]
    f[i][0] = f[i - 1][0] + grid[i][0]
    
    bonus: rolling array to make space improvement
    */
    public int minPathSum(int[][] grid) {
        if (grid == null || grid.length == 0 || grid[0].length == 0) return 0;
        int m = grid.length, n = grid[0].length;
        int[][] minSum = new int[m][n];
        //intialize
        minSum[0][0] = grid[0][0];
        for (int i = 1; i < m ; i++) {
            minSum[i][0] = minSum[i - 1][0] + grid[i][0];
        }
        for (int j = 1; j < n; j++) {
            minSum[0][j] = minSum[0][j - 1] + grid[0][j];
        }
        //state
        for (int i = 1; i< m ; i++) {
            for (int j = 1; j < n; j++) {
                minSum[i][j] = Math.min(minSum[i - 1][j], minSum[i][j - 1]) + grid[i][j];
            }
        }
        //result
        return minSum[m - 1][n - 1];
    }
    
    private int minPathSumWithRollingArray(int[][] grid) {
        if (grid == null || grid.length == 0 || grid[0].length == 0) return 0;
        int m = grid.length, n = grid[0].length;
        int[][] minSum = new int[2][n];
        //intialize
        minSum[0][0] = grid[0][0];
        //state
        for (int i = 0; i< m ; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 && j == 0) continue;
                if (i == 0) {
                    minSum[i % 2][j] = minSum[i % 2][j - 1] + grid[i][j];
                    continue;
                }
                if (j == 0) {
                    minSum[i % 2][j] = minSum[(i - 1) % 2][j] + grid[i][j];
                    continue;
                }
                minSum[i % 2][j] = Math.min(minSum[(i - 1) % 2][j], minSum[i % 2][j - 1]) + grid[i][j];
            }
        }
        //result
        return minSum[(m - 1) % 2][n - 1];
    }
}

Last updated

Was this helpful?