引用 :算法:最长升序降序序列,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[n] 。
利用 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[N], f[N], d[N]; // 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.: 如果有不准确地方欢迎指出,如果有更好的处理,麻烦教我,多多交流!~