LeetCode - 62、63. Unique Paths I - II、64. Minimum Path Sum

本文总结两道矩阵地图中行走的基础题,作为对DP算法的总结。

还有一道也是用二维矩阵表示地图,在里边计算路径数的题目是一道找工作的机试题,在我的另一篇博客中有记录,https://blog.csdn.net/Bob__yuan/article/details/82733954 。

引用一下百度百科里的说法:

    任何思想方法都有一定的局限性,超出了特定条件,它就失去了作用。同样,动态规划也并不是万能的。适用动态规划的问题必须满足最优化原理无后效性

  • 最优化原理(最优子结构性质):一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质
  • 无后效性将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。
  • 子问题的重叠性 动态规划将原来具有指数级时间复杂度的搜索算法改进成了具有多项式时间复杂度的算法。其中的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法

本篇文章的三道题目是比较基础,比较简单的三道Medium的题目,另有一篇包括它记录了矩阵中行走DP的题目的三道Hard题目 - https://blog.csdn.net/Bob__yuan/article/details/82762069

62. Unique Paths

A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below).

The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below).

How many possible unique paths are there?

解:

    前两道题其实不是严格意义的动态规划,因为没有求最优的路径,而是求的最多有多少不同的路径。但是其实思想是一样,就是由于从一个位置出发,只能向下或者向右走,这就很容易想到,从左上角到达矩阵中任意位置 m[i][j] 的不同路径数 dp[i][j] 只和这个位置的上边和左边有关,因为只有这两个位置能够直接到达 m[i][j],且上边位置就只能向下走,左边只能向右走,也就是各只有唯一能够到达 m[i][j] 的方式。这样我们就能够推出公式 dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 。用文字解释就是,从左上角到达矩阵中 (i, j) 位置的不同路径数等于从左上角到达矩阵中 (i - 1, j) 位置的不同路径数加上从左上角到达矩阵中 (i, j - 1) 位置的不同路径数。有了这个公式,再对初始值进行单独处理,就很容易写出代码,其实有bug,没有处理地图只有一行或者一列的情况。

int uniquePaths(int m, int n)
{
    vector<vector<int> > paths(m, vector<int> (n, 1));
    for(int i = 1; i < m; i++)
        for(int j = 1; j < n; j++)
            paths[i][j] = paths[i - 1][j] + paths[i][j - 1];
    return paths[m - 1][n - 1];
}

63. Unique Paths II

A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below).

The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below).

Now consider if some obstacles are added to the grids. How many unique paths would there be?

An obstacle and empty space is marked as 1 and 0 respectively in the grid.

解:

    这道题和上边题目的区别就是加入了障碍物,也就是矩阵中有一部分位置是不能遍历的,整体思路还是一点没有变,只不过在障碍物位置上,dp值变为了0。

int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid)
{
    int m = obstacleGrid.size(), n = obstacleGrid[0].size();
    
    vector<vector<int> > dp(m, vector<int>(n, 0)); // 全初始化为0,就不需要考虑障碍物的问题了
    if(obstacleGrid[0][0] == 1)
        return 0;
    else
        dp[0][0] = 1;
    for(int j = 1; j < n; j++)      // 初始化dp矩阵第一行
    {
        if(obstacleGrid[0][j] == 0)
            dp[0][j] = dp[0][j - 1];
        else
            dp[0][j] = 0;
    }
    for(int i = 1; i < m; i++)      // 初始化dp矩阵第一行
    {
        if(obstacleGrid[i][0] == 0)
            dp[i][0] = dp[i - 1][0];
        else
            dp[i][0] = 0;
    }
    for(int i = 1 ; i < m ; ++i)
        for(int j = 1 ; j < n ; ++j)
            if(obstacleGrid[i][j] == 0)
                dp[i][j] = (i - 1 >= 0 ? dp[i - 1][j] : 0) + (j - 1 >= 0 ? dp[i][j - 1] : 0);
    return dp[m - 1][n - 1];
}

    可以看到做了很多判断,而且对第一行第一列进行了单独的初始化,否则下标就越界了。这样非常麻烦,所以就诞生了一种简单版的dp矩阵,我们将创建dp矩阵的时候不创建为和原地图一样大,而是创建多一行多一列,dp矩阵中的 (i, j)对应的是原地图中的 (i - 1, j - 1),这样就不需要考虑下标越界的问题了,第一行第一列也不需要单独初始化了,直接用 dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 计算即可。dp矩阵中的第一行第一列是没有意义的,只是为了让计算变得简单。我们让 dp[0][1] 或者 dp[1][0] = 1,这样通过公式计算除了第一行第一列以外,每个位置的值就行了。相当于在原地图的最左侧,最上侧,各添加了一行,dp矩阵剩余部分还是和原地图一一对应。代码如下:

