LeetCode算法练习——动态规划提高(二)

这一部分,对已经做过的计数类型的二维dp问题进行总结。

LeetCode62. 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径?

示例 1:

输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右

示例 2:

输入: m = 7, n = 3
输出: 28

此题是一道典型的计数型动态规划,针对的是二维空间。如果机器人到达grid[i][j]的位置,由于它只能向右或者向左运动,则他的上一步坐标可能为grid[i - 1][j]或grid[i][j - 1],即上一步只存在两种可能性,那么最多路径呢则为右移和下移路径可能性之和。

我们令 dp[i][j] 是到达 i, j 最多路径,可以很容易得出动态方程:dp[i][j] = dp[i-1][j] + dp[i][j-1],注意,对于第一行dp[0][j],或者第一列dp[i][0],由于都是在边界,所以只能为1。

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m, vector<int>(n, 0));
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(i == 0 || j == 0)    dp[i][j] = 1;
                else    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[m - 1][n - 1];
    }
};

LeetCode63. 不同路径 II 与本题相似,增加了障碍物的条件,代码如下:

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

LeetCode剑指 Offer 47. 礼物的最大价值

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

示例 1:

输入: 
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物

此题为上一题的变形,需要统计所过路径的最大礼物价值。思路同上,在到达grid[i][j]的位置时,由于它只能向右或者向左运动,则他的上一步坐标可能为grid[i - 1][j]或grid[i][j - 1],即上一步只存在两种可能性,由于计算最大值,我们则需要取这两种路径移动的礼物最大值加上当前位置的礼物价值,作为该位置移动路径的礼物最大值。

即得到状态方程:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j],并将实时更新路径移动的最大价值,这里我们需要注意初始化i = 0, j = 0的情况,即第一行和第一列状态的初始化问题。

class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        int row = grid.size();
        int column = grid[0].size();
        int ans = INT_MIN;
        vector<vector<int>> dp(row, vector<int>(column, 0));
        for(int i = 0; i < row; i++){
            for(int j = 0; j < column; j++){
                if(i == 0 && j == 0)    dp[i][j] = grid[i][j];
                else if(i == 0)  dp[i][j] = dp[i][j - 1] + grid[i][j];  //初始横向移动
                else if(j == 0)  dp[i][j] = dp[i - 1][j] + grid[i][j];  //初始纵向移动
                //上一步是横向移动和纵向移动得到的最大路径:max(dp[i - 1][j], dp[i][j - 1])
                else    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
                ans = max(ans, dp[i][j]);
            }
        }
        return ans;
    }
};

LeetCode72. 编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。你可以对一个单词进行如下三种操作:

  •     插入一个字符
  •     删除一个字符
  •     替换一个字符
示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

动态规划思路:

看到这道题,我们很自然知道有3个选择,可以替换、删除或者增加字符。但是怎么利用呢?

将大问题拆分为小问题,即面对word1的前i个要变为word2的前j个字符时,我们需要消耗多少步呢?

替换的情况:当word1中的前i-1个就可以变换为word2中的前j-1个,那么我们只要根据word1中的第i个是否等于word2中的第j个字符进行判断,如果相等,那么dp[i][j]=dp[i - 1][j - 1];否则,dp[i][j]=dp[i - 1][j - 1] + 1,加的1就是我们将word1中第i个字符替换为word2中第j个的消耗。

删除的情况:当word1中的前i - 1个就可以变换为word2中的前j个时,我们需要将word1中的第i个字符删除,dp[i][j]=dp[i - 1][j]+1

增加的情况:当word1中的前i个可以变换为word2中的前j - 1个时,我们需要将word1中的第i个字符后面增加一个,dp[i][j]=dp[i][j - 1]+1

所以,我们的dp[i][j]取上列的最小值即可:dp[i][j] = min(dp[i - 1][j], min(dp[i][j - 1], dp[i - 1][j - 1])) + 1

ps:注意,我们的dp数组索引均是从1开始,而word中索引是0开始的,所以word1[i - 1] == word2[j - 1],就是在判断我们word1中的第i个字符是否等于word2中第j个字符。即:dp[i][j] = min(dp[i][j], dp[i - 1][j - 1])

