每日刷题计划Day4-动态规划

题源:LeetCode

931. 下降路径最小和

给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径 的 最小和 。

下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)、(row + 1, col) 或者 (row + 1, col + 1) 。

示例 1:
输入:matrix = [[2,1,3],[6,5,4],[7,8,9]]
输出:13
解释:下面是两条和最小的下降路径,用加粗+斜体标注:
[[2,1,3], [[2,1,3],
[6,5,4], [6,5,4],
[7,8,9]] [7,8,9]]

示例 2:
输入:matrix = [[-19,57],[-40,-5]]
输出:-59
解释:下面是一条和最小的下降路径,用加粗+斜体标注:
[[-19,57],
[-40,-5]]

示例 3:
输入:matrix = [[-48]]
输出:-48

提示:
1 <= matrix[i].length <= 100
-100 <= matrix[i][j] <= 100

class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& matrix) {
        vector<vector<int> > dp(matrix.size(), vector<int>(matrix.size()));
        //dp数组表示:走到[i][j]的最小路径和
        for(int i = 0; i < matrix.size(); i++){
            dp[0][i] = matrix[0][i];
        }
        
        for(int i = 1; i < matrix.size();i++){
            for(int j = 0; j < matrix.size(); j++){
            //一开始边界条件没有考虑到
                if(j == 0) dp[i][j] = min(dp[i-1][j], dp[i-1][j+1]) + matrix[i][j];
                else if(j == matrix.size()-1) dp[i][j] = min(dp[i-1][j], dp[i-1][j-1]) + matrix[i][j];
                else dp[i][j] = min({dp[i-1][j], dp[i-1][j-1], dp[i-1][j+1]}) + matrix[i][j];
            }
        }
        
        int sum = dp[matrix.size()-1][0];
        for(int i = 1; i < matrix.size(); i++){
            sum = min(sum,dp[matrix.size()-1][i]);
        }
        
        return sum;
    }
};

还可以不用dp数组。

1143. 最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:
输入:text1 = “abcde”, text2 = “ace”
输出:3
解释:最长公共子序列是 “ace” ,它的长度为 3 。

示例 2:
输入:text1 = “abc”, text2 = “abc”
输出:3
解释:最长公共子序列是 “abc” ,它的长度为 3 。

示例 3:
输入:text1 = “abc”, text2 = “def”
输出:0
解释:两个字符串没有公共子序列,返回 0 。

提示:
1 <= text1.length, text2.length <= 1000
text1 和 text2 仅由小写英文字符组成。

对于两个字符串求子序列的问题,都是用两个指针i和j分别在两个字符串上移动,大概率是动态规划思路。

解题思路

首先,区分两个概念:子序列可以是不连续的;子数组(子字符串)需要是连续的;
另外,动态规划也是有套路的:单个数组或者字符串要用动态规划时,可以把动态规划 dp[i] 定义为 nums[0:i] 中想要求的结果;当两个数组或者字符串要用动态规划时,可以把动态规划定义成两维的 dp[i][j] ,其含义是在 A[0:i] 与 B[0:j] 之间匹配得到的想要的结果。

  1. 状态定义
    比如对于本题而言,可以定义 dp[i][j] 表示 text1[0:i-1] 和 text2[0:j-1] 的最长公共子序列。 (注:text1[0:i-1] 表示的是 text1 的 第 0 个元素到第 i - 1 个元素,两端都包含)
    之所以 dp[i][j] 的定义不是 text1[0:i] 和 text2[0:j] ,是为了方便当 i = 0 或者 j = 0 的时候,dp[i][j]表示的为空字符串和另外一个字符串的匹配,这样 dp[i][j] 可以初始化为 0.

  2. 状态转移方程
    知道状态定义之后,我们开始写状态转移方程。
    当 text1[i - 1] == text2[j - 1] 时,说明两个子字符串的最后一位相等,所以最长公共子序列又增加了 1,所以 dp[i][j] = dp[i - 1][j - 1] + 1。
    综上状态转移方程为:
    dp[i][j] = dp[i - 1][j - 1] + 1, 当 text1[i - 1] == text2[j - 1];
    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]), 当 text1[i - 1] != text2[j - 1]

  3. 状态的初始化
    初始化就是要看当 i = 0 与 j = 0 时, dp[i][j] 应该取值为多少。
    当 i = 0 时,dp[0][j] 表示的是 text1 中取空字符串 跟 text2 的最长公共子序列,结果肯定为 0.
    当 j = 0 时,dp[i][0] 表示的是 text2 中取空字符串 跟 text1 的最长公共子序列,结果肯定为 0.
    综上,当 i = 0 或者 j = 0 时,dp[i][j] 初始化为 0.

  4. 遍历方向与范围
    由于 dp[i][j] 依赖与 dp[i - 1][j - 1] , dp[i - 1][j], dp[i][j - 1],所以 i 和 j 的遍历顺序肯定是从小到大的。
    另外,由于当 i 和 j 取值为 0 的时候,dp[i][j] = 0,而 dp 数组本身初始化就是为 0,所以,直接让 ii 和 jj 从 1 开始遍历。遍历的结束应该是字符串的长度为 len(text1) 和 len(text2)。

  5. 最终返回结果
    由于 dp[i][j] 的含义是 text1[0:i-1] 和 text2[0:j-1] 的最长公共子序列。我们最终希望求的是 text1 和 text2 的最长公共子序列。所以需要返回的结果是 i = len(text1) 并且 j = len(text2) 时的 dp[len(text1)][len(text2)]。

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int m = text1.length(), n = text2.length();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        for (int i = 1; i <= m; i++) {
            char c1 = text1.at(i - 1);
            for (int j = 1; j <= n; j++) {
                char c2 = text2.at(j - 1);
                if (c1 == c2) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[m][n];
    }
};

