您的位置:首页 > 其它

算法 : 最长升序降序序列

2010-03-31 20:45 113 查看
引用 :算法:最长升序降序序列,7 5 6 8 10 7 9 3 8 7 4 1 8 9 4,则最长的升序序列为5, 6, 8,
10,最长的降序序列为8,7,4,1

看了一些网上的资料,最长的升序/降序 好像不是非要在彼此挨着的吧?

最长的升序序列 不可以为 :5 6 7 8 9 吗?

最长的降序序列 不可以为 :10 9 8 7 4 1 吗?



引用:Lifeng Wang
主动去创造环境,否则你无法设计人生

个人C++ 没学过,C#倒很熟悉,C嘛,大一学到指针就结课了,后面一些除了校门用得更多的东西反倒没学,悲哀啊。

再加上本人有点笨,还是看不懂 什么 时间和空间复杂度的 问题啊。。。。想学算法,还真 不容易啊。。。还望高手指点。



一、算法思想




算法还是容易想到的,两
重循环
DP

即可。不过
如果数据规模最大可以达到几十万甚至更大
,经典的
O(n^2)

的动态规划算法明显会超时。我们需要寻找更好的方法来解决是最长上升子序列问题。以
下以最长递增子序列为例进行说明:

 

 先回顾经典的
O(n^2)

的动态规划算法,设
A[i]

表示序列中的第
i

个数,
F[i]

表示从
1


i

这一段中以
i

结尾的最长上升子序列的长度,初始时设
F[i] = 0(i = 1, 2, ..., len(A))


则有动态规划方程:
F[i] = max{1, F[j] +
1} (j = 1, 2, ..., i - 1,


A[j] < A[i])



  现在,我们仔细考虑计算
F[i]

时的情况。假设有两个元素
A[x]


A[y]

,满足
(1)x < y < i (2)A[x] < A[y] < A[i]
(3)F[x] = F[y]

  此时,选择
F[x]

和选择
F[y]

都可以得到同样的
F[i]

值,那么,在最长上升子序列的这个位置中,应该选择
A[x]

还是应该选择
A[y]

呢?

  很明显,选择
A[x]

比选择
A[y]

要好。因为由于条件
(2)

,在
A[x+1] ... A[i-1]

这一段中,如果存在
A[z]


A[x] < A[z] < a[y]

,则与选择
A[y]

相比,将会得到更长的上升子序列。

  再根据条件
(3)

,我们会得到一个启示:根据
F[]

的值进行分类。对于
F[]

的每一个取值
k

,我们只需要保留满足
F[i] = k

的所有
A[i]

中的最小值。设
D[k]

记录这个值,即
D[k] = min{ A[i] } ( F[i] = k )



  注意到
D[]

的两个特点:

  
(1)

 
D[k]

的值是在整个计算过程中是单调不上升的。
//

此处需要特别注意
!!!

关键之所在
!

  
(2)

 
D[]

的值是有序的,即
D[1] < D[2] < D[3] < ... < D



利用
D[]

,我们可以得到另外一种计算最长上升子序列长度的方
法。设当前已经求出的最长上升子序列长度为
len


先判断
A[i]


D[len],


A[i] > D[len]

,则将
A[i]

接在
D[len]

后将得到一个更长的上升子序列
,len = len + 1,D[len+1] = A[i];



,


D[1]..D[len]


,

找到最大的
j,

满足
D[j] < A[i].


k = j + 1,

则有
D[j] < A[i] <= D[k]

,将
A[i]

接在
D[j]

后将得到一个更长的上升子序列
,

同时更新
D[k] = A[i].

最后,
len

即为所要求的最长上升子序列的长度。

  在上述算法中
,

若使用朴素的顺序查找在
D[1]..D[len]

查找
,

由于共有
O(n)

个元素需要计算
,

每次计算时的复杂度是
O(n),

则整个算法的时间复杂度为
O(n^2),

与原来的算法相比没有任何进步
.

但是由于
D[]

的特点
(2),

我们在
D[]

中查找时
,

可以使用二分查找高效地完成
,

则整个算法的时间复杂度下降为
O(nlogn),

有了非常显著的提高
.

需要注意的是
,D[]

在算法结束后记录的并不是一个符合题意的最长上升子序

.


个算法还可以扩展到整个最长子序列系列问题
,


个算法的难点在于二分查找的设计
,


要非常小心注意
.

二、
实现


// By Fandywang
2008.7.21

// Call: LIS(a,
n);
求最大递增/

升子序列(
如果为最大非降子序列,
只需把上面的注释部分给与替换)

const
int
N =
1001;

int

a
, f
, d
; // d[i]

用于记录a[0...i]
的最大长度

int
bsearch(const
int
*f, int
size, const
int
&a)

{


int

l=0, r=size-1;


while
( l <= r )


{


int

mid = (l+r)/2;


if
( a > f[mid-1] && a <=
f[mid] ) return
mid; // >&&<=

换为: >= && <


else
if
( a
< f[mid] ) r = mid-1;


else
l = mid+1;


}

}

int
LIS(const
int
*a, const
int
&n){


int

i, j, size = 1;


f[0]
= a[0]; d[0] = 1;


for
( i=1; i < n; ++i ){



if
( a[i] <=
f[0] ) j = 0;
// <=

换为: <


else
if
(
a[i] > f[size-1] ) j = size++;
// >

换为: >=


else
j = bsearch(f, size, a[i]);


f[j]
= a[i]; d[i] = j+1;


}


return
size;

}

三、
学以致用


JOJ 1829
Candies



最大非下降子序列

JOJ 2162
Inuyasha And the Monsters



求和最大的非
下降子序列
(

这个我
用的是
O(n^2)


算法做的
,

居然还那个
了第一
...)

JOJ 2529
Chorus




大上升子序列
+

最大下
降子序列

JOJ 1048
Wooden Sticks



先自定义排下序
(

先按
l


小到大
, l

相等再按
w

从小到大
),

然后求
w

的最大下降子序列

POJ 3636
Nested Dolls




JOJ1048




w


小到大
,w

相等
h

从大到小
,

然后求
h

最大非升子序列


p.s.:
如果有不准确地方欢迎指出,如果有更好的处理,麻烦教我,多多交流!~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: