当前位置:   article > 正文

算法沉淀——动态规划之子序列问题(上)(leetcode真题剖析)_构建子序列算法题

构建子序列算法题

在这里插入图片描述

01.最长递增子序列

题目链接:https://leetcode.cn/problems/longest-increasing-subsequence/

给你一个整数数组 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 。
  • 1
  • 2
  • 3

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4
  • 1
  • 2

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1
  • 1
  • 2

提示:

  • 1 <= nums.length <= 2500
  • -104 <= nums[i] <= 104

思路

1. 状态表达: 我们定义一个动态规划数组 dp,其中 dp[i] 表示以第 i 个位置元素为结尾的所有「子序列」中,最长递增子序列的长度。

2. 状态转移方程: 针对 dp[i] 的状态,我们考虑「子序列的构成方式」进行分类讨论:

  • 当子序列长度为 1 时,只包含自身,因此 dp[i] = 1
  • 当子序列长度大于 1 时,我们遍历前面的所有元素 j0 <= j <= i - 1),检查是否可以将第 i 个位置的元素接在第 j 个元素之后形成递增子序列。如果 nums[j] < nums[i],说明可以构成递增序列,更新状态:dp[i] = max(dp[j] + 1, dp[i])

3. 初始化: 初始时,每个元素都可以构成长度为 1 的递增子序列,因此将整个 dp 数组初始化为 1

4. 填表顺序: 我们按照从左到右的顺序遍历数组。

5. 返回值: 由于不知道最长递增子序列以哪个元素结尾,我们返回 dp 数组中的最大值。这样的动态规划算法通过遍历数组,不断更新状态,最终得到以每个位置为结尾的子序列中最长递增子序列的长度。最后返回数组中所有元素的最大值。

代码

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size();
        vector<int> dp(n,1);
        int ret=1;
        for(int i=1;i<n;i++){
            for(int j=0;j<i;j++){
                if(nums[j]<nums[i])
                    dp[i]=max(dp[i],dp[j]+1);
            }
            ret=max(ret,dp[i]);
        }
        return ret;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

02.摆动序列

题目链接:https://leetcode.cn/problems/wiggle-subsequence/

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 **摆动序列 。**第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

  • 例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
  • 相反,[1, 4, 7, 2, 5][1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。

给你一个整数数组 nums ,返回 nums 中作为 摆动序列最长子序列的长度

示例 1:

输入:nums = [1,7,4,9,2,5]
输出:6
解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。
  • 1
  • 2
  • 3

示例 2:

输入:nums = [1,17,5,10,13,15,10,5,16,8]
输出:7
解释:这个序列包含几个长度为 7 摆动序列。
其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8) 。
  • 1
  • 2
  • 3
  • 4

示例 3:

输入:nums = [1,2,3,4,5,6,7,8,9]
输出:2
  • 1
  • 2

提示:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <= 1000

思路

1. 状态表达: 我们定义两个动态规划数组 fg,其中:

  • f[i] 表示以第 i 个位置元素为结尾的所有的子序列中,最后一个位置呈现「上升趋势」的最长摆动序列的长度。
  • g[i] 表示以第 i 个位置元素为结尾的所有的子序列中,最后一个位置呈现「下降趋势」的最长摆动序列的长度。

2. 状态转移方程: 对于 f[i] 的状态,我们考虑子序列的构成方式:

  • 当子序列长度为 1 时,只包含自身,因此 f[i] = 1
  • 当子序列长度大于 1 时,遍历前面的所有元素 j0 <= j <= i - 1),检查是否可以将第 i 个位置的元素接在第 j 个元素之后形成递增子序列。如果 nums[j] < nums[i],则在满足这个条件下,要使 j 结尾呈现下降状态,最长的摆动序列即为 g[j] + 1。更新状态:f[i] = max(g[j] + 1, f[i])

对于 g[i] 的状态,我们同样考虑子序列的构成方式:

  • 当子序列长度为 1 时,只包含自身,因此 g[i] = 1
  • 当子序列长度大于 1 时,遍历前面的所有元素 j0 <= j <= i - 1),检查是否可以将第 i 个位置的元素接在第 j 个元素之后形成递减子序列。如果 nums[j] > nums[i],则在满足这个条件下,要使 j 结尾呈现上升状态,最长的摆动序列即为 f[j] + 1。更新状态:g[i] = max(f[j] + 1, g[i])

3. 初始化: 初始时,每个元素都可以构成长度为 1 的摆动序列,因此将整个 fg 数组初始化为 1

4. 填表顺序: 按照从左到右的顺序遍历数组。

5. 返回值: 返回 fg 数组中的最大值。这样的动态规划算法通过遍历数组,不断更新状态,最终得到以每个位置为结尾的子序列中最长的摆动序列长度。最后返回两个数组中的最大值。

代码

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n=nums.size();
        vector<int> f(n,1),g(n,1);

        int ret=1;
        for(int i=1;i<n;++i){
            for(int j=0;j<i;j++){
                if(nums[j]<nums[i]) f[i]=max(g[j]+1,f[i]);
                else if(nums[j]>nums[i]) g[i]=max(f[j]+1,g[i]);
            }
            ret=max(ret,max(g[i],f[i]));
        }

        return ret;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

03.最长递增子序列的个数

题目链接:https://leetcode.cn/problems/number-of-longest-increasing-subsequence/

