0814算法 - 动态规划10

300.最长递增子序列 | 674. 最长连续递增序列 | 718. 最长重复子数组

Posted by ZA on August 14, 2024

300. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

思路

思考一下递推关系从哪里来?任意一个元素是否可以加入某个最长递增子序列,有两个条件:

  • 递增:它一定比前方某个递增子序列的尾部元素大 a[i]>a[j], j in [0, i-1]
  • 最长:它本身不属于更长的递增子序列 max(dp[i], dp[j]+1)

根据以上条件可以构造递归步骤如下:

  1. dp数组及其下标含义:以nums[i]为结尾的最长子序列长度为dp[i]
  2. 递推公式:根据上述条件分析,位置i的最长递增子序列,等于[0, i-1]各个位置的最长递增子序列 + 1 的最大值。即: if(nums[i] > nums[j]) dp[i] = max(dp[i], dp[j]+1);
  3. 数组初始化:每个元素的起始子序列长度都为1,dp[i] = 1;
  4. 遍历顺序:子序列必然是从前向后推导
  5. 举例推导:输入:[0,1,0,3,2],dp数组的变化如下:

代码实现

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int>dp(nums.size(), 1);
        int result = 0;
        for(int i=1; i<nums.size(); i++){
            for(int j=0; j<i; j++){
                if(nums[i]>nums[j]){
                    dp[i] = max(dp[i], dp[j]+1);
                }
            }
            if(dp[i]>result) result = dp[i];
        }
        return result;
    }
};

674. 最长连续递增序列

给定一个未经排序的整数数组,找到最长且 ** 连续递增的子序列** ,并返回该序列的长度。

连续递增的子序列 可以由两个下标 lrl < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。

示例 1:

输入:nums = [1,3,5,4,7]
输出:3
解释:最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。 

思路

本题相较于上题额外增加了一个 连续 的条件,其实更简单了,因为不用再去跟[0, i-1]的每一个元素进行比较,只需要跟nums[i-1]对比,更大更长就加入,否则重新开始

  1. dp数组及其下标含义:以元素i结尾的最长连续递增子序列长度为dp[i]
  2. 递推公式:简化为单一比较 if(nums[i]>nums[i-1]) dp[i]=dp[i-1]+1;
  3. 数组初始化:同理每个元素的初始子序列为1
  4. 遍历顺序:同理从前向后查找子序列
  5. 举例推导:已输入nums = [1,3,5,4,7]为例,dp数组状态如下:

代码实现

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        vector<int> dp(nums.size(), 1);
        int result = 1;

        for(int i=1; i<nums.size(); i++){
            if(nums[i]>nums[i-1]){
                dp[i] = dp[i-1]+1;
            }
            if(dp[i]>result) result = dp[i];
        }
        return result;
    }
};

注意 i 要从 1 开始查找,因为它的dp结果取决于前方元素的子序列长度,dp[0-1] 会发生越界错误,上一题也翻了相同的错误,只不过用的是 <i 而不是 i-- 因此没有报错。

718. 最长重复子数组

给两个整数数组 nums1nums2 ,返回 *两个数组中 公共的 、长度最长的子数组的长度 * 。

示例 1:

输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
输出:3
解释:长度最长的公共子数组是 [3,2,1] 。

思路

对于两个数组的最长重复子序列,就需要用到一个二维数组记录两者的长度,

  1. dp数组及其下标含义:以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]
  2. 递推公式:`dp[i][j] = dp[i-1][j-1]+1
  3. 数组初始化:已知dp[1][1] = dp[0][0] + 1,推导出dp[0][0] = 0
  4. 遍历顺序:两层循环从左到右遍历A和B
  5. 举例推导:A: [1,2,3,2,1],B: [3,2,1,4,7]为例:

代码实现

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>>dp(nums1.size()+1, vector<int>(nums2.size()+1, 0));
        int result = 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;
                }
                if(dp[i][j]>result) result = dp[i][j];
            }
        }
        return result;
    }
};

参考