最长上升子序列长度
题意:给定一个无序的整数数组,找到其中最长上升子序列的长度。
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
解法:这是一道经典的动态规划
状态表示:dp[i]
表示序列前i
个数(必须包含nums[i])的最长上升子序列长度
状态转移:dp[i] = max(dp[j] + 1), 0<=j<i && nums[j]<nums[i]
程序(CPP)
const int N = 1e5+10;
int dp[N];
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
int res = 0;
for(int i = 0; i < n; i++)
{
dp[i] = 1;
for(int j = 0; j < i; j++)
if(nums[j] < nums[i])
dp[i] = max(dp[i], dp[j] + 1);
res = max(res, dp[i]);
}
return res;
}
};
带限制的子序列和
题意:给你一个整数数组 nums 和一个整数 k ,请你返回 非空 子序列元素和的最大值,子序列需要满足:子序列中每两个 相邻 的整数 nums[i] 和 nums[j] ,它们在原数组中的下标 i 和 j 满足 i < j 且 j - i <= k 。
数组的子序列定义为:将数组中的若干个数字删除(可以删除 0 个数字),剩下的数字按照原本的顺序排布。
输入:nums = [10,-2,-10,-5,20], k = 2
输出:23
解释:子序列为 [10, -2, -5, 20]
思路:与最长上升子序列问题一样
状态表示:dp[i]
表示序列前i
个数(必须包含nums[i])的带限制的子序列和最大值
状态转移:dp[i] = nums[i] + max(dp[j]), i-k<=j<i
朴素解法如下:
const int N = 1e5+10;
int dp[N];
class Solution {
public:
int constrainedSubsetSum(vector<int>& nums, int k) {
int n = nums.size();
int res = nums[0];
for(int i = 0; i < n; i++)
{
dp[i] = nums[i];
for(int j = max(0, i - k); j < i; j++)
dp[i] = max(dp[i], dp[j] + nums[i]);
res = max(res, dp[i]);
}
return res;
}
};
高级解法:状态转移方程为dp[i] = nums[i] + max(dp[j]), i-k<=j<i
,暴力解法每次求max(dp[j]), i-k<=j<i
都要遍历k个数,时间复杂度为。我们可以用优先队列维护max(dp[j]) i-k<=j<i
,时间复杂度将为,程序如下:
const int N = 1e5+10;
int dp[N];
class Solution {
public:
int constrainedSubsetSum(vector<int>& nums, int k) {
// 优先队列队首元素为{max(dp[j]), 对应最大dp[j]的下标}
priority_queue<pair<int, int>> q;
int n = nums.size();
int res = nums[0];
for(int i = 0; i < n; i++)
{
dp[i] = nums[i];
// 下标与i距离超过k的以后永远不会用到,出队即可
while(!q.empty() && i-q.top().second > k) q.pop();
if(!q.empty()) dp[i] = max(dp[i], q.top().first + nums[i]);
q.push({dp[i], i});
res = max(res, dp[i]);
}
return res;
}
};