示例的演示过程如下:

class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.size();
        int n = word2.size();
        // 有一个字符串为空串
        if (n * m == 0) return n + m;
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
        for(int i = 0; i < m + 1; i++)
            dp[i][0] = i;
        for(int j = 0; j < n + 1; j++)
            dp[0][j] = j;
        for(int i = 1; i < m + 1; i++){
            for(int j = 1; j < n + 1; j++){
                int del = dp[i - 1][j];         //增
                int add = dp[i][j - 1];         //删
                int mdf = dp[i - 1][j - 1];     //改
                dp[i][j] = min(dp[i - 1][j], min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
                if(word1[i - 1] == word2[j - 1])
                    dp[i][j] = min(dp[i][j], dp[i - 1][j - 1]);
            }
        }
        return dp[m][n];
    }
};

LeetCode221. 最大正方形

在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。

示例:

输入: 

1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0

输出: 4

此题中,我们需要求得正方形的最大边长即可,思路如下:

用 dp(i,j) 表示以 (i,j) 为右下角,且只包含 1 的正方形的边长最大值。如果我们能计算出所有 dp(i,j) 的值,那么其中的最大值即为矩阵中只包含 1 的正方形的边长最大值,其平方即为最大正方形的面积。

那么如何计算 dp 中的每个元素值呢?对于每个位置 (i, j),检查在矩阵中该位置的值:

  • 如果该位置的值是 0,则 dp(i, j)=0,因为当前位置不可能在由 1 组成的正方形中;
  • 如果该位置的值是 1,则 dp(i, j) 的值由其上方、左方和左上方的三个相邻位置的 dp 值决定。具体而言,当前位置的元素值等于三个相邻位置的元素中的最小值加 1,状态转移方程如下:dp(i, j)=min(dp(i − 1, j),dp(i − 1, j − 1),dp(i, j − 1)) + 1

 此外,还需要考虑边界条件。如果 i 和 j 中至少有一个为 0,则以位置 (i,j) 为右下角的最大正方形的边长只能是 1,因此 dp(i,j)=1。

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        if(matrix.size() == 0 || matrix[0].size() == 0) return 0;
        int height = matrix.size();
        int width = matrix[0].size();
        int max = 0;
        vector<vector<int>> dp(height + 1, vector<int>(width + 1, 0));
        for(int i = 1; i <= height; i++){
            for(int j = 1; j <= width; j++){
                if(matrix[i - 1][j - 1] == '0')    dp[i][j] = 0;
                else{
                    dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
                    if(max <= dp[i][j]) max = dp[i][j];
                }
            }
        }
        return max * max;
    }
};

LeetCode1277. 统计全为 1 的正方形子矩阵

给你一个 m * n 的矩阵,矩阵中的元素不是 0 就是 1,请你统计并返回其中完全由 1 组成的 正方形 子矩阵的个数。

示例 1:

输入:matrix =
[
  [0,1,1,1],
  [1,1,1,1],
  [0,1,1,1]
]
输出:15
解释: 
边长为 1 的正方形有 10 个。
边长为 2 的正方形有 4 个。
边长为 3 的正方形有 1 个。
正方形的总数 = 10 + 4 + 1 = 15.

思路和上一题类似,当正方形边长为1时我们需要计数,不为1的情况极为上一题的特殊情况,最后进行累加可得答案。

class Solution {
public:
    int countSquares(vector<vector<int>>& matrix) {
        if(matrix.size() == 0 || matrix[0].size() == 0) return 0;
        int res = 0;
        vector<vector<int>> dp(matrix.size(), vector<int>(matrix[0].size(), 0));
        for(int i = 0; i < matrix.size(); i++){
            for(int j = 0; j < matrix[0].size(); j++){
                if (i == 0 || j == 0) 
                    dp[i][j] = matrix[i][j];
                else if (matrix[i][j] == 0)
                    dp[i][j] = 0;
                else
                    dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
                res += dp[i][j];
            }
        }
        return res;
    }
};

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值