给定一个未排序的整数数组 nums , 返回最长递增子序列的个数 。

注意 这个数列必须是 严格 递增的。

示例 1:

输入: [1,3,5,4,7] 
输出: 2 
解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。 
  • 1
  • 2
  • 3

示例 2:

输入: [2,2,2,2,2] 
输出: 5 
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。 
  • 1
  • 2
  • 3

提示:

1 <= nums.length <= 2000

-106 <= nums[i] <= 106

思路

1. 状态表达: 我们定义两个动态规划数组 lencount,其中:

  • len[i] 表示以第 i 个位置为结尾的最长递增子序列的长度。
  • count[i] 表示以第 i 个位置为结尾的最长递增子序列的个数。

2. 状态转移方程: 首先,对于 len[i] 的状态,我们考虑子序列的构成方式:

  • 当子序列长度为 1 时,只包含自身,因此 len[i] = 1
  • 当子序列长度大于 1 时,遍历前面的所有元素 j0 <= j < i),检查是否可以将第 i 个位置的元素接在第 j 个元素之后形成递增子序列。如果 nums[j] < nums[i],则在满足这个条件下,要使 j 结尾呈现上升状态,最长的递增子序列即为 len[j] + 1。更新状态:len[i] = max(len[j] + 1, len[i])

接着,对于 count[i] 的状态,我们考虑子序列的构成方式:

  • 我们此时已经知道 len[i] 的信息,还知道 [0, i - 1] 区间上的 count[j] 信息,使用 j 表示 [0, i - 1] 区间上的下标。
  • 我们可以再遍历一遍 [0, i - 1] 区间上的所有元素,只要能够构成上升序列,并且上升序列的长度等于 len[i],那么我们就把 count[i] 加上 count[j] 的值。这样循环一遍之后,count[i] 存的就是我们想要的值。更新状态:count[i] += count[j],其中 0 <= j < inums[j] < nums[i] && len[j] + 1 == len[i]

3. 初始化:

  • 对于 len[i],所有元素自己就能构成一个递增序列,直接全部初始化为 1
  • 对于 count[i],我们先初始化为 0,然后在累加的时候判断。

4. 填表顺序: 从左往右遍历数组。

5. 返回值: 最终的最长递增子序列的长度为 maxLen。根据题目要求,返回所有长度等于 maxLen 的子序列的个数。

代码

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> len(n, 1), count(n, 1); 
        int retlen = 1, retcount = 1; 
        for (int i = 1; i < n; i++) 
        {
            for (int j = 0; j < i; j++)
            {
                if (nums[j] < nums[i])
                {
                    if (len[j] + 1 == len[i])
                        count[i] += count[j];
                    else if (len[j] + 1 > len[i]) 
                        len[i] = len[j] + 1, count[i] = count[j];
                }
            }
            if (retlen == len[i])
                retcount += count[i];
            else if (retlen < len[i]) 
                retlen = len[i], retcount = count[i];
        }
      
        return retcount;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

04.最长数对链

题目链接:https://leetcode.cn/problems/maximum-length-of-pair-chain/

给你一个由 n 个数对组成的数对数组 pairs ,其中 pairs[i] = [lefti, righti]lefti < righti

现在,我们定义一种 跟随 关系,当且仅当 b < c 时,数对 p2 = [c, d] 才可以跟在 p1 = [a, b] 后面。我们用这种形式来构造 数对链

找出并返回能够形成的 最长数对链的长度

你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。

示例 1:

输入:pairs = [[1,2], [2,3], [3,4]]
输出:2
解释:最长的数对链是 [1,2] -> [3,4] 。
  • 1
  • 2
  • 3

示例 2:

输入:pairs = [[1,2],[7,8],[4,5]]
输出:3
解释:最长的数对链是 [1,2] -> [4,5] -> [7,8] 。
  • 1
  • 2
  • 3

提示:

  • n == pairs.length
  • 1 <= n <= 1000
  • -1000 <= lefti < righti <= 1000

思路

  1. 排序: 首先,对数对数组按照每个数对的第一个元素进行升序排序。这是为了方便后续动态规划的处理。
  2. 状态表达: 我们定义动态规划数组 dp,其中 dp[i] 表示以第 i 个数对为结尾的最长数对链的长度。
  3. 状态转移方程: 对于 dp[i],遍历所有 [0, i - 1] 区间内的数对,用 j 表示下标。找出所有满足 pairs[j][1] < pairs[i][0]j,然后找出其中最大的 dp[j],最后加上 1 就是以第 i 个数对为结尾的最长数对链。状态转移方程为:dp[i] = max(dp[j] + 1, dp[i]),其中 0 <= j < i
  4. 初始化: 刚开始的时候,全部初始化为 1
  5. 填表顺序: 根据状态转移方程,填表顺序是从左往右。
  6. 返回值: 根据状态表达,返回整个 dp 数组中的最大值。

代码

class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) {
        sort(pairs.begin(),pairs.end());

        int n=pairs.size();
        vector<int>  dp(n,1);

        int ret=1;
        for(int i=1;i<n;i++){
            for(int j=0;j<i;j++){
                if(pairs[i][0]>pairs[j][1]) dp[i]=max(dp[i],dp[j]+1);
            }
            ret=max(ret,dp[i]);
        }
        return ret;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/484199?site
推荐阅读
相关标签
  

闽ICP备14008679号