代码随想录算法训练营打卡Day53 | LeetCode1143 最长公共子序列、LeetCode1035 不相交的线、LeetCode53 最大子数组和

摘要

  • 如果不要求子序列中的元素在原序列中连续,相比于要求“连续”,dp数组的定义和递推公式是不一样的。
  • 如果要求“连续”,那dp数组定义为以具体的元素结尾;如果不要求连续,那么dp数组定义为尝试到对应元素。

LeetCode1143 最长公共子序列

1143. 最长公共子序列 - 力扣(Leetcode)

  • 代码随想录算法训练营打卡Day52中的最后一题,简单地分析了一下如何通过dp数组的定义和递推公式来保证子序列中的元素在原序列中连续,那不要求“连续”要怎么做呢?这就是本题的要求。

  • 本题只要求子序列中的元素保持在原序列中的相对顺序,不要求连续分布。

  • dp数组及数组下标的含义:dp[i][j]表示,text1的子序列中的元素的下标属于[0, i-1]text2的子序列中的元素的下标属于[0, j-1],这两个子序列的公共部分的长度为dp[i][j]

    • 可以这么理解,就是尝试到了text1[i-1]text2[j-1]的子序列,可以选择text1[i-1]text2[j-1]的子序列,但text1[i-1]text2[j-1]不一定是子序列的结尾。
  • 那么递推公式也好理解了,dp[i][j]有三种更新的可能,dp[i][j]的更新对应着当前比对的元素为text1[i-1]text2[j-1]

    • 当前尝试到text1[i-1],如果不选text1[i-1],那么dp[i][j]=dp[i-1][j]

    • 当前尝试到text2[j-1],如果不选text2[j-1],那么dp[i][j]=dp[i][j-1]

      • 以上两种情况相当于``text1[i-1] != text2[j-1],公共子序列的长度没有增加,这两种情况组合起来其实也就包含了不选text1[i-1]text2[j-1]`都不选情况。
    • 如果text1[i-1] == text2[j-1],说明公共子序列的长度可以增加,增加之前的最长公共子序列的长度是dp[i-1][j-1],新增一个公共元素,dp[i][j]=dp[i-1][j-1]

    • 递推公式

    dp[i][j]= \begin{cases} max(dp[i-1][j], dp[i][j-1]),& text1[i-1] \ne text[j-1] \\ dp[i-1][j-1] + 1,,& text1[i-1] == text[j-1] \end{cases}

  • 遍历顺序:dp[i][j]更新依赖的状态都是i-1j-1,所以ij都是从小到大遍历。

初见的题解代码如下

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));

        int res = 0;
        for (int i = 1; i <= text1.size(); i++) {
            for (int j = 1; j <= text2.size(); j++) {
                if (text1[i - 1] == text2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else dp[i][j] = max(dp[i - 1][j - 1], max(dp[i - 1][j], dp[i][j - 1]));
                res = max(res, dp[i][j]);
            }
        }

        return res;
    }
};

实际上,正如之前所分析的,不需要再比较dp[i-1][j-1],因为dp[i-1][j]dp[i][j-1]就包含了dp[i-1][j-1]的情况

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));

        int res = 0;
        for (int i = 1; i <= text1.size(); i++) {
            for (int j = 1; j <= text2.size(); j++) {
                if (text1[i - 1] == text2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                res = max(res, dp[i][j]);
            }
        }

        return res;
    }
};

根据dp数组的定义,实际上也不需要再在dp数组的更新过程中维护一个最大值,dp[text1.size()][text2.size()]相当于已经比对完了所有元素,它的值就是答案。

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));

        // int res = 0;
        for (int i = 1; i <= text1.size(); i++) {
            for (int j = 1; j <= text2.size(); j++) {
                if (text1[i - 1] == text2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                // res = max(res, dp[i][j]);
            }
        }

        return dp[text1.size()][text2.size()];
    }
};
  • 以 LeetCode 的示例一为例来模拟dp数组的更新过程

LeetCode1035 不相交的线

1035. 不相交的线 - 力扣(Leetcode)

  • 复制上一题的题解代码,查找并替换,提交通过。
  • 开个玩笑,但这道题目如果用动态规划的思路来解决,几乎是和上一题一样的。只是这道题目是用具体情形来描述问题的,需要我们从具体的情形中抽象出一个“最长公共子序列”的问题。

    • 首先要找到可以连接的元素,实际上就是在找nums1nums2中元素的交集,那问题来了,这种求集合的问题一般都要明确是求组合还是求排列,这题要求什么?接着看题目的要求。

    • 找一个nums1的子集与一个nums2的子集,这两个子集中的元素相同,并且要保证各组相同元素之间连线不能相交。怎么保证各组相同元素之间的连线不相交呢?

    • 假设前面已经连好了一组,nums1[i]nums2[j]相连,再连一条线,假设是nums1[a]nums2[b]相连:

      • 如果 a < i \ \wedge \ j < bi < a \ \wedge \ b < j ,如下图,那么连线一定会相交
  • 只有 a < i \ \wedge \ b < ji < a \ \wedge \ j < b ,如下图,连线才不会相交
  • 这告诉我们,nums1的子集中的元素和nums2中子集中的元素,要保持在原序列中的相对顺序,即选取出的子集中的元素下标应该是严格递增的。所以,就是要求一个nums1的子序列和一个nums2的子序列,这两个子序列相等,且长度尽可能长。就是求最长公共子序列。

题解代码如下

class Solution {
public:
    int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));

        // int res = 0;
        for (int i = 1; i <= nums1.size(); i++) {
            for (int j = 1; j <= nums2.size(); j++) {
                if (nums1[i - 1] == nums2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                // res = max(res, dp[i][j]);
            }
        }

        return dp[nums1.size()][nums2.size()];
    }
};

LeetCode53 最大子数组和

  • 这道题目之前用贪心法写过,贪心策略为:如果当前子数组的和大于0,就继续累加;如果当前子数组的和大于之前记录过的最大值,就更新最大值;如果当前子数组的和小于0,说明之前确定的子数组的起始下标和终止下标不合适,以i+1作为新的起始下标。
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int res = INT_MIN;
        int cur = 0;
        for (int i = 0; i < nums.size(); i++) {
            cur += nums[i];
            if (cur > res) res = cur;
            if (cur < 0) cur = 0;
        }
        return res;
    }
};
  • 那么用动态规划的思想,怎么解决这道题呢?

  • 首先,本题要求“连续”,根据之前的经验,在定义dp数组和推导递推公式时需要注意。

  • 确定dp数组及数组下标的含义:本题要求“连续”,所以这样定义dp数组——dp[i]表示以nums[i]结尾的连续子数组的元素之和的最大值。

  • 递推公式,对于dp[i],其可能有两种更新来源,当前的子数组以nums[i]结尾

    • 接上之前以nums[i-1]为结尾的子数组能得到最大和,那么根据dp数组的定义,之前0i-1的最大和为dp[i-1],所以dp[i] = dp[i-1] + nums[i]
    • 不接上之前以nums[i-1]为结尾的子数组能得到最大和,那么根据dp数组的定义,以nums[i]结尾的子数组只能是只有nums[i]自身,所以dp[i] = nums[i]
    • 当然是取两种情况的最大值 dp[i] = max(nums[i], dp[i - 1] + nums[i])
  • 本题的初始化可以将dp[i]统一初始化成nums[i],但实际上更新dp数组时也会覆盖一次初始值,所以无所谓其他dp值得初始化。只要初始化dp[0]=nums[0]即可。

  • 遍历顺序,dp[i]依赖于dp[i-1]i从小到大遍历。

题解代码如下

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        vector<int> dp(nums.size(), INT_MIN);
        dp[0] = nums[0];

        int res = dp[0];
        for (int i = 1; i < nums.size(); i++) {
            dp[i] = max(nums[i], dp[i - 1] + nums[i]);
            res = max(res, dp[i]);
        }

        return res;
    }
};
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容