最长递增(不减)子序列

问题:

拿POJ 2533来说。

Sample Input

7
1 7 3 5 9 4 8

Sample Output(最长上升/非降子序列的长度)

4

解法一(O(n^2)):

如何把这个问题分解成子问题呢?经过分析,发现 “求以ak(k=1, 2, 3…N)为终点的最长上升子序列的长度”是个好的子问题――这里把一个上升子序列中最右边的那个数,称为该子序列的“终点”。虽然这个子问题和原问题形式上并不完全一样,但是只要这N个子问题都解决了,那么这N个子问题的解中,最大的那个就是整个问题的解。

由上所述的子问题只和一个变量相关,就是数字的位置。因此序列中数的位置k 就是“状态”,而状态 k 对应的“值”,就是以ak做为“终点”的最长上升子序列的长度。这个问题的状态一共有N个。状态定义出来后,转移方程就不难想了。假定MaxLen (k)表示以ak做为“终点”的最长上升子序列的长度,那么:

MaxLen (1) = 1

MaxLen (k) = Max { MaxLen (i):1<i < k 且 ai < ak且 k≠1 } + 1

这个状态转移方程的意思就是,MaxLen(k)的值,就是在ak左边,“终点”数值小于ak,且长度最大的那个上升子序列的长度再加1。因为ak左边任何“终点”小于ak的子序列,加上ak后就能形成一个更长的上升子序列。

实际实现的时候,可以不必编写递归函数,因为从 MaxLen(1)就能推算出MaxLen(2),有了MaxLen(1)和MaxLen(2)就能推算出MaxLen(3)……

解法二(O(nlog(n))):

这个算法其实已经不是DP了,有点像贪心。至于复杂度降低其实是因为这个算法里面用到了二分搜索。本来有N个数要处理是O(n),每次计算要查找N次还是O(n),一共就是O(n^2);现在搜索换成了O(logn)的二分搜索,总的复杂度就变为O(nlogn)了。

这个算法的具体操作如下(by RyanWang):

开一个栈,每次取栈顶元素top和读到的元素temp做比较,如果temp > top 则将temp入栈;如果temp < top则二分查找栈中的比temp大的第1个数,并用temp替换它。 最长序列长度即为栈的大小top。

这也是很好理解的,对于x和y,如果x < y且Stack[y] < Stack[x],用Stack[x]替换Stack[y],此时的最长序列长度没有改变但序列Q的‘‘潜力‘‘增大了。

举例:原序列为1,5,8,3,6,7

栈为1,5,8,此时读到3,用3替换5,得到1,3,8; 再读6,用6替换8,得到1,3,6;再读7,得到最终栈为1,3,6,7。最长递增子序列为长度4。

最终栈里的元素不是符合要求的子序列,所以该方法不能实现打印。

#include <iostream>
#include <vector>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <assert.h>
using namespace std;

std::vector<int> v;
std::vector<int> seq;
int len;
int record[10000];
int before[10000];
int stack[10000];
int stackSize;
bool bujian=false;
void input(){
cin >> len;
for(int i = 0 ; i < len ; i++){
int a;
cin >> a;
v.push_back(a);
record[i] = 0;
before[i] =-1;
}
stackSize = 0;
}

void output(){
cout << len<<endl;
for(int i = 0 ; i < v.size() ; i++){
cout << v[i]<<" ";

}
}

//O(n^2)
int algorithm1(){
record[0] = 1;

for (int i = 1 ; i < len; i++){
for (int j = 0 ; j < i; j++){
if (bujian){
if (v[j] <= v[i] && (record[i] < (record[j]+1)) ){//不减版本
record[i] = record[j]+1;
before[i] = j;
}
}
else{
if (v[j] < v[i] && (record[i] < (record[j]+1)) ){//不减版本
record[i] = record[j]+1;
before[i] = j;
}
}

}
}

int maxx = -1;
int finalIndex;

for (int i = 0 ; i < len; i++){
if (record[i] > maxx){
maxx = record[i];
finalIndex = i;
}
}

while(finalIndex!=-1){
seq.push_back(v[finalIndex]);
finalIndex = before[finalIndex];
}
reverse(seq.begin(), seq.end());

// for (int i = 0 ; i < len; i++){
// cout << record[i]<< " ";
// }
// cout << endl;

return maxx;
}

//不减版本
int binSearch(int s,int e, int value){

if (e-s == 1){
if (stack[s] > value){
return s;
}
else{
return s+1;
}

}
int middle = (e+s)/2;
if (value == stack[middle]){
if (bujian) while(value == stack[++middle]){}//这里决定是否要递增,或者不减.注释为递增
return middle;
}
if(value < stack[middle]){
assert(s!=middle);
return binSearch(s,middle, value);
}
else{
return binSearch(middle,e, value) ;
}
}

//O(nlog(n))
int algorithm2(){

stackSize++;
stack[stackSize-1] = v[0];

for (int i = 1 ; i < len; i++){
if (bujian){
if(v[i] >= stack[stackSize-1] ){//这里决定是否要递增,或者不减.>为递增,>=为不减
stackSize++;
stack[stackSize-1] = v[i];
}
else{
int index = binSearch(0,stackSize,v[i]);
stack[index] = v[i];
}
}
else{
if(v[i] > stack[stackSize-1] ){//这里决定是否要递增,或者不减.>为递增,>=为不减
stackSize++;
stack[stackSize-1] = v[i];
}
else{
int index = binSearch(0,stackSize,v[i]);
stack[index] = v[i];
}
}

}

return stackSize;
}