583. 两个字符串的删除操作

给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。

示例:
输入: “sea”, “eat”
输出: 2
解释: 第一步将"sea"变为"ea",第二步将"eat"变为"ea"

提示:
给定单词的长度不超过500。
给定单词中的字符只含有小写字母。

方法一:
思路:最长公共子序列的变形。

class Solution {
public:
    int minDistance(string word1, string word2) {
        int n = word1.size(), m = word2.size();
        vector< vector<int> > dp((n+1), vector<int>(m + 1));
        dp[0][0] = 0;

        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                if(word1[i-1] == word2[j-1]){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else{
                    dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
                }
                
            }
        }
        int res = dp[n][m];
        return n + m - 2 * res;
    }
};

注意最后的返回值,考虑到n与m的大小关系对结果的影响。
我的error code:return n - res;

方法二:动态规划
也可以直接使用动态规划计算最少删除操作次数,不需要计算最长公共子序列的长度。

p[i][j]:假设字符串word1 和 word2 的长度分别为m和n,创建m+1行n+1列的二维数组dp,其中dp[i][j]表示使word1[0:i]和word2[0:j]相同的最少删除操作次数。

边界:dp[ 0 ][ j ]= j; dp[ i ][ j ] = i;

当i > 0 且 j > 0时,考虑dp[i][j]的计算:

  1. 当word1[ i - 1 ] = word2[ j - 2 ]时,将这两个相同的字符称为公共字符,考虑使word1[ 0: i - 1 ]和word2[0 : j-1 ] 相同的最少删除操作次数,增加一个公共字符之后,最少删除操作次数不变,因此dp[i][j] = dp[ i - 1] [ j - 1 ]。
  2. 当word1[ i - 1 ] ≠ word2[ j - 2 ]时,
    A. 使word1[0:i−1] 和 word2 ​ [0:j] 相同的最少删除操作次数,加上删除 word1[ i-1 ]的1次操作;
    B. 使word1[0:i] 和 word2[0:j-1] 相同的最少删除操作次数,加上删除word2[ j-1 ]的1次操作。

在这里插入图片描述

class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.size();
        int n = word2.size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));

        for (int i = 1; i <= m; i++) {
            dp[i][0] = i;
        }
        for (int j = 1; j <= n; j++) {
            dp[0][j] = j;
        }
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (word1[i - 1] == word2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1;
                }
            }
        }

        return dp[m][n];
    }
};

712. 两个字符串的最小ASCII删除和

给定两个字符串s1, s2,找到使两个字符串相等所需删除字符的ASCII值的最小和。

示例 1:
输入: s1 = “sea”, s2 = “eat”
输出: 231
解释: 在 “sea” 中删除 “s” 并将 “s” 的值(115)加入总和。
在 “eat” 中删除 “t” 并将 116 加入总和。
结束时,两个字符串相等,115 + 116 = 231 就是符合条件的最小和。

示例 2:
输入: s1 = “delete”, s2 = “leet”
输出: 403
解释: 在 “delete” 中删除 “dee” 字符串变成 “let”,
将 100[d]+101[e]+101[e] 加入总和。在 “leet” 中删除 “e” 将 101[e] 加入总和。
结束时,两个字符串都等于 “let”,结果即为 100+101+101+101 = 403 。
如果改为将两个字符串转换为 “lee” 或 “eet”,我们会得到 433 或 417 的结果,比答案更大。

注意: 0 < s1.length, s2.length <= 1000。 所有字符串中的字符ASCII值在[97, 122]之间。

思路:表里存值。dp[ i ][ j ]表示word1[0:i-1]和word2[0:j-1]公共子序列删除字符和最少的值。

class Solution {
public:
    int minimumDeleteSum(string s1, string s2) {
        int n = s1.size();
        int m = s2.size(); 
        vector<vector<int> > dp(n+1, vector<int>(m+1));

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

        for(int i = 1; i < n+1; i++){
            for(int j = 1; j < m+1; j++){
                if(s1[i-1] == s2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                }else{
                    dp[i][j] = min(int(s1[i-1])+dp[i-1][j], int(s2[j-1])+dp[i][j-1]);
                }
            }
        }
        return dp[n][m];
    }
};

错误:

第九行,error code: dp[i][0] += int(s1[i-1]);

第八行和第九行,error code:

for(int i = 0; i < n; i++)
            dp[i][0] = int(s1[i]);

子序列问题

定义 dp 数组,找状态转移关系的两种模板。

一维的 dp 数组:

int n = array.size();
vector<int> dp(n);
//base case
//……
for (int i = 1; i < n; i++) {
    for (int j = 0; j < i; j++) {
        dp[i] = 最值(dp[i], dp[j] + ...)
    }
}

二维的 dp 数组

int n = array.size();
vector<vector<int> > dp(n,vector<int>(n));
//base case
//……

for (int i = 1; i < n; i++) {
    for (int j = 1; j < n; j++) {
        if (array[i] == array[j]) 
            dp[i][j] = dp[i-1][j-1] + ...
        else
            dp[i][j] = 最值(...)
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值