int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid)
{
    int m = obstacleGrid.size() , n = obstacleGrid[0].size();
    vector<vector<int> > dp(m + 1,vector<int>(n + 1, 0));
    dp[0][1] = 1;
    for(int i = 1 ; i <= m ; ++i)
        for(int j = 1 ; j <= n ; ++j)
            if(obstacleGrid[i - 1][j - 1] == 0)
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
    return dp[m][n];
}

64. Minimum Path Sum

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.

Note: You can only move either down or right at any point in time.

Input: [   [1,3,1], [1,5,1], [4,2,1] ]    Output: 7      Explanation: Because the path 1→3→1→1→1 minimizes the sum.

解:

    这道题就是正常的DP问题了,要在地图中计算在只能向下,向右走的情况下,从左上角走到右下角途径位置上的值的和最小是多少。思路和上边两道题是一样的,每个位置的dp值只和它上边以及它左边的dp值有关,上边两题算所有不同路径数,所以是dp[i][j] = dp[i - 1][j] + dp[i][j - 1],这道题要计算Sum最小的路径,所以 dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + m[i][j]

int minPathSum(vector<vector<int>>& grid)
{
    int m = grid.size(), n = grid[0].size();
    vector<vector<int> > dp(m + 1, vector<int>(n + 1, INT_MAX));
    dp[0][1] = 0;
    for(int i = 1; i <= m; i++)
        for(int j = 1; j <= n; j++)
            dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i - 1][j - 1];
    return dp[m][n];
}

    上述方式其实是计算并存储了从左上角到右下角的每个位置的路径最小Sum,但其实我们只需要知道最后一个位置的,理论上我们只需要存出一个值就行,不过因为每一个位置和它上边以及左边位置的值,所以需要记录一行的值,当我们计算第三行的位置上的dp值的时候,第0行和第1行的dp值其实是没有任何用的。 修改为只用一个数组存储一行的值并且更新的代码如下:

    重点其实就是 cur_path[j] = min(cur_path[j - 1], cur_path[j]) + grid[i][j]; 这一句,这里等号左边的 cur_path[j] 是我们要求的这一行的第 j 个位置的dp值,cur_path[j - 1] 是刚刚求出来的这一行的第 j 个位置左边位置的dp值,等号右边的 cur_path[j] 表示的是上一行第 j 个位置的dp值

int minPathSum(vector<vector<int>>& grid)
{
    int m = grid.size(), n = grid[0].size();
    vector<int> cur_path(n, 0);
    cur_path[0] = grid[0][0];
    for(int j = 1; j < n; j++)      // 第0行的dp值
        cur_path[j] = cur_path[j - 1] + grid[0][j]; 
    for(int i = 1; i < m; i++) {    // 每一行的dp值
        cur_path[0] += grid[i][0];
        for(int j = 1; j < n; j++)
            cur_path[j] = min(cur_path[j - 1], cur_path[j]) + grid[i][j];
    }
    return cur_path[n - 1];
}

猜你喜欢

转载自blog.csdn.net/Bob__yuan/article/details/82757994
今日推荐