目录
leetcode题目
一、最长递增子序列
二、摆动序列
三、最长递增子序列的个数
四、最长数对链
五、最长定差子序列
六、最长的斐波那契子序列的长度
七、最长等差数列
八、等差数列划分 II
leetcode题目
一、最长递增子序列
300. 最长递增子序列 - 力扣(LeetCode)https://leetcode.cn/problems/longest-increasing-subsequence/1.题目解析
子序列与子数组的区别在于:子序列可以不连续,但是要求元素顺序与原数组元素顺序不变~
因此子序列本质是包含子数组的~, 本题要求求出最长的严格递增子序列的长度
2.算法分析
1.状态表示
dp[i] 表示 以 i 位置元素为结尾的所有子序列中, 最长递增子序列的长度
2.状态转移方程
3.初始化
dp表全部初始化成14.填表顺序
从左向右
5.返回值
返回dp表的最大值
3.算法代码
class Solution {
public:
int lengthOfLIS(vector<int>& nums)
{
//1.创建dp表
int n = nums.size();
vector<int> dp(n, 1);
//2.填表 + 返回值
int ret = 1;
for(int i = 1; i < n; i++)
{
for(int j = i - 1; j >= 0; j--)
{
if(nums[j] < nums[i])
dp[i] = max(dp[i], dp[j] + 1);
}
ret = max(ret, dp[i]);
}
return ret;
}
};
二、摆动序列
376. 摆动序列 - 力扣(LeetCode)https://leetcode.cn/problems/wiggle-subsequence/description/
1.题目解析
摆动序列是指相邻元素之间的差值正负交替,只有1个元素或2个元素也叫做摆动序列,求数组中满足摆动序列的最长子序列
2.算法分析
1.状态表示
f[i] 表示 以 i 位置元素为结尾的所有子序列中,最后呈现"上升"趋势,最长摆动序列的长度
g[i] 表示 以 i 位置元素为结尾的所有子序列中,最后呈现"下降"趋势,最长摆动序列的长度
2.状态转移方程
3.初始化
两个表都初始化成1
4.填表顺序
从左向右两个表一起填
5.返回值
两个表的最大值
3.算法代码
class Solution {
public:
int wiggleMaxLength(vector<int>& nums)
{
//1.创建dp表
int n = nums.size();
vector<int> f(n, 1), g(n, 1);
//2.填表 + 返回值
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(g[j] + 1, f[i]);
if(nums[i] < nums[j])
g[i] = max(f[j] + 1, g[j]);
}
ret = max(ret, max(f[i], g[i]));
}
return ret;
}
};
三、最长递增子序列的个数
673. 最长递增子序列的个数 - 力扣(LeetCode)https://leetcode.cn/problems/number-of-longest-increasing-subsequence/1.题目解析
求最长递增子序列的个数(严格递增)
2.算法分析
补充知识: 在数组中找出最大值出现的次数(要求遍历一次)
maxval = arr[0], count = 1, 遍历到数组中的元素x
1. x == maxval: count += 1
2. x < maxval: 无视
3. x > maxval: maxval = x, count = 0
1.状态表示
len[i]: 以 i 位置元素为结尾的所有的子序列中,最长递增子序列的长度
count[i]: 以 i 位置元素为结尾的所有的子序列中,最长递增子序列的个数
2.状态转移方程(参考上面的补充知识)
len[i] = count[i] = 1
j在[0, i-1]遍历, 在nums[j] < nums[i]的前提下:
①len[j] + 1 == len[i]:count[i] += count[j]
②len[j] + 1 < len[i]:无视
③len[j]+1 > len[i]:len[i] = len[j] + 1, count[i] = count[j]
3.初始化
两个表都初始化成1
4.填表顺序
从左往右两个表一起填
5.返回值
遍历len表的同时找出最大值出现的次数
3.算法代码
class Solution {
public:
int findNumberOfLIS(vector<int>& nums)
{
//1.创建dp表
int n = nums.size();
vector<int> len(n, 1), count(n, 1);
//2.填表 + 返回值
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;
}
};
四、最长数对链
646. 最长数对链 - 力扣(LeetCode)https://leetcode.cn/problems/maximum-length-of-pair-chain/1.题目解析
给定一个数组,数组每个元素是一个数对[left, right], left 是严格 < right 的,[a, b], [c, d], [e, f]要能构成数对链,要求b < c, d < e, 求能构成的最长数对链的长度
2.算法分析
依旧是动态规划,但是在分析状态转移方程时,求dp[i]往往要依赖前一个dp[i-1]的值或者依赖后一个dp[i+1]的值,而本题分析到某一个数对时,无法保证要用到的上一个数对是在该数对的前面
比如 [[1, 2], [7, 8], [4, 5]]这个数组,最终形成的数对链中的[4, 5]就链到了[7, 8]的前面
因此我们需要先对数组排序, 而排序之后:
对于[a, b], [c, d], d > c >= a, 推出 d > a, 因此[c, d]是不可能链到[a, b]后面的, 这样就可以得出状态转义移方程了~
1.状态表示
dp[i]: 以 i 位置元素为结尾的所有数对链中,最长的数对链的长度
2.状态转移方程
3.初始化
dp表全部初始化成1
4.填表顺序
从左往右填表
5.返回值
dp表的最大值
3.算法代码
class Solution {
public:
int findLongestChain(vector<vector<int>>& pairs)
{
//排序
sort(pairs.begin(), pairs.end());
//1.创建dp表
int n = pairs.size();
vector<int> dp(n, 1);
//2.填表 + 返回值
int ret = 1;
for(int i = 1; i < n; i++)
{
for(int j = 0; j < i; j++)
{
if(pairs[j][1] < pairs[i][0])
dp[i] = max(dp[i], dp[j]+1);
}
ret = max(ret, dp[i]);
}
return ret;
}
};
五、最长定差子序列
1218. 最长定差子序列 - 力扣(LeetCode)https://leetcode.cn/problems/longest-arithmetic-subsequence-of-given-difference/description/1.题目解析
给定一个数组和difference, 返回最长定差子序列(相邻元素之间差为difference)的长度
2.算法分析
1.状态表示
dp[i]: 以 i 位置元素为结尾的所有子序列中,最长的定差子序列的长度
2.状态转移方程
3.初始化
hash[arr[0]] = 1
4.填表顺序
从左往右
5.返回值
dp表的最大值(哈希表的最大值)
3.算法代码
class Solution {
public:
int longestSubsequence(vector<int>& arr, int difference)
{
//1.创建dp表(哈希表)
unordered_map<int, int> hash; //arr[i]-dp[i]
//2.初始化
hash[arr[0]] = 1;
//3.填表 + 返回值
int ret = 1;
for(int i = 1; i < arr.size(); i++)
{
hash[arr[i]] = hash[arr[i] - difference] + 1; //这一行既保证了b不存在的时候,hash[i]是1, 也保证了b是最后一个b
ret = max(ret, hash[arr[i]]);
}
return ret;
}
};
六、最长的斐波那契子序列的长度
873. 最长的斐波那契子序列的长度 - 力扣(LeetCode)https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/1.题目解析
给定一个数组,求满足斐波那契式的最长的子序列的长度
2.算法分析
1.状态表示
dp[i][j]:以 i 位置 以及 j 位置 为结尾的所有子序列中,最长的斐波拉契子序列的长度
ps:开始是定义的dp[i], 但是推不出状态转移方程, 因为只知道子序列的个数,但是无法确定具体的斐波那契式的子序列,也就无法根据dp[i]前面的值推导出dp[i]~
题目七与题目八的状态表示也是同样的得到方法~
2.状态转移方程
3.初始化
把dp表中的所有值都初始化成2(只会用到dp表中 i < j 的位置)
4.填表顺序
dp[i][j] = dp[k][i] + 1, k < i && i < j, 因此从上往下填表即可
5.返回值
ret < 3 ? 0 : dp表中的最大值
3.算法代码
class Solution
{
public:
int lenLongestFibSubseq(vector<int>& arr)
{
//1.将元素和下标绑定丢进哈希表
int n = arr.size();
unordered_map<int, int> hash;
for(int i = 0; i < n; i++)
hash[arr[i]] = i;
//2.创建dp表
vector<vector<int>> dp(n, vector<int>(n, 2));
//3.填表+返回值
int ret = 2;
for(int j = 2; j < n; j++) //最后一个位置
{
for(int i = 1; i < j; i++) //倒数第二个位置
{
int a = arr[j] - arr[i];
if(hash.count(a) && hash[a] < i)
dp[i][j] = dp[hash[a]][i] + 1;
ret = max(ret, dp[i][j]);
}
}
return ret < 3 ? 0 : ret;
}
};
七、最长等差数列
1027. 最长等差数列 - 力扣(LeetCode)https://leetcode.cn/problems/longest-arithmetic-subsequence/description/1.题目解析
给你一个整数数组
nums
,返回nums
中最长等差子序列的长度
2.算法分析
1.状态表示
dp[i][j]:以 i 位置 以及 j 位置 为结尾的所有子序列中,最长的等差序列的长度
2.状态转移方程
3.初始化
把dp表中的所有值都初始化成2(只会用到dp表中 i < j 的位置)
4.填表顺序
先固定倒数第2个数(b),再枚举倒数第一个数(c) --- 因为优化中,我们要保存的的是离i位置最近的a元素的下标,因此选择把b固定住,让c去移动
5.返回值
dp表中的最大值
3.算法代码
class Solution {
public:
int longestArithSeqLength(vector<int>& nums)
{
//优化
unordered_map<int, int> hash;
hash[nums[0]] = 0;
//1.创建dp表 + 初始化
int n = nums.size();
vector<vector<int>> dp(n, vector<int>(n, 2));
//2.填表 + 返回值
int ret = 2;
for(int i = 1; i < n; i++) //固定倒数第2个数
{
for(int j = i + 1; j < n; j++) //枚举倒数第一个数
{
int a = 2 * nums[i] - nums[j];
if(hash.count(a))
dp[i][j] = dp[hash[a]][i] + 1;
ret = max(ret, dp[i][j]);
}
hash[nums[i]] = i; //i位置固定完之后,将nums[i]与i存入哈希表
}
return ret;
}
};
八、等差数列划分 II
446. 等差数列划分 II - 子序列 - 力扣(LeetCode)https://leetcode.cn/problems/arithmetic-slices-ii-subsequence/1.题目解析
求数组中等差子序列的个数(本题的等差子序列至少要有3个元素)
2.算法分析
1.状态表示
dp[i][j]:以 i 位置 以及 j 位置 为结尾的所有子序列中,等差子序列的个数
2.状态转移方程
3.初始化
dp表所有的值都初始化0(因为最坏情况下,两个元素是无法构成等差子序列的)
4.填表顺序
固定倒数第1个数,枚举倒数第2个数
5.返回值
dp表所有元素的和
3.算法代码
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& nums)
{
//优化
int n = nums.size();
unordered_map<long long, vector<int>> hash;
for(int i = 0; i < n; i++)
hash[nums[i]].push_back(i);
//1.创建dp表 + 初始化
vector<vector<int>> dp(n, vector<int>(n));
//2.填表 + 返回值
int sum = 0;
for(int j = 2; j < n; j++) //固定倒数第1个数
{
for(int i = 1; i < j; i++) //枚举倒数第2个数
{
long long a = (long long)2 * nums[i] - nums[j];
if(hash.count(a))
{
for(auto k : hash[a])
{
if(k < i)
dp[i][j] += dp[k][i] + 1;
}
}
sum += dp[i][j];
}
}
return sum;
}
};