动态规划题目中,常出现子序列相关问题,这里单独挑出来训练
注意:子序列与子数组不同,子数组要求的是原数组中连续的一部分,而子序列要的是原数组以原排列方式,抽取一部分
1.最长递增子序列
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列
1.状态表示:用dp[ i ]表示选到第i个元素的子序列的最长递增长度
2.状态转移方程:dp[ i ] = dp[ j ] + 1
3.初始化:全初始化为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[i] > nums[j])
dp[i] = max(dp[i], dp[j] + 1);
ret = max(ret, dp[i]);
}
return ret;
}
};
这是ac代码
2.摆动序列
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列
给你一个整数数组 nums
,返回 nums
中作为 摆动序列 的 最长子序列的长度
1.状态表示:用 f [ i ]表示选到第i个元素的子序列以大结尾的最长摆动序列长度
用 g [ i ]表示选到第i个元素的子序列以小结尾的最长摆动序列长度
2.状态转移方程:
f[ i ] = g [ j ] + 1
g[ i ] = f [ j ] + 1
3.初始化:全初始化为1
4.填表顺序:从左往右两个表一起填
5.返回值:两个表中的最大值
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[i] > nums[j])
f[i] = max(f[i], g[j] + 1);
if(nums[i] < nums[j])
g[i] = max(g[i], f[j] + 1);
}
ret = max(ret, max(f[i], g[i]));
}
return ret;
}
};
这是ac代码
3.最长递增子序列的个数
给定一个未排序的整数数组 nums
, 返回最长递增子序列的个数 。
注意 这个数列必须是 严格 递增的
1.状态表示:用 len [ i ]表示以第 i 个元素为结尾的所有子序列中最长递增子序列的长度
用 count [ i ]表示以第 i 个元素为结尾的所有子序列中最长递增子序列的次数
2.状态转移方程:
if( len[ i ] == len[ j ] + 1) count[ i ] += count[ j ] ;
else if ( len[ i ] < len[ j ] + 1) count[ i ] = count[ j ], len[ i ] = len[ j ] + 1;
3.初始化:全初始化为1
4.填表顺序:从左往右两个表一起填
5.返回值:retcount
注意:完成这道题之前必须熟练掌握下面这个小算法,一次遍历如何找到数组中最大值出现个数
class Solution {
public:
int findNumberOfLIS(vector<int>& nums)
{
int n = nums.size();
vector<int> len(n, 1);
auto count = len;
int retcount = 1, retlen = 1;
for(int i = 1; i < n; ++i)
{
for(int j = 0; j < i; ++j)
{
if(nums[i] > nums[j])
{
if(len[i] == len[j] + 1) count[i] += count[j];
else if(len[i] < len[j] + 1) count[i] = count[j], len[i] = len[j] + 1;
}
}
if(retlen == len[i]) retcount += count[i];
else if(retlen < len[i]) retcount = count[i], retlen = len[i];
}
return retcount;
}
};
这是ac代码
4.最长数对链
给你一个由 n
个数对组成的数对数组 pairs
,其中 pairs[i] = [lefti, righti]
且 lefti < righti
。
现在,我们定义一种 跟随 关系,当且仅当 b < c
时,数对 p2 = [c, d]
才可以跟在 p1 = [a, b]
后面。我们用这种形式来构造 数对链 。
找出并返回能够形成的 最长数对链的长度 。
你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造
注意:利用排序,对数据进行处理,使可以用动态规划来解决
1.状态表示:用dp[ i ]表示以第 i 个元素为结尾所有子序列中,最长子序列
2.状态转移方程:dp[ i ] = dp[ j ] + 1
3.初始化:全初始化为1
4.填表顺序:从左往右填
5.返回值: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] = dp[j] + 1;
ret = max(ret, dp[i]);
}
return ret;
}
};
这是ac代码
5.最长定差子序列
给你一个整数数组 arr
和一个整数 difference
,请你找出并返回 arr
中最长等差子序列的长度,该子序列中相邻元素之间的差等于 difference
。
子序列 是指在不改变其余元素顺序的情况下,通过删除一些元素或不删除任何元素而从 arr
派生出来的序列
1.状态表示:用dp[ i ]表示以第 i 个元素为结尾所有子序列中,最长定差子序列
2.状态转移方程:dp[ i ] = dp[ j ] + 1
3.初始化:全初始化为1
4.填表顺序:从左往右填
5.返回值:dp表中最大值
class Solution {
public:
int longestSubsequence(vector<int>& arr, int difference)
{
int n = arr.size();
int ret = 1;
vector<int> dp(n, 1);
for(int i = 1; i < n; ++i)
{
for(int j = 0; j < i; ++j)
if(arr[i] == arr[j] + difference)
dp[i] = dp[j] + 1;
ret = max(ret, dp[i]);
}
return ret;
}
};
提交发现超时
再仔细思考,不难发现,只要有一个数与公差,我们可以退出来整个等差序列,于是我们可以借助哈希表来完成任务
于是,我们有以下ac代码
class Solution {
public:
int longestSubsequence(vector<int>& arr, int difference)
{
unordered_map<int, int> hash;
int n = arr.size();
hash[arr[0]] = 1;
int ret = 1;
for(int i = 1; i < n; ++i)
{
hash[arr[i]] = hash[arr[i] - difference] + 1;
ret = max(ret, hash[arr[i]]);
}
return ret;
}
};
6.最长的斐波那契子序列的长度
如果序列 X_1, X_2, ..., X_n
满足下列条件,就说它是 斐波那契式 的:
n >= 3
- 对于所有
i + 2 <= n
,都有X_i + X_{i+1} = X_{i+2}
给定一个严格递增的正整数数组形成序列 arr ,找到 arr 中最长的斐波那契式的子序列的长度。如果一个不存在,返回 0 。
(回想一下,子序列是从原序列 arr 中派生出来的,它从 arr 中删掉任意数量的元素(也可以不删),而不改变其余元素的顺序。例如, [3, 5, 8]
是 [3, 4, 5, 6, 7, 8]
的一个子序列)
1.状态表示:用dp[ i ][ j ]表示以第 i 个元素, 第 j 个元素为结尾的所有子序列中最长的斐波那契序列长度(i < j)
2.状态转移方程:dp[ i ][ j ] = dp[ hash [ a ] ][ i ] + 1;
3.初始化:全初始化为2
4.填表顺序:从上到下每一行,从左到右每一列
5.返回值:dp表中最大值(最大值为2时找不到斐波那契子序列,返回0)
class Solution {
public:
int lenLongestFibSubseq(vector<int>& arr)
{
int n = arr.size();
unordered_map<int, int> hash;
for(int i = 0; i < n; ++i)
hash[arr[i]] = i;
int ret = 2;
vector<vector<int>> dp(n, vector<int>(n, 2));
for(int j = 2; j < n; ++j)
{
for(int i = 1; i < j; ++i)
{
int a = arr[j] - arr[i];
if(a < arr[i] && hash.count(a))
dp[i][j] = dp[hash[a]][i] + 1;
ret = max(ret, dp[i][j]);
}
}
return ret == 2 ? 0 : ret;
}
};
这是ac代码
7.最长等差数列
给你一个整数数组 nums
,返回 nums
中最长等差子序列的长度。
回想一下,nums
的子序列是一个列表 nums[i1], nums[i2], ..., nums[ik]
,且 0 <= i1 < i2 < ... < ik <= nums.length - 1
。并且如果 seq[i+1] - seq[i]
( 0 <= i < seq.length - 1
) 的值都相同,那么序列 seq
是等差的
1.状态表示:用dp[ i ][ j ]表示以第 i 个元素, 第 j 个元素为结尾的所有子序列中最长的等差数列长度(i < j)
2.状态转移方程:dp[ i ][ j ] = dp[ hash [ a ] ][ i ] + 1;
3.初始化:全初始化为2
4.填表顺序:从上到下每一行,从左到右每一列
5.返回值:dp表中最大值(最大值为2时找不到斐波那契子序列,返回0)
注意:由于给定数组是无序的,找到的值应该是离 i 最近的,这就要随 i 的遍历实时更新哈希表
class Solution {
public:
int longestArithSeqLength(vector<int>& nums)
{
int n = nums.size();
vector<vector<int>> dp(n, vector<int>(n, 2));
unordered_map<int, int> hash;
hash[nums[0]] = 0;
int ret = 2;
for(int i = 1; i < n; ++i)
{
for(int j = i + 1; j < n; ++j)
{
int a = nums[i] * 2 - nums[j];
if(hash.count(a) && hash[a] < i)
dp[i][j] = dp[hash[a]][i] + 1;
ret = max(ret, dp[i][j]);
}
hash[nums[i]] = i;
}
return ret;
}
};
这是ac代码
8.等差数列划分II-子序列
给你一个整数数组 nums
,返回 nums
中所有 等差子序列 的数目。
如果一个序列中 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该序列为等差序列
1.状态表示:用dp[ i ][ j ]表示以第 i 个元素, 第 j 个元素为结尾的所有子序列中等差数列个数(i < j)
2.状态转移方程:dp[ i ][ j ] += (dp[e][i] + 1);
3.初始化:无需初始化
4.填表顺序:从上到下每一行,从左到右每一列
5.返回值:dp表的和
注意:由于数组无序,以及重复数字的影响,这里需要用值,下标数组的哈希表来对数据进行映射
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& nums)
{
int n = nums.size();
vector<vector<int>> dp(n, vector<int>(n));
unordered_map<long long, vector<int>> hash;
for(int i = 0; i < n; ++i)
hash[nums[i]].push_back(i);
int ret = 0;
for(int i = 1; i < n; ++i)
{
for(int j = i + 1; j < n; ++j)
{
long long a = (long long)nums[i] * 2 - nums[j];
if(hash.count(a))
for(auto e : hash[a])
if(e < i)
dp[i][j] += (dp[e][i] + 1);
ret += dp[i][j];
}
}
return ret;
}
};
这是ac代码