算法沉淀——动态规划之子序列问题
01.最长递增子序列02.摆动序列03.最长递增子序列的个数04.最长数对链
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 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
1 <= nums.length <= 2500-104 <= nums[i] <= 104
思路
1. 状态表达: 我们定义一个动态规划数组 dp,其中 dp[i] 表示以第 i 个位置元素为结尾的所有「子序列」中,最长递增子序列的长度。
2. 状态转移方程: 针对 dp[i] 的状态,我们考虑「子序列的构成方式」进行分类讨论:
当子序列长度为 1 时,只包含自身,因此 dp[i] = 1;当子序列长度大于 1 时,我们遍历前面的所有元素 j(0 <= 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 n=nums.size();
vector
int ret=1;
for(int i=1;i for(int j=0;j if(nums[j] dp[i]=max(dp[i],dp[j]+1); } ret=max(ret,dp[i]); } return ret; } }; 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) 。 示例 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) 。 示例 3: 输入:nums = [1,2,3,4,5,6,7,8,9] 输出:2 提示: 1 <= nums.length <= 10000 <= nums[i] <= 1000 思路 1. 状态表达: 我们定义两个动态规划数组 f 和 g,其中: f[i] 表示以第 i 个位置元素为结尾的所有的子序列中,最后一个位置呈现「上升趋势」的最长摆动序列的长度。g[i] 表示以第 i 个位置元素为结尾的所有的子序列中,最后一个位置呈现「下降趋势」的最长摆动序列的长度。 2. 状态转移方程: 对于 f[i] 的状态,我们考虑子序列的构成方式: 当子序列长度为 1 时,只包含自身,因此 f[i] = 1。当子序列长度大于 1 时,遍历前面的所有元素 j(0 <= 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 时,遍历前面的所有元素 j(0 <= j <= i - 1),检查是否可以将第 i 个位置的元素接在第 j 个元素之后形成递减子序列。如果 nums[j] > nums[i],则在满足这个条件下,要使 j 结尾呈现上升状态,最长的摆动序列即为 f[j] + 1。更新状态:g[i] = max(f[j] + 1, g[i])。 3. 初始化: 初始时,每个元素都可以构成长度为 1 的摆动序列,因此将整个 f 和 g 数组初始化为 1。 4. 填表顺序: 按照从左到右的顺序遍历数组。 5. 返回值: 返回 f 和 g 数组中的最大值。这样的动态规划算法通过遍历数组,不断更新状态,最终得到以每个位置为结尾的子序列中最长的摆动序列长度。最后返回两个数组中的最大值。 代码 class Solution { public: int wiggleMaxLength(vector int n=nums.size(); vector int ret=1; for(int i=1;i for(int j=0;j if(nums[j] else if(nums[j]>nums[i]) g[i]=max(f[j]+1,g[i]); } ret=max(ret,max(g[i],f[i])); } return ret; } }; 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]。 示例 2: 输入: [2,2,2,2,2] 输出: 5 解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。 提示: 1 <= nums.length <= 2000 -106 <= nums[i] <= 106 思路 1. 状态表达: 我们定义两个动态规划数组 len 和 count,其中: len[i] 表示以第 i 个位置为结尾的最长递增子序列的长度。count[i] 表示以第 i 个位置为结尾的最长递增子序列的个数。 2. 状态转移方程: 首先,对于 len[i] 的状态,我们考虑子序列的构成方式: 当子序列长度为 1 时,只包含自身,因此 len[i] = 1。当子序列长度大于 1 时,遍历前面的所有元素 j(0 <= 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 < i 且 nums[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 n = nums.size(); vector 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; } }; 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] 。 示例 2: 输入:pairs = [[1,2],[7,8],[4,5]] 输出:3 解释:最长的数对链是 [1,2] -> [4,5] -> [7,8] 。 提示: n == pairs.length1 <= n <= 1000-1000 <= lefti < righti <= 1000 思路 排序: 首先,对数对数组按照每个数对的第一个元素进行升序排序。这是为了方便后续动态规划的处理。状态表达: 我们定义动态规划数组 dp,其中 dp[i] 表示以第 i 个数对为结尾的最长数对链的长度。状态转移方程: 对于 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。初始化: 刚开始的时候,全部初始化为 1。填表顺序: 根据状态转移方程,填表顺序是从左往右。返回值: 根据状态表达,返回整个 dp 数组中的最大值。 代码 class Solution { public: int findLongestChain(vector sort(pairs.begin(),pairs.end()); int n=pairs.size(); vector int ret=1; for(int i=1;i for(int j=0;j if(pairs[i][0]>pairs[j][1]) dp[i]=max(dp[i],dp[j]+1); } ret=max(ret,dp[i]); } return ret; } }; 精彩链接
发表评论