剑指Offer题目笔记29(动态规划矩阵路径问题)

面试题98:

面试题98

问题:

​ 一个机器人从m x n的格子的左上角出发,它每一步只能向下走或者向右走,计算机器人从左上角到达右下角的路径数量。

解决方案:
  1. 机器人每走一步都有两个选择,要么向下走要么向右走。一个任务需要多个步骤才能完成,每步面临若干选择,这类问题看起来可以用回溯法解决,但由于这个题目只要求计算从左上角到达右下角的路径的数目,并没有要求列出所有的路径,因此这个问题更适合用动态规划解决。
  2. 当i等于0或者j等于0时,机器人位于格子最左边的一列,机器人走到左下角或者右上角只能走直线,故f(0,0)走到f(i,0)或者f(0,j)都是只有一条路径。
  3. 当行号i、列号j都大于0时,机器人有两种方法可以到达坐标为(i,j)的位置。它既可以从坐标为(i-1,j)的位置向下走一步,也可以从坐标为(i,j-1)的位置向右走一步,因此,f(i,j)等于 f(i-1,j)与f(i,j-1)之和。
源代码(递归):
class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];
        return dfs(m-1,n-1,dp);
    }

    private int dfs(int i,int j,int[][] dp){
        if(dp[i][j] == 0){
            if(i == 0 || j == 0){
                dp[i][j] = 1;
            }else{
                dp[i][j] = dfs(i-1,j,dp) + dfs(i,j-1,dp);
            }
        }

        return dp[i][j];
    }
}
源代码(迭代):
class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];
        dp[0][0] = 0;

        for(int j = 0;j < n;j++){
            dp[0][j] = 1;
        }

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

        return dp[m-1][n-1];
    }
}
优化空间效率思路:

​ 在计算f(i,j)时需要用到f(i-1,j)和f(i,j-1)的值。接下来在计算f(i,j+1)时需要用到f(i-1,j+1)和f(i,j)的值。由于在用f(i-1,j)计算出f(i,j)之后就不再需要f(i-1,j),因此可以只用一个位置来保存f(i-1,j)和f(i,j)的值。

源代码:
class Solution {
    public int uniquePaths(int m, int n) {
        int[] dp = new int[n];
        Arrays.fill(dp,1);

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

        return dp[n-1];
    }
}

面试题99:

面试题99

问题:

​ 在一个m x n的格子中,每个位置都有一个数字。机器人每步只能向下或向右,计算它从格子左上角到右下角的路径数字之和的最小值。

解决方案:
  1. 机器人每一步都有两种选择,要么向下走,要么向右走,但是题目并没有让我们计算出所有的路径,而是让我们计算路径的最小数字之和,故使用动态规划。
  2. 用函数f(i,j)表示从格子的左上角坐标为(0,0)的位置(用grid[0][0]表示)出发到达坐标为(i,j)的位置(用grid[i][j]表示)的路径的数字之和的最小值。如果格子的大小为m×n,那么f(m-1,n-1)就是问题的解。
  3. 当i等于0或者j等于0时,机器人位于格子最上面的一行,机器人只能向下或者向右,如果它想去左下角或者右上角,那么它只能走直线,f(i,0)就等于0到i的数字之和,f(0,j)就等于0到j的数字之和。
  4. 当行号i、列号j都大于0时,机器人有两种方法可以到达坐标为(i,j)的位置。它既可以从坐标为(i-1,j)的位置向下走一步,也可以从坐标为(i,j-1)的位置向右走一步,因此,f(i,j)等于 f(i-1,j)与f(i,j-1)的最小值加上grid[i][j]。
源代码:
class Solution {
    public int minPathSum(int[][] grid) {
        int len1 = grid.length;
        int len2 = grid[0].length;

        int[][] dp = new int[len1][len2];
        dp[0][0] = grid[0][0];

        for(int j = 1;j < len2;j++){
            dp[0][j] = dp[0][j-1] + grid[0][j];
        }

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

        return dp[len1-1][len2-1];
    }
}
优化空间效率思路:

​ 在计算f(i,j)时需要f(i-1,j)、f(i,j-1)的值。值得注意的是,f(i-1,j)在完成f(i,j)的计算之后再也用不到了,因此将f(i-1,j)和f(i,j)保存到同一个数组dp的同一个位置“dp[j]”中。而f(i,j-1)在计算f(i,j)之前就已经计算好了。

