关于 动态规划的理解 与例题,点击👇
并且我们之前做过有关 子数组的dp问题:C++子数组dp问题
子序列与子数组的区别主要在于:子数组是连续的,即下标连续的不中断的;而子序列可以连续可以不连续(子序列的范围更大)。
有了上面的经验,我们来解下面 子序列 问题 :
通过下面一道例题理解子序列问题,以及如何解子序列dp问题:

思路
根据题目要求,我们设置状态表示:

根据上面的思路,我们可以用两个for循环编写呆猫:
代码
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
// 创建dp数组 + 初始化
vector<int> dp(n, 1); // dp[i]: 以i为结尾,严格递增子序列的最长长度
int ret = 1; // 结果:
for(int i = 1; i < n; ++i)
{
for(int j = 0; j <= i-1; ++j)
{
if(nums[j] < nums[i])
dp[i] = max(dp[j]+1, dp[i]);
}
ret = max(ret, dp[i]);
}
return ret;
}
};
需要注意的是:本题的最优解法并不是利用动态规划,但通过本道例题可以很好的对子序列问题进行理解:
(顺带贴上更优解法:贪心 + 二分)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
// 贪心
vector<int> ret;
ret.push_back(nums[0]);
for(int i = 0; i < nums.size(); ++i)
{
if(nums[i] > ret.back()) {
ret.push_back(nums[i]);
} else { // 二分查找
int left = 0, right = ret.size() - 1;
while(left < right)
{
int mid = (left + right) >> 1;
if(ret[mid] < nums[i])
left = mid + 1;
else
right = mid;
}
ret[left] = nums[i]; // 插入
}
}
return ret.size();
}
};
通过《最长递增子序列》一题给的经验,我们来解下面的算法题:

思路


代码
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int n = nums.size();
// dp数组的创建 + 元素初始化
vector<int> f(n, 1); // 以i为结尾,最后呈现“上升”状态的 子序列的最长长度
vector<int> g(n, 1); // 以i为结尾,最后呈现“下降”状态的 子序列的最长长度
int ret = 1; // 最终结果
for(int i = 1; i < n; ++i)
{
for(int j = 0; j <= i - 1; ++j)
{
if(nums[j] < nums[i])
f[i] = max(g[j]+1, f[i]); // 可以将第 i 个元素加入到以第 j 个元素结尾的递增子序列中
if(nums[j] > nums[i])
g[i] = max(f[j]+1, g[i]);
}
ret = max(max(f[i], g[i]), ret);
}
return ret;
}
};

思路
根据这个思路,我们进行分析:


代码
class Solution {
public:
int findNumberOfLIS(vector<int>& nums) {
int n = nums.size();
// 创建dp数组 + 初始化元素
vector<int> len(n, 1), count(n, 1); // 分别为:以i为结尾,1.递增子序列的最长长度 与 2.最长递增子序列的个数
int retLen = 1, retCount = 1;
for(int i = 1; i < n; ++i)
{
for(int j = 0; j <= i-1; ++j)
{
if(nums[j] < nums[i])
{
if(len[j] + 1 == len[i]) count[i] += count[j]; // 第j位恰好与i组成最长递增子序列
// else if(len[j] + 1 < len[i]) continue; // 无视此次,可以注释掉
else if(len[j] + 1 > len[i]) len[i] = len[j] + 1, count[i] = count[j]; // j的递增序列更长
}
}
// 更新结果
if(retLen == len[i]) retCount += count[i];
else if(retLen < len[i]){
retCount = count[i];
retLen = len[i];
}
// else 无视该情况
}
return retCount;
}
};

思路

代码
class Solution {
public:
int findLongestChain(vector<vector<int>>& pairs) {
// 预处理:排序数组
sort(pairs.begin(), pairs.end());
int n = pairs.size();
// 创建dp数组 + 初始化元素
vector<int> dp(n, 1); // dp[i]: 以i为结尾,数对链的最长长度
int ret = 1; // 结果
for(int i = 1; i < n; ++i)
{
for(int j = 0; j <= i-1; ++j)
{
if(pairs[j][1] < pairs[i][0])
{
dp[i] = max(dp[j] + 1, dp[i]);
}
}
ret = max(ret, dp[i]);
}
return ret;
}
};

思路


代码
class Solution {
public:
int longestSubsequence(vector<int>& arr, int difference) {
unordered_map<int, int> hash; // arr[i] : dp[i]
hash[arr[0]] = 1; // 初始化
int ret = 1, n = arr.size();
for(int i = 1; i < n; ++i)
{
hash[arr[i]] = hash[arr[i] - difference] + 1; // dp[i] = dp[j] + 1,可以保证是最后一个b
ret = max(ret, hash[arr[i]]);
}
return ret;
}
};

思路


代码
class Solution {
public:
int lenLongestFibSubseq(vector<int>& arr) {
int n = arr.size();
// 创建dp数组 + 元素初始化
vector<vector<int>> dp(n, vector<int>(n, 2));
// 哈希表:值映射下标
unordered_map<int, int> hash;
for(int i = 0; i < arr.size(); ++i)
hash[arr[i]] = i;
int ret = 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;
}
};

思路


代码
class Solution {
public:
int longestArithSeqLength(vector<int>& nums) {
int n = nums.size();
unordered_map<int, int> hash; // <元素 下标>
hash[nums[0]] = 0;
vector<vector<int>> dp(n, vector<int>(n, 2));// dp表的创建+初始化
int ret = 2;
for(int i = 1; i < n; ++i) // 固定倒数第二个数
{
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; // dp[k][i] + 1
ret = max(ret, dp[i][j]);
}
hash[nums[i]] = i; // 保存最近的元素下标
}
return ret;
}
};

思路
代码
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);
vector<vector<long long>> dp(n, vector<long long>(n, 0));// dp表的创建+初始化
long long sum = 0;
for(int j = 2; j < n; ++j) // 固定倒数第二个数
{
for(int i = 1; i < j; ++i) // 固定最后一个数
{
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; // += dp[k][i] + 1
sum += dp[i][j];
}
}
return sum;
}
};