最長上升子序列長度
題意:給定一個無序的整數數組,找到其中最長上升子序列的長度。
輸入: [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;
}
};