本文涉及知识点
状态机dp 线段树
C++动态规划
LeetCode2407. 最长递增子序列 II
给你一个整数数组 nums 和一个整数 k 。
找到 nums 中满足以下要求的最长子序列:
子序列 严格递增子序列中相邻元素的差值 不超过 k 。
请你返回满足上述要求的 最长子序列 的长度。
子序列 是从一个数组中删除部分元素后,剩余元素不改变顺序得到的数组。
示例 1:
输入:nums = [4,2,1,4,3,4,5,8,15], k = 3
输出:5
解释:
满足要求的最长子序列是 [1,3,4,5,8] 。
子序列长度为 5 ,所以我们返回 5 。
注意子序列 [1,3,4,5,8,15] 不满足要求,因为 15 - 8 = 7 大于 3 。
示例 2:
输入:nums = [7,4,5,1,8,12,4,7], k = 5
输出:4
解释:
满足要求的最长子序列是 [4,5,8,12] 。
子序列长度为 4 ,所以我们返回 4 。
示例 3:
输入:nums = [1,5], k = 1
输出:1
解释:
满足要求的最长子序列是 [1] 。
子序列长度为 1 ,所以我们返回 1 。
提示:
1 <= nums.length <= 105
1 <= nums[i], k <= 105
动态规划+线段树
n = nums.length
也可以用最大值树状数组,性能更佳。
动态规划的状态表示
用最大值线段树记录状态,lineTree[i]表示以i结尾的最长子序列长度。
空间复杂度:O(n)
动态规划的状态转移方程
left = max(0,nums[i]-k)
right = max(nums[i]+k,1e5)
val = lineTree.Query(left,right)
lineTree.Update(nums[i],val);
动态规划的初始值
全部为0。
动态规划的填表顺序
依次枚举nums。
动态规划返回值
查询整个线段树
代码
核心代码
template<class TSave, class TRecord >
class CSingeUpdateLineTree
{
protected:
virtual void OnInit(TSave& save, int iSave) = 0;
virtual void OnQuery(TSave& save) = 0;
virtual void OnUpdate(TSave& save, int iSave, const TRecord& update) = 0;
virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, int iSaveLeft, int iSaveRight) = 0;
};
template<class TSave, class TRecord >
class CVectorSingUpdateLineTree : public CSingeUpdateLineTree<TSave, TRecord>
{
public:
CVectorSingUpdateLineTree(int iEleSize, TSave tDefault) :m_iEleSize(iEleSize),m_save(iEleSize*4,tDefault){
}
void Update(int index, TRecord update) {
Update(1, 0, m_iEleSize-1, index, update);
}
void Query(int leftIndex, int leftRight) {
Query(1, 0, m_iEleSize - 1, leftIndex, leftRight);
}
void Init() {
Init(1, 0, m_iEleSize - 1);
}
TSave QueryAll() {
return m_save[1];
}
protected:
int m_iEleSize;
void Init(int iNodeNO, int iSaveLeft, int iSaveRight)
{
if (iSaveLeft == iSaveRight) {
this->OnInit(m_save[iNodeNO], iSaveLeft);
return;
}
const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;
Init(iNodeNO * 2, iSaveLeft, mid);
Init(iNodeNO * 2 + 1, mid + 1, iSaveRight);
this->OnUpdateParent(m_save[iNodeNO], m_save[iNodeNO*2], m_save[iNodeNO*2+1], iSaveLeft, iSaveRight);
}
void Query(int iNodeNO, int iSaveLeft, int iSaveRight, int iQueryLeft, int iQueryRight) {
if ((iSaveLeft >= iQueryLeft) && (iSaveRight <= iQueryRight)) {
this->OnQuery(m_save[iNodeNO]);
return;
}
if (iSaveLeft == iSaveRight) {//没有子节点
return;
}
const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;
if (mid >= iQueryLeft) {
Query(iNodeNO * 2, iSaveLeft, mid, iQueryLeft, iQueryRight);
}
if (mid + 1 <= iQueryRight) {
Query(iNodeNO * 2 + 1, mid + 1, iSaveRight, iQueryLeft, iQueryRight);
}
}
void Update(int iNodeNO, int iSaveLeft, int iSaveRight, int iUpdateNO, TRecord update) {
if (iSaveLeft == iSaveRight)
{
this->OnUpdate(m_save[iNodeNO], iSaveLeft, update);
return;
}
const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2;
if (iUpdateNO <= mid) {
Update(iNodeNO * 2, iSaveLeft, mid, iUpdateNO, update);
}
else {
Update(iNodeNO * 2 + 1, mid + 1, iSaveRight, iUpdateNO, update);
}
this->OnUpdateParent(m_save[iNodeNO], m_save[iNodeNO*2], m_save[iNodeNO*2+1], iSaveLeft, iSaveRight);
}
vector<TSave> m_save;
};
template<class TSave, class TRecord>
class CMaxLineTree : public CVectorSingUpdateLineTree<TSave, TRecord>
{
public:
int m_iMax = 0;
protected:
using CVectorSingUpdateLineTree<TSave, TRecord>::CVectorSingUpdateLineTree;
virtual void OnInit(TSave& save, int iSave) override {
}
virtual void OnQuery(TSave& save) override {
m_iMax = max(m_iMax, save);
}
virtual void OnUpdate(TSave& save, int iSave, const TRecord& update) override {
save = update;
}
virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, int iSaveLeft, int iSaveRight) override {
par = max(left, r);
}
};
class Solution {
public:
int lengthOfLIS(vector<int>& nums, int k) {
const int iMax = *std::max_element(nums.begin(), nums.end());
CMaxLineTree<int, int> lineTree(iMax+1,0);
for (const auto& n : nums) {
auto Query = [&lineTree, &iMax](int left, int right) {
lineTree.m_iMax = 0;
left = max(0, left);
right = min(right, iMax);
lineTree.Query(left, right);
return lineTree.m_iMax;
};
const auto val = Query(n-k,n-1);
lineTree.Update(n, val+1);
}
return lineTree.QueryAll();
}
};
单元测试
template<class T1, class T2>
void AssertEx(const T1& t1, const T2& t2)
{
Assert::AreEqual(t1, t2);
}
template<class T>
void AssertEx(const vector<T>& v1, const vector<T>& v2)
{
Assert::AreEqual(v1.size(), v2.size());
for (int i = 0; i < v1.size(); i++)
{
Assert::AreEqual(v1[i], v2[i]);
}
}
template<class T>
void AssertV2(vector<vector<T>> vv1, vector<vector<T>> vv2)
{
sort(vv1.begin(), vv1.end());
sort(vv2.begin(), vv2.end());
Assert::AreEqual(vv1.size(), vv2.size());
for (int i = 0; i < vv1.size(); i++)
{
AssertEx(vv1[i], vv2[i]);
}
}
namespace UnitTest
{
vector<int> nums;
int k;
TEST_CLASS(UnitTest)
{
public:
TEST_METHOD(TestMethod00)
{
nums = { 4, 2, 1, 4, 3, 4, 5, 8, 15 }, k = 3;
auto res = Solution().lengthOfLIS(nums, k);
AssertEx(5, res);
}
TEST_METHOD(TestMethod01)
{
nums = { 7,4,5,1,8,12,4,7 }, k = 5;
auto res = Solution().lengthOfLIS(nums, k);
AssertEx(4, res);
}
TEST_METHOD(TestMethod02)
{
nums = {1,5 }, k = 1;
auto res = Solution().lengthOfLIS(nums, k);
AssertEx(1, res);
}
TEST_METHOD(TestMethod03)
{
nums = { 1 }, k = 5;
auto res = Solution().lengthOfLIS(nums, k);
AssertEx(1, res);
}
};
}
树状数组
template<class T = int>
class CTreeArrMax
{
public:
CTreeArrMax(int iEleSize, T def):iMax(iEleSize) {
m_aMax.assign(iEleSize+1, def);
m_aRangMax.assign(iEleSize+1, def);
}
void Modify(int indexBase0, int value)
{
indexBase0++;
if (value <= m_aMax[indexBase0])
{
return;
}
m_aMax[indexBase0] = value;
while (indexBase0 <= iMax)
{
m_aRangMax[indexBase0] = max(m_aRangMax[indexBase0], value);
indexBase0 += BitLower(indexBase0);
}
}
int Query(int leftBas0, int rBase0)
{
leftBas0++;
rBase0++;
leftBas0 = max(1, leftBas0);
int iMax = 0;
while (rBase0 >= leftBas0)
{
const int pre = rBase0 - BitLower(rBase0);
if (pre + 1 >= leftBas0)
{
iMax = max(iMax, m_aRangMax[rBase0]);
rBase0 = pre;
}
else
{
iMax = max(iMax, m_aMax[rBase0]);
rBase0--;
}
}
return iMax;
}
protected:
int BitLower(int x)
{
return x & (-x);
}
const int iMax;
vector<T> m_aMax, m_aRangMax;
};
class Solution {
public:
int lengthOfLIS(vector<int>& nums, int k) {
const int iMax = *max_element(nums.begin(), nums.end());
CTreeArrMax<int> treeArr(iMax + 1, 0);
for (const int& n : nums)
{
int PreNum = treeArr.Query(n - k, n - 1);
treeArr.Modify(n, PreNum + 1);
}
return treeArr.Query(1, iMax);
}
};
扩展阅读
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关推荐
我想对大家说的话 |
---|
《喜缺全书算法册》以原理、正确性证明、总结为主。 |
按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。