算法 : 最长升序降序序列

引用 :算法:最长升序降序序列,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] 表示从 1i 这一段中以 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.: 如果有不准确地方欢迎指出,如果有更好的处理,麻烦教我,多多交流!~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值