int main(){

input();
int a1 = algorithm1();
int a2 = algorithm2();
if (a1!=a2){
cout << "wrong! a1="<<a1<< " a2="<<a2<<endl;
for (int i = 0 ; i < a1 ; i++){
cout << seq[i]<<" ";
}
}
else{
cout <<"max length is " << a1<<endl;
for (int i = 0 ; i < a1 ; i++){
cout << seq[i]<<" ";
}
}
//output();
return 0;
}

时间: 2024-08-06 07:57:10

最长递增(不减)子序列的相关文章

最长递增子序列和网易去除最少使从左向右递增又递减问题

(1)最长递增子序列问题 有两种方法:(1)动态规划方法(2)类似二分查找的方法O(nlogn) 动态规划方法: 以i结尾的序列的最长递增子序列和其[0, i - 1]“前缀”的最长递增子序列有关,设LIS[i]保存以i结尾的最长递增子序列的长度:     若i = 0,则LIS[i] = 1:     若i > 0,则LIS[i]的值和其[0, i - 1]前缀的最长递增子序列长度有关,用j遍历[0, i - 1]得到其最长递增子序列为LIS[j],对每一个LIS[j],如果序列array[j

HDU 3998 Sequence (最长递增子序列+最大流SAP,拆点法)经典

Sequence Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 1666    Accepted Submission(s): 614 Problem Description There is a sequence X (i.e. x[1], x[2], ..., x[n]). We define increasing subsequ

算法面试题 之 最长递增子序列 LIS

找出最长递增序列 O(NlogN)(不一定连续!) 参考 http://www.felix021.com/blog/read.php?1587%E5%8F%AF%E6%98%AF%E8%BF%9E%E6%95%B0%E7%BB%84%E9%83%BD%E6%B2%A1%E7%BB%99%E5%87%BA%E6%9D%A5 我就是理解了一下他的分析 用更通俗易懂的话来说说题目是这样 d[1..9] = 2 1 5 3 6 4 8 9 7 要求找到最长的递增子序列首先用一个数组b[] 依次的将d里面

[网络流24题]最长递增子序列问题

题目大意:给定长度为n的序列a,求:1.最长递增子序列长度:2.最多选出几个不相交的最长递增子序列:3.最多选出几种在除了第1个和第n个以外的地方不相交的最长递增子序列.(n<=1000) 思路:先倒着DP,求出f[i]表示以a[i]开头的最长的递增子序列长度,然后建图,若f[i]=最长递增子序列长度则S向i连1,若f[i]=1则i向T连1,若i<j且a[i]<a[j]且f[i]=f[j]+1则i向j连1,为保证每个点只被流一次,拆成入点和出点,流量限制1,跑最大流即可解决第二问,点1和

最大子数组之和、最大子数组之积、最长递增子序列求法

昨天做爱奇艺笔试题,最后一道编程题是求整型数组最长递增子序列,由于时间关系,没有完全写出来,今天重新来做做这一系列题. <1> 最大子数组之和 首先从最简单的最大子数组之和求取.数组里有正数.负数.零.设包含第 i 个元素的子数组的和为 Sum,则Sum的值为 Sum(i) = Sum(i-1) + arrey[i]; 显然如果arrey[i]<=0,则Sum(i)<=Sum(i-1);则必须把Sum(i)=arrey[i];同时maxSum用来保存Sum最大值.时间复杂度为o(n

求数组中最长递增子序列

编程之美有一道关于数组中最长递增子序列,题目如下: 写一个时间复杂度尽可能低的程序,求一个一维数组(N个元素)中最长递增子序列的长度. 例如在序列1,-1,2,-3,4,-5,6,-7中,其最长的递增子序列的长度为4(如1,2,4,6),从该书给的例子我们可以知道的是其最长的递增子序列可以不连续的. 作者利用动态规划方法给了三种解法. 解法一: 根据无后效性的定义,各阶段按照一定的次序排列好之后,对于某个给定阶段的状态来说,它以前各阶段的状态无法直接影响它未来的决策,而只能间接地通过当前状态来影

LIS 最长递增子序列问题

一,    最长递增子序列问题的描述 设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm.求最大的m值. 比如int* inp = {9,4,3,2,5,4,3,2}的最长递减子序列为{9,5,4,3,2}: 二,解决: 1.用一个临时数组tmp保存这样一种状态:tmp[i]表示以i为终点的递增序列的长度: 比如inp =

hdu1087最长递增子序列

原题地址 简单dp题,LIS.不同之处是这里要求得的不是最长的子序列,而是权重和最长的子序列.其实大同小异. 状态数组就是到达每个位置的最大权重. LIS问题常用解法就是两个: 人人为我 我为人人 本题我用了我为人人的思路 .就是确定子序列起点,把其后面每一个大于它的值的位置的状态数组更新. #include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using name

最长递增子序列 LIS 时间复杂度O(nlogn)的Java实现

关于最长递增子序列时间复杂度O(n^2)的实现方法在博客http://blog.csdn.net/iniegang/article/details/47379873(最长递增子序列 Java实现)中已经做了实现,但是这种方法时间复杂度太高,查阅相关资料后我发现有人提出的算法可以将时间复杂度降低为O(nlogn),这种算法的核心思想就是替换(二分法替换),以下为我对这中算法的理解: 假设随机生成的一个具有10个元素的数组arrayIn[1-10]如[2, 3, 3, 4, 7, 3, 1, 6,