写在前面:仅为个人代码/总结,未必标准,仅供参考!如有错误,还望指出交流,共同进步!
最长单调递增子序列
【题目描述】
找出由n个数组成的序列中的最长单调递增子序列及其长度。
【O(n*n)算法解题思路】
思路一:用数组 b[0:n-1]记录以a[i] (0≤i<n) 为结尾元素的最长递增子序列的长度。序列a的最长递增子序列的长度为max {b[i]} (0≤i<n) 。易知,b[i]满足最优子结构性质,可以递归定义为:b[0]=1, b[i]=max{b[k]}+1(0≤k≤i)。
思路二:将原问题转化为求最长公共子序列问题,求原序列和升序排序后序列的最长公共子序列。
【O(nlogn)算法解题思路】
用归纳解此问题。归纳假设是:已知计算序列a[0:i-1] (i<n)的最长递增子序列的长度的正确算法。从初始情况开始,对于长度为n的序列a[0:n-1],设法转换为求长度小于n的序列的最长递增子序列的长度。
定义P:(0≤i<n) k是序列a[0:i]的最长递增子序列的长度; b[K]是序列a[0:i]中所有长度为k的递增子序列中的最小结尾元素值。若w等于某一个b[k]的值,则pre[w]用于存储排在w前面的所有长度为k-1的递增子序列中的最小结尾元素值。
具体解题步骤如下:
在由i-1到i的循环中,
①当a[i]>b[k]时,k=k+1, b[k]=a[i],否则k值不变。
②当a[i]≤b[k]时,如果a[i]<b[1].则将b[1]的值改变为a[i]。当b[1]≤a[i]≤b[k]时,用二分搜索算法找到有序的数组b下标j,使得b[i-1]≤a[i]<b[j]。b[j-1]<a[i]<b[j]时, b[1:j-1]和b[j+1:k]的值不变, b[j]的值改变为a[i]。b[j-1]==a[i]时,保持不变。
③对于记录最长递增子序列,可在求得每一个b[j]时用pre[b[i]]记录b[j]的前驱数据为b[j-1],即排在b[j]前面的序列a[0:i-1]的所有长度为j-1的递增子序列中的最小结尾元素值。
【解题过程遇到的问题】
问:为什么步骤的第②点中当b[j-1]==a[i]时要保持不变?
答:来个例子,比如序列1 2 3 2,当i递增到3时,发现a[i]==b[2],但如果将b[3]改为a[i],这会得到2是长度为3的递增子序列中的最小结尾元素值,这虽然不影响求得原序列的最长单调递增子序列长度为3,但如果要根据数组b来记录最长单调递增子序列则行不通,因为将b[3]改为2后已不满足b[k]的定义,2并不是a[0…3]中长度为3的单调递增子序列的最小结尾值,2,2不构成递增关系。
【样例解答】
【实现代码】
#include <bits/stdc++.h>using namespace std;int a[101]={0};int b[101]={0};int pre[101]={0};int n;stack <int> S;//k是序列a[0:i]的最长递增子序列的长度//b[k]是序列a[0:i]中所有长度为k的递增子序列中的最小结尾元素值//假若w等于某一个b[k],则pre[w]存排在w前面的所有长度为k-1的递增子序列中的最小结尾元素值int binary(int i,int k){int u=k;if(a[i]<b[1]){return 1;}//二分法查找满足b[j-1]<=a[i]<b[j]的jfor(int h=1,j=k;h!=j-1;){if(b[k=(h+j)/2]<=a[i])h=k;elsej=k;u=j;}return u;}int LIS(){int v;b[1]=a[0];for(int i=1,k=1;i<n;i++){if(a[i]>b[k])//a[i]>b[k]{b[++k]=a[i];pre[a[i]]=b[k-1];}else//a[i]≤b[k]{int m=binary(i,k);if(a[i]!=b[m-1]){pre[a[i]]=b[m-1];b[m]=a[i];}}v=k;}return v;}int main(){cin>>n;for(int i=0;i<n;i++){cin>>a[i];}int res=LIS();cout<<res<<endl;int index=b[res];S.push(index);while(pre[index]!=0){S.push(pre[index]);index=pre[index];}while(!S.empty()){cout<<S.top()<<' ';S.pop();}return 0;}/*157 2 5 6 3 4 9 8 12 13 26 14 30 20 21*//*72 4 3 5 6 4 8*//*812 13 14 1 2 3 2 4*/