最长递增子序列
2016-12-14 13:52
127 查看
传送门 :动态规划
传送门 :最长递增子序列 O(NlogN)算法
一个序列有 N 个数:A[1],A[2],…,A[N],求出最长非降子序列的长度。 (讲DP基本都会讲到的一个问题LIS:longest increasing subsequence)
正如上面我们讲的,面对这样一个问题,我们首先要定义一个“状态”来代表它的子问题, 并且找到它的解。注意,大部分情况下,某个状态只与它前面出现的状态有关, 而独立于后面的状态。
下面来一步步找到“状态”和“状态转移方程”。 假如我们考虑求 A[1],A[2],…,A[i] 的最长非降子序列的长度,其中i<N, 那么上面的问题变成了原问题的一个子问题(问题规模变小了,你可以让 i=1,2,3 等来分析) 然后我们定义 d(i),表示前 i 个数中以 A[i] 结尾的最长非降子序列的长度。 如果我们把 d(1) 到 d(N) 都计算出来,那么最终我们要找的答案就是这里面最大的那个。 状态找到了,下一步找出状态转移方程。
为了方便理解我们是如何找到状态转移方程的,我先把下面的例子提到前面来讲。 如果我们要求的这N个数的序列是:
5,3,4,8,6,7
根据上面找到的状态,我们可以得到:(下文的最长非降子序列都用LIS表示)
前1个数的LIS长度
前2个数的LIS长度
前3个数的LIS长度
前4个数的LIS长度
OK,分析到这,我觉得状态转移方程已经很明显了,如果我们已经求出了d(1)到d(i-1), 那么d(i)可以用下面的状态转移方程得到:
d(i)=max1,d(j)+1 , 其中j<i , A[j]<=A[i]
用大白话解释就是,想要求d(i),就把i前面的各个子序列中, 最后一个数不大于A[i]的序列长度加1,然后取出最大的长度即为 d(i) 。 当然了,有可能i前面的各个子序列中最后一个数都大于 A[i],那么 d(i)=1, 即它自身成为一个长度为 1 的子序列。
类似于一种松弛的思想
传送门里的代码没有给出位置,我加上了位置信息,感觉好费劲。
原文的代码
第一种方法给出了位置信息,第二种O(NlogN)算法怎样给出位置信息,如果大家知道可以告诉我哈,谢谢
lis算法的时间复杂度是O(N^2 ),并不是最优的解法。LIS是O(NlogN)。
传送门 :最长递增子序列 O(NlogN)算法
一个序列有 N 个数:A[1],A[2],…,A[N],求出最长非降子序列的长度。 (讲DP基本都会讲到的一个问题LIS:longest increasing subsequence)
正如上面我们讲的,面对这样一个问题,我们首先要定义一个“状态”来代表它的子问题, 并且找到它的解。注意,大部分情况下,某个状态只与它前面出现的状态有关, 而独立于后面的状态。
下面来一步步找到“状态”和“状态转移方程”。 假如我们考虑求 A[1],A[2],…,A[i] 的最长非降子序列的长度,其中i<N, 那么上面的问题变成了原问题的一个子问题(问题规模变小了,你可以让 i=1,2,3 等来分析) 然后我们定义 d(i),表示前 i 个数中以 A[i] 结尾的最长非降子序列的长度。 如果我们把 d(1) 到 d(N) 都计算出来,那么最终我们要找的答案就是这里面最大的那个。 状态找到了,下一步找出状态转移方程。
为了方便理解我们是如何找到状态转移方程的,我先把下面的例子提到前面来讲。 如果我们要求的这N个数的序列是:
5,3,4,8,6,7
根据上面找到的状态,我们可以得到:(下文的最长非降子序列都用LIS表示)
前1个数的LIS长度
d(1)=1(序列:5)
前2个数的LIS长度
d(2)=1(序列:3;3前面没有比3小的)
前3个数的LIS长度
d(3)=2(序列:3,4;4前面有个比它小的3,所以
d(3)=d(2)+1)
前4个数的LIS长度
d(4)=3(序列:3,4,8;8前面比它小的有3个数,所以
d(4)=max{d(1),d(2),d(3)}+1=3)
OK,分析到这,我觉得状态转移方程已经很明显了,如果我们已经求出了d(1)到d(i-1), 那么d(i)可以用下面的状态转移方程得到:
d(i)=max1,d(j)+1 , 其中j<i , A[j]<=A[i]
用大白话解释就是,想要求d(i),就把i前面的各个子序列中, 最后一个数不大于A[i]的序列长度加1,然后取出最大的长度即为 d(i) 。 当然了,有可能i前面的各个子序列中最后一个数都大于 A[i],那么 d(i)=1, 即它自身成为一个长度为 1 的子序列。
类似于一种松弛的思想
传送门里的代码没有给出位置,我加上了位置信息,感觉好费劲。
原文的代码
#include <iostream> using namespace std; int lis(int A[], int n){ int *d = new int ; int len = 1; for(int i=0; i<n; ++i){ d[i] = 1; for(int j=0; j<i; ++j) if(A[j]<=A[i] && d[j]+1>d[i]) d[i] = d[j] + 1; if(d[i]>len) len = d[i]; } delete[] d; return len; } int main(){ int A[] = { 5, 3, 4, 8, 6, 7 }; cout<<lis(A, 6)<<endl; return 0; }
第一种方法给出了位置信息,第二种O(NlogN)算法怎样给出位置信息,如果大家知道可以告诉我哈,谢谢
#include <iostream> #include <cstdlib> using namespace std; //在非递减序列 O(NlogN)算法 arr[s..e](闭区间)上二分查找第一个大于等于key的位置,如果都小于key,就返回e+1 int upper_bound(int arr[], int startPos, int endPos, int key) { int mid; //如果最长串大小endPos的结束值比待插入的值小 if (arr[endPos] <= key) { //最长串的数量加1 相当于d[j]+1>d[i] return endPos + 1; } //二分查找 因为保存最大长度的数组是有序的 while (startPos < endPos) { mid = startPos + (endPos - startPos) / 2; if (arr[mid] <= key) { startPos = mid + 1; } else { endPos = mid; } } return startPos; } int LIS(int A[], int n) { int i = 0, len = 1, *end = new int[n+1]; end[1] = A[0]; //初始化:长度为1的LIS末尾为A[0] int temp[n+1]= {0}; int pos=-1; for (i = 1; i < n; i++) { pos = upper_bound(end, 1, len, A[i]); //找到插入位置 temp[i]=end[pos];//记录删除的元素 end[pos] = A[i]; if (len <= pos) { len = pos;//按需要更新LIS长度 } } //输出位置 pos=len; int posTemp=len; int maxNum=end[pos]; //寻找最大值 while(pos!=0) { if(end[pos]<temp[posTemp]&&temp[posTemp]<maxNum) { cout<<temp[posTemp]<<endl; maxNum = temp[posTemp]; pos--; } else { cout<<end[pos]<<endl; maxNum = end[pos]; pos--;//比较下一个位置的结果 if(temp[posTemp]>maxNum) { posTemp=pos;//更新位置 } } //cout<<"\t maxNum is "<<maxNum<<"\t end["<<pos+1<<"] is "<<end[pos+1]<<"\t temp["<<pos+1<<"] is "<<temp[pos+1]<<"\t posTemp is "<<posTemp<<endl; } return len; } //o(N^2) 复杂度N^2 int lis(int A[], int n) { int *d = new int ; int len = 1; int position = {0}; //记录d[i]是由哪来的 int endPos=-1;//结束位置 for(int i=0; i<n; ++i) { d[i] = 1; //计算d[i] for(int j=0; j<i; ++j) { if(A[j]<=A[i] && d[j]+1>d[i]) { d[i] = d[j] + 1; position[i]=j; } } //更新最长长度 if(d[i]>len) { len = d[i]; endPos=i; } cout<<"d["<<i<<"]\t"<<d[i]<<"\t pos["<<i<<"]\t"<<position[i]<<endl; } //输出序列 while(d[endPos]!=1) { cout<<A[endPos]<<endl; endPos=position[endPos]; } cout<<A[endPos]<<endl; //释放空间 delete[] d; return len; } int main() { int A[] = {5, 3, 4, 8, 6, 7};// { 10,22,9,33,88,50,41,60,80};// cout<<"Length of LIS is "<<lis(A, sizeof(A)/sizeof(int))<<endl; cout<<"Length of LIS is "<<LIS(A, sizeof(A)/sizeof(int))<<endl; return 0; } /* 80 maxNum is 80 end[6] is 80 temp[6] is 50 posTemp is 6 60 maxNum is 60 end[5] is 60 temp[5] is 88 posTemp is 6 50 maxNum is 50 end[4] is 41 temp[4] is 4 posTemp is 6 33 maxNum is 33 end[3] is 33 temp[3] is 0 posTemp is 2 22 maxNum is 22 end[2] is 22 temp[2] is 10 posTemp is 2 10 maxNum is 10 end[1] is 9 temp[1] is 6553936 posTemp is 2 Length of LIS is 6 */
lis算法的时间复杂度是O(N^2 ),并不是最优的解法。LIS是O(NlogN)。
相关文章推荐
- [网络流24题] 最长递增子序列问题 最大流/
- LIS(最长递增子序列)
- 【C++】动态规划:最长递增子序列和建桥问题
- codevs1906 最长递增子序列问题
- HDU 3308——LCIS(线段树,区间合并,最长连续递增子序列)
- 最长递增子序列LIS的O(nlogn)的求法
- hdu 5773 最长递增子序列的应用---随意改变为0的数
- 153 最长递增子序列
- 最长递增子序列
- 杭电oj1087最长递增子序列java实现
- leetcode Longest Increasing Subsequence 最长递增子序列
- hdu1069长方体堆砌.如何转化为最长递增子序列
- 2.16 求数组中最长递增子序列
- CODE(VS) 1044 导弹拦截 (判断最长递增和最长递减子序列)
- 求数组中最长递增子序列
- 华为OJ2288-合唱队(最长递增子序列)
- CDOJ 251 导弹拦截 最长递增子序列
- 编程之美之最长递增子序列实现
- 数组中最长递增子序列问题研究(2)
- 防御导弹 最长递增子序列