[参考Irimsky博主博客](https://blog.csdn.net/irimsky/article/details/99684346)
3-2 O(nlogn)算法
①一个队列可以通过每一个人记住自己的前一个人,这样排列而成,题目的条件是他比他的前一个人高
②第i个人在选择他前面的人j时(a[j]<a[i]),还涉及到位置j+1,如果j+1位置有人x,i必须有充分的理由理由来取代x,在这个题目里面,i的理由是他比x矮,如果i站在这里更有可能形成最长的队列
③因此i只要在现有的站法里找到一个这样的位置j:j站位的人比i矮,j+1站位的人比i高(除非j+1站位还没有人)
⑤最终我们只要找到位置最靠后的人,问他记住的前面的人是谁,如果是x就接着问x他前面的人是谁,知道问到队伍第一个人为止
比如{1,3,5,4,4,8,6,7}
a[0] = 1, 则长度为1的LIS为1,则last[1] = 1 , len = 1;
a[1] = 3, 3>1,故目前的最长的LIS可以为2了,last[2] = 3, len = 2;
a[2] = 5,last[3] = 5 , len = 3;
a[3] = 4, 其可以加在5和3之间, 把5替换掉,即last[3] = 4;
a[4] = 4,与last[3]相等了,不做变动。
a[5] = 8,比最后一位(5)大,所以last[4] = 8, len = 4;
a[6] = 6,在5和8中间,所以last[4] = 6;
a[7] = 7, 比最后一位6大,所以last[5] = 7, len =5;
最终得到结果LIS长度为:5,last数组为:1,3,4,6,7
0认可的队列:
a[i]=x | lis | last[lis] | lastidx[lis] |
---|---|---|---|
a[0]=1 | 1 | 1 | 0 |
1认可的队列:
a[i]=x | lis | last[lis] | lastidx[lis] |
---|---|---|---|
a[0]=1 | 1 | 1 | 0 |
a[1]=3 | 2 | 3 | 1 |
2认可的队列:
a[i]=x | lis | last[lis] | lastidx[lis] |
---|---|---|---|
a[0]=1 | 1 | 1 | 0 |
a[1]=3 | 2 | 3 | 1 |
a[2]=5 | 3 | 5 | 2 |
3认可的队列:
a[i]=x | lis | last[lis] | lastidx[lis] |
---|---|---|---|
a[0]=1 | 1 | 1 | 0 |
a[1]=3 | 2 | 3 | 1 |
a[3]=4 | 3 | 4 | 3 |
5认可的队列:
a[i]=x | lis | last[lis] | lastidx[lis] |
---|---|---|---|
a[0]=1 | 1 | 1 | 0 |
a[1]=3 | 2 | 3 | 1 |
a[3]=4 | 3 | 4 | 3 |
a[5]=8 | 4 | 8 | 5 |
6认可的队列:
a[i]=x | lis | last[lis] | lastidx[lis] |
---|---|---|---|
a[0]=1 | 1 | 1 | 0 |
a[1]=3 | 2 | 3 | 1 |
a[3]=4 | 3 | 4 | 3 |
a[6]=6 | 4 | 6 | 6 |
7认可的队列:
a[i]=x | lis | last[lis] | lastidx[lis] |
---|---|---|---|
a[0]=1 | 1 | 1 | 0 |
a[1]=3 | 2 | 3 | 1 |
a[3]=4 | 3 | 4 | 3 |
a[6]=6 | 4 | 6 | 6 |
a[7]=7 | 5 | 7 | 7 |
最长的队列是7认可的队列:
找到7认可的队列,7肯定会说他前面的人是6
找到6认可的队列,6肯定会说他前面的人是3
找到3认可的队列,3肯定会说他前面的人是1
找到1认可的队列,1肯定会说他前面的人是0
0前面没人了
它们的身高排列为:1,3,4,6,7
代码实现:
①需要记录i号的的身高的数组a[i]=>数组a[n]
②需要记录len长度的队列最后一个人的编号last_id[len]和身高last[len]=>数组last_id[n] last[n]
③需要记录i号的认可的他前面的人的编号pre[i]=> 数组pre[n]
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
int main()
{
//input
int maxn,i;
cin>>maxn;
int a[maxn];
for(i=0;i<maxn;i++)
cin>>a[i];
int last[maxn], last_id[maxn], pre[maxn], len=0, maxi;
//initial
memset(last,0x3f,sizeof last);
last[0] = -inf;
for(int i=0;i<maxn;i++)
{
//二分查找,找i可以取代的人
int pos = lower_bound(last, last+maxn, a[i])-last;
//i取代了长pos的队列的最后一个人
last[pos] = a[i];
last_id[pos] = i;
//i认可的前一个人
pre[i] = last_id[pos-1];
//i取代最高的人,或者补位
if(pos >= len)
{
len = pos;
//LIS最后一个数字的下标
maxi = i;
}
}
//output
int lis[maxn];
cout << "最长公共子串长度:" << len << endl;
for(int i=len-1;i>=0;i--)
{
//找到maxi
lis[i] = a[maxi];
//问maxi他前一个人的编号
maxi = pre[maxi];
}
for(int i=0;i<len;i++)
cout << lis[i] << " ";
return 0;
}
0 1 2 3 4 5 6 7 8
1 4 8 2 3 5 9 4 7
0认可:0 1
1认可:0 1 1 4
2认可:0 1 2 1 4 8
3认可:0 3 2 1 2 8
4认可:0 3 4 1 2 3
5认可:0 3 4 5 1 2 3 5
6认可:0 3 4 5 6 1 2 3 5 9
7认可:0 3 4 7 6 1 2 3 4 9
8认可:0 3 4 7 8 1 2 3 4 7
最后一个人:8
找到8,前面是7
找到7,前面是4
找到4,前面是3
找到3,前面是0
最终:1 2 3 4 7
2-1动态规划算法:
- 状态dp[i]:以第i个数结尾的最长递增子序列的长度
- 状态方程:dp[i]=dp[j]+1,其中 a[j]<a[i],j<i 且 dp[j] 最大。若没有满足的a[j]则dp[i]=1
- 最后max(dp)即为最长递增子序列的长度
- 要求得最长递增子序列,则用另外的数组lastidx记录上述的“j”
#include<iostream>
using namespace std;
int main()
{
int a[6] = {1,3,5,4,4,6};
int dp[6],lastidx[6];
for(int i = 0;i < 6;i++)
{
dp[i] = 1; lastidx[i] = i;
for(int j = 0;j < i;j++)
{
if(a[j] < a[i] && dp[j] + 1 > dp[i])
{
dp[i] = dp[j] + 1;
lastidx[i] = j;
}
}
}
int maxx = 0, maxi = 0;
for(int i = 0;i < 6;i++)
if(dp[i]>maxx)
{
maxx = dp[i];
maxi = i;
}
int lis[6];
for(int i=maxx-1;i>0;i--)
{
lis[i] = a[maxi];
maxi = lastidx[maxi];
}
cout << "最长公共子串长度:" << maxx << endl;
for(int i=0;i<maxx;i++)
cout << lis[i] << " ";
return 0;
}
3.最长公共子序列法
对于给定长度为N的数组A:
使数组B为排序后的数组A (O(NlogN))
求出A与B的最长公共子序列(LCS) (O(N2))
对求得的公共子序列进行去重 (O(N))例如:A = {1,3,5,4,4,6}
则B = {1,3,4,4,5,6}
最长公共子串C = {1,3,4,4,6}
对C去重得到结果:{1,3,4,6}