LeetCode64. 最小路径和

题目概述:

在这里插入图片描述
题目链接:点我做题

题解

  这个题不同于其他动态规划的关键在在于边界条件。
  定义f(i,j)为从左上角到下标为 ( i , j ) (i,j) (i,j)的位置所需的最小路径和。
  当 i = = 0 i == 0 i==0,也就是说在第0行时,只能通过起始位置往右走,所以 f ( 0 , j ) = ∑ k = 0 j g r i d [ 0 ] [ k ] f(0,j) = \sum_{k=0}^{j}{grid[0][k]} f(0,j)=k=0jgrid[0][k],而不是像普通的矩阵动态规划那样 f ( 0 , j ) = g r i d [ 0 ] [ j ] f(0,j) = grid[0][j] f(0,j)=grid[0][j]
  同理,当 j = = 0 j==0 j==0时,也就是说在第0列时,只能从起始位置往下走才能到达,所以 f ( i , o ) = ∑ k = 0 i g r i d [ k ] [ 0 ] f(i,o) = \sum_{k=0}^{i}{grid[k][0]} f(i,o)=k=0igrid[k][0],而不是像普通的矩阵动态规划那样 f ( i , 0 ) = g r i d [ i ] [ 0 ] f(i,0) = grid[i][0] f(i,0)=grid[i][0]
  其他情况下,到达 ( i , j ) (i,j) (i,j)要么是从 ( i − 1 , j ) (i-1,j) (i1,j)向右走一步过来的,要么是从 ( i , j − 1 ) (i,j-1) (i,j1)向下走一步过来的,到达 ( i , j ) (i,j) (i,j)的最短路径是到达(i-1,j)的最短路径与到达 ( i , j − 1 ) (i,j-1) (i,j1)的最短路径中的较小值与 g r i d [ i ] [ j ] grid[i][j] grid[i][j]的和,状态转移方程为:
f ( i , j ) = m i n ( f ( i − 1 , j ) , f ( i , j − 1 ) ) + g r i d [ i ] [ j ] f(i,j)=min(f(i-1,j), f(i,j-1))+grid[i][j] f(i,j)=min(f(i1,j),f(i,j1))+grid[i][j]
符合最优子结构,所以可以使用动态规划,代码如下:

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) 
    {
        int rows = grid.size();
        int cols = grid[0].size();
        vector<vector<int>> dp(rows, vector<int>(cols));
        //dp表示到达这个格子的最小路径
        int sum1 = 0;
        for (int j = 0; j < cols; ++j)
        {
            //第一行的格子都只能是往右走过来的 不可能是其他路径过来的
            sum1 += grid[0][j];
            dp[0][j] = sum1;
        }
        int sum2 = 0;
        for (int i = 0; i < rows; ++i)
        {
            //同理 第一列的格子只能是从左上角往下走才能到达
            //不可能是其他路径过来的
            sum2 += grid[i][0];
            dp[i][0] = sum2;
        }
        for (int i = 1; i < rows; ++i)
        {
            for (int j = 1; j < cols; ++j)
            {
                dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            }
        }
        return dp[rows - 1][cols - 1];
    }
};

时间复杂度: O ( m n ) O(mn) O(mn)
空间复杂度: O ( m n ) O(mn) O(mn)
  注意到,如果先遍历行再遍历列,本题的状态转移方程仅和上一轮外层循环,即前一行的状态,以及遍历列时上一次的计算结果有关;
  同理,如果先遍历列再遍历行,本题的状态转义方程仅和上一轮的外层循环,即前一列的状态,以及遍历行是上一次的计算结果有关;
  所以我们可以先比较行数和列数的大小,如果行数小,就在内层循环遍历行,开辟一个数组 d p dp dp来储存上一轮外层遍历,即在上一列时的行遍历结果;如果列数小,就在内层循环遍历列,开闭一个数组 d p dp dp来储存上一轮的外层遍历结果,即在上一行时的列遍历结果。这样空间复杂度就可以优化到 O ( m i n ( m , n ) ) O(min(m,n)) O(min(m,n))
  注意控制边界条件,如果是行数等于0,那么此时的这一行上每一列对应的最短路径仅和前一列的值以及 g r i d [ i ] [ j ] grid[i][j] grid[i][j]有关;如果列数等于0,那么此时这一列上每一行的对应对端路径仅和前一行的值以及 g r i d [ i ] [ j ] grid[i][j] grid[i][j]有关,需要注意的是储存行状态时, d p [ i − 1 ] dp[i - 1] dp[i1]等价与 f ( i − 1 , j ) f(i-1,j) f(i1,j) d p [ i ] dp[i] dp[i]等价于 f ( i , j − 1 ) f(i,j-1) f(i,j1);储存列状态时, d p [ j ] dp[j] dp[j]等价于 f ( i − 1 , j ) f(i-1,j) f(i1,j) d p [ j − 1 ] dp[j-1] dp[j1]等价于 f ( i , j − 1 ) f(i,j-1) f(i,j1),注意据此控制边界条件。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) 
    {
        int rows = grid.size();
        int cols = grid[0].size();
        int ret = 0;
        if (rows > cols)
        {
            vector<int> dp(cols);
            //如果cols小 就把它放在内层循环
            for (int i = 0; i < rows; ++i)
            {
                for (int j = 0; j < cols; ++j)
                {
                    //dp[j]相当于f(i - 1, j)
                    //dp[j - 1]相当于f(i, j - 1)
                    if (i == 0 && j == 0)
                    {
                        //首个元素
                        dp[j] = grid[i][j];
                    }
                    else if (i == 0)
                    {
                        //如果i==0 表明在第一行
                        //只能是和前继求和 即dp[j - 1]
                        dp[j] = dp[j - 1] + grid[i][j];
                    }
                    else if (j == 0)
                    {
                        //如果j==0 表明是在第1列
                        //只和上一层的有关
                        //所以和dp[j]求和
                        dp[j] = dp[j] + grid[i][j];
                    }
                    else
                    {
                        //否则 f(i-1, j)和f(i,j-1)中取小
                        dp[j] = min(dp[j], dp[j - 1]) + grid[i][j];
                    }
                }
            }
            ret = dp[cols - 1];
        }
        else
        {
            vector<int> dp(rows);
            //如果rows小 就把rows放在内层循环
            for (int j = 0; j < cols; ++j)
            {
                for (int i = 0; i < rows; ++i)
                {
                    //dp[i]相当于f(i, j - 1)
                    //dp[i - 1]相当于f(i - 1, j)
                    if (i == 0 && j == 0)
                    {
                        //起始位置
                        dp[i] = grid[i][j];
                    }
                    else if (j == 0)
                    {
                        //如果在第1列
                        //只和第1列前一行的数有关 即f(i - 1, j)
                        //即dp[i - 1]
                        dp[i] = dp[i - 1] + grid[i][j];
                    }
                    else if (i == 0)
                    {
                        //如果在第1行
                        //只和第一行前一列的数有关 即f(i,j - 1)
                        //即dp[i]
                        dp[i] = dp[i] + grid[i][j];
                    }
                    else
                    {
                        //否则 f(i - 1,j) f(i, j - 1)取小
                        dp[i] = min(dp[i - 1], dp[i]) + grid[i][j];
                    }
                }
            }
            ret = dp[rows - 1];
        }
        return ret;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值