源代码:
class Solution {
    public int minPathSum(int[][] grid) {
        int len1 = grid.length;
        int len2 = grid[0].length;

        int[] dp = new int[len2];
        dp[0] = grid[0][0];

        for(int j = 1;j < len2;j++){
            dp[j] = dp[j-1] + grid[0][j];
        }

        for(int i = 1;i < len1;i++){
            //注意:这里与上题不一样,因为上题是求路径数目,dp【i】与dp【j】相同都是1,使用上题不需要这个步骤。而这题不同,求的是路径和,故需要进行grid[i][0]的累加操作。
            dp[0] += grid[i][0];
            for(int j = 1;j < len2;j++){
                dp[j] = Math.min(dp[j],dp[j-1]) + grid[i][j];
            }
        }

        return dp[len2-1];
    }
}

面试题100:

面试题100

问题:

​ 在一个由数字组成的三角形中,第1个行有1个数字,第2行有2个数字,以此类推,第n行有n个数字。每步只能前往下一行中相邻的数字,计算从三角形顶部到底部的路径经过的数字之和的最小值。

解决方案:
  1. 如果一个三角形有多行,那么从它的顶部到底部需要多步,而且每步都面临两个选择。但是题目没要求我们写出从三角形顶部到底部的全部路径,而是让我们求三角形顶部到底部的最小路径之和,故使用动态规划解决该问题。
  2. 可以用f(i,j)表示从三角形的顶部出发到达行号和列号分别为i和j(i≥j)的位置时路径数字之和的最小值,同时用T[i][j]表示三角形行号和列号分别为i和j的数字。如果三角形中包含n行数字,那么 f(n-1,j)的最小值就是整个问题的最优解。
  3. 如果j等于0,也就是当前到达某行的第1个数字。由于路径的每步都是前往正下方或右下方的数字,而此时当前位置的左上方没有数字,那么前一步是一定来自它的正上方的数字,因此f(i,0)等于 f(i-1,0)与T[i][0]之和。如果i等于j,也就是当前到达某行的最后一个数字,此时它的正上方没有数字,前一步只能是来自它左上方的数字,因此f(i,i)等于f(i-1,i-1)与T[i][i]之和。
  4. 如果当前行号和列号分别为i和j的位置位于某行的中间,那么前一步既可能是来自它正上方的数字(行号和列号分别为i-1和j),也可能是来自它左上方的数字(行号和列号分别为i-1和j-1),所以 f(i,j)等于f(i-1,j)与f(i-1,j-1)的最小值再加上T[i][j]。
源代码:
class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int len = triangle.size();
        int[][] dp = new int[len][len];
        dp[0][0] = triangle.get(0).get(0);

        for(int i = 1;i < len;i++){
            dp[i][0] = dp[i-1][0] + triangle.get(i).get(0);
        }

        for(int i = 1;i < len;i++){
            for(int j = 1;j <= i;j++){
                if(j == i){
                    dp[i][j] = dp[i-1][j-1] + triangle.get(i).get(j);
                }else{
                    dp[i][j] = Math.min(dp[i-1][j],dp[i-1][j-1]) + triangle.get(i).get(j);
                }
            }
        }
        int min = Integer.MAX_VALUE;
        for(int j = 0;j < len;j++){
            min = Math.min(min,dp[len-1][j]);
        }
        return min;
    }
}
优化空间效率思路:
  1. 在计算f(i,j)之前“dp[j]”中保存的是f(i-1,j)的值。在计算f(i,j)时需要f(i-1,j-1)和f(i-1,j)。在计算完 f(i,j)之后能否用f(i,j)的值覆盖保存在“dp[j]”中的f(i-1,j)取决于是否还需要f(i-1,j)的值。
  2. 如果从左到右计算的话,计算f(i,j)后会覆盖f(i-1,j),但是后面计算f(i,j+1)的时候还需要用到f(i-1,j),由于计算f(i,j)时并不依赖同一行左侧的f(i,j-1),所以我们可以按照从右到左的顺序计算。
源代码:
class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int len = triangle.size();
        int[] dp = new int[len];
        dp[0] = triangle.get(0).get(0);

        for(int i = 1;i < len;i++){
            for(int j = i;j >= 0;j--){
                if(j == 0){
                    dp[j] += triangle.get(i).get(j);
                }else if(j == i){
                    dp[j] = dp[j-1] + triangle.get(i).get(j);
                }else{
                    dp[j] = Math.min(dp[j],dp[j-1]) + triangle.get(i).get(j);
                }
            }
        }

        int min = Integer.MAX_VALUE;
        for(int j = 0;j < len;j++){
            min = Math.min(min,dp[j]);
        }
        return min;
    }
}
  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值