最长上升子序列类的经典解法
(大部分内容都是知乎大神pecco的内容 在此写下博文为了自己更好的消化)
转移函数:dp[i]表示以nums[i]为最后一个元素的上升子序列的长度
d
p
[
i
]
=
m
a
x
d
p
[
j
]
+
1
0
≤
j
<
i
a
n
d
n
u
m
s
[
i
]
<
n
u
m
s
[
j
]
dp[i] = max\quad dp[j]+1 \qquad 0 \leq j < i \quad and \quad nums[i] < nums[j]
dp[i]=maxdp[j]+10≤j<iandnums[i]<nums[j]
可以写出代码:
int LIS(vector<int>& nums)
{
//leetcode上的答案
int n = nums.size();
if(n ==0)
{
return 0;
}
vector<int> dp(n,0);
dp[0] = 1;
for(int i =0; i < nums.size(); i++)
{
//对于第i个元素 从第0个元素开始到第i-1 个元素
for(int j = 0; j<i ; j++)
{
if(nums[j] < nums[i])
{
dp[i] = max(dp[i], dp[j] + 1);//当前的dp[i]中和新找到的dp[j]+1中大的那个
}
}
}
return *max_element(dp.begin(),dp.end());
}
贪心+二分
如果我们需要使得上升子序列尽可能的长,则我们需要使得上升子序列上升的尽可能的慢。
这次的转移函数是:
dp[len] 表示长度为len的上升子序列的末尾元素的最小值。(pecco的知乎笔记有例子)
dp的性质:
- dp[len]是单调递增的。
- 如果nums[i] < dp[len] ,在dp[1,2,…,len]中找到 j j j使得 d p [ j − 1 ] < n u m s ≤ d p [ j ] dp[j-1]<nums\leq dp[j] dp[j−1]<nums≤dp[j],将dp[j]更新为nums[i]
leetcode上的解法(手写了二分)
int LIS(vector<int>& nums)
{
int len = 1, n = nums.size();
if (n == 0)
return 0;
vector<int> dp(n+1,0);
dp[len] = nums[0];
for(int i = 1; i <n; i++)
{
if(nums[i] > dp[len])
{
dp[++len] = nums[i];
}
else
{
int l = 1,r = len, pos = 0;
while(l<=r)
{
int mid = (l+r)/2;
if(d[mid] < nums[i])
{
pos = mid;
l = mid+1;
}
else
{
r = mid-1;
}
}
dp[pos+1] = nums[i];
}
}
return len;
}
得到的pos是最后一个满足 dp[pos] < nums[i]的index,也就是说
d
p
[
p
o
s
+
1
]
≥
n
u
m
s
[
i
]
dp[pos+1] \geq nums[i]
dp[pos+1]≥nums[i]
其实可以注意到leetcode 写二分的一个模板;
int left = index1, right = index2+1;
int ans = index1 -1;
while(left <= right)
{
int mid = (left+ right) /2;
if(func(mid))
{
ans = mid;
left = mid+1;
}
else
{
right = mid-1;
}
}
得到的ans是递增序列[left, right-1]中最后一个满足func的index。
pecco的知乎算法笔记中用一个函数来代替了,看起来更加简洁一些:
int len = 0;
for(int i = 0; i< nums[i].size(); i++)
{
if(nums[i] >dp[len])
{
dp[++len] = nums[i];
}
else
{
*lower_bound(dp+1, dp+len, nums[i]) = nums[i];
}
}
return len;
*lower_bound(dp+1,dp+len, nums[i])就是寻找1 到 len-1之间第一个大于等于 nums[i]的元素
拓展
最长不减子序列
int len = 0;
for(int i = 0; i < n; i++)
{
if(dp[len] <= nums[i])
{
dp[++len] = nums[i]; //等于时允许插入
}
else
{
*upper_bound(dp+1, dp+len, nums[i]) = nums[i]; //
}
}
可以用 [4 8 9 5 6 7 2 7 6] 做实验。
最长递减子序列
int len = 0;
for(int i = 0; i < n; i++)
{
if(dp[len] > nums[i])
{
dp[++len] = nums[i];
}
else
{
*lower_bound(dp+1, dp+len, nums[i], greater<int>()) = nums[i]; //
}
}
最长不增子序列
int len = 0;
for(int i = 0; i < n; i++)
{
if(dp[len] >= nums[i])
{
dp[++len] = nums[i]; //等于时允许插入
}
else
{
*upper_bound(dp+1, dp+len, nums[i],greater<int>()) = nums[i]; //
}
}