最长上升子序列(Longest Increasing Subsequence)

原创 2013年12月03日 15:30:51

问题:

给出一个序列a1,a2,a3,a4,a5,a6,a7….an,求它的一个子序列(设为s1,s2,…sn),使得这个子序列满足这样的性质,s1<s2<s3<…<sn并且这个子序列的长度最长。输出这个最长的长度。(为了简化该类问题,我们将诸如最长下降子序列及最长不上升子序列等问题都看成同一个问题,其实仔细思考就会发现,这其实只是<符号定义上的问题,并不影响问题的实质)
例如有一个序列:1 7 3 5 9 4 8,它的最长上升子序列就是 1 3 4 8 长度为4.

分析:
对于几个给定的数字
a1、a2、... an,
必然存在最长上升子路径。有了路径,那么对于路径上的非起始节点ai而言,肯定会存在一个节点是排在ai前面的,不妨记为prev(ai),叫做前驱节点
现在,可以给出问题中的最优子结构了:到达节点ai的最长递增路径,包含了到达这条路径上的每一个节点的最长递增路径。自然地,最长递增路径也就包含了到达前驱节点的最长递增路径。至于证明,可以用反证法简单地做到。
设L(ai)为到达节点ai的最长递增子序列的长度,并且它一般可以定义为
L(ai)=1+max{L(aj), aj<-prev(ai)}。
用文字描述就是:到达节点ai的最长递增子序列的长度,比所有可能的路径上的到达前驱节点aj的子序列的长度中的最大值还要多一。

例子:

原始数据为  5 2 8 6 3 6 9 7 

那么赋予它们的每一个数的初始长度均为0,所以辅助数组的元素全为0并且长度为8。接下来从5出发,对于所有比5大的数,即可以作为5的后续节点,修改为1。那么经过第一轮修改后,辅助数组变成
0 0 1 1 0 1 1 1。
而对于从2开始的情况,在修改时需要注意一点:当发现后续节点所对应的长度已经比先有长度加上一还要大时,就不应该修改了。因为根据最优解的值的递归定义,每一个节点对应的最长子序列的长度在构造时所选取的都是前驱节点中的长度的最大值。所以,第二轮变换后辅助数组变成了
0 0 1 1 1 1 1 1。
变化不大,只是第五个数字3对应的长度变成了1而已。这是对的,因为在3之前,能够作为前驱节点的也只有2而已,所以它们之间的距离是1,加上原来2对应的长度0,还是1。剩下的步骤和之前进行的这两步是一致的,于是到最后,辅助数组就会变成
0 0 1 1 1 2 3 3。
于是我们知道,在数字中拥有最大的长度的,也就是到达它们的递增子序列最长的数字是9和7。那么现在有了两个数组,一个是原来的输入,另一个则是计算出来的辅助数组,通过这两个数组,就可以得到在输入中所存在的至少一个最长递增子序列了。方法也很简单,首先需要找出在辅助数组中具有最大值的元素的下标是几。显然,下标是6和7(从0开始计算的下标),选择一个来演示就可以了,例如6。那么从下标为6的元素位置开始,可以知道它对应的子序列的长度为3,那么它的前驱对应的子序列的长度应该为2,于是将下标不停地减小,直到找到这样的一个数:这个数本身要比测试的数字小,并且它对应的长度恰好比测试的数字对应的长度小一。并且这样一直进行下去,直到找到了的数字对应的长度为0为止。此时把这个最后的数字加入已经得到的序列中,就可以得到在输入中存在的“最长递增子序列”了。

代码:

算法复杂度:O(n^2)

public class LIS {
    
    public static int getLIS(int arr[], int n) {
        int dp[] = new int[n];
        for (int i = 0; i < n; i++) {
            dp[i] = 0;
        }   
        int ans = 0;
        for (int i = 0; i < n-1; i++) {
            ans = dp[i];
            for (int j = i+1; j < n; j++) {
                if (arr[i] < arr[j] && dp[j] < ans+1) {
                    dp[j] = ans+1;
                }                    
            }
        }
        ans = 0;
        for (int i = 0; i < n; i++) {
            if(dp[i] > ans) {
                ans = dp[i];
            }
        }
        return ans;
    }
}
不过,这个方法只是得到最长子序列的长度,并没有实现得到最长子序列。




版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

最长递增子序列详解(longest increasing subsequence)

一个各公司都喜欢拿来做面试笔试题的经典动态规划问题,互联网上也有很多文章对该问题进行讨论,但是我觉得对该问题的最关键的地方,这些讨论似乎都解释的不很清楚,让人心中不快,所以自己想彻底的搞一搞这个问题,...

Longest Increasing Subsequence (LIS) 的java实现

最长递增子序列 Longest Increasing Subsequence (LIS) 的java实现

Java实现O(nlogn)最长上升子序列

问题描述:给出一个序列a1,a2,a3,a4,a5,a6,a7....an,求它的一个子序列(设为s1,s2,...sn),使得这个子序列满足这样的性质,s1

“最长上升子序列,最大连续子序列和,最长公共子串”的Java实现

一、问题描述 这是三道典型的dp问题。 最长上升子序列:在一列数中寻找一些数,这些数满足:任意两个数a[i]和a[j],若i 设dp[i]表示以i为结尾的最长递增子序列的长度,则状态转移方程为:dp...

点分线,线分平面,平面分空间.

题目:在一个平面上画1999条直线最多能将这一平面划分成多少个部分?分析:当有n-1条直线时,最多将平面分割成f(n-1)个平面,则要使第n条直线切成的区域数最多,就必须与每条直线相交且不能有同一交点...
  • wcyoot
  • wcyoot
  • 2011-05-17 16:02
  • 1440

最长上升子序列nlogn算法

这题目是经典的DP题目,也可叫作LIS(Longest Increasing Subsequence)最长上升子序列 或者 最长不下降子序列。很基础的题目,有两种算法,复杂度分别为O(n*logn...

白话算法之【动态规划入门】

动态规划入门 什么是动态规划?         动态规划(Dynamic Programming,所以我们简称动态规划为DP)是运筹学的一个分支,是求解决策过程(decision process)最优...

算法题12 查找最小的k个元素

题目:输入n个整数,输出其中最小的k个。例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4。分析:这道题最简单的思路莫过于把输入的n个整数排序,这样排在最前面的k个数就...
  • wcyoot
  • wcyoot
  • 2011-05-17 13:19
  • 1111

单链表的逆向反转(V1.0)

链表的逆转算法是常规招聘中的考题,比较灵活,现在给出整个实现的代码,仅供各位参考~~~~~~~~~该算法暂时只考虑了实现功能,未进行优化,未释放指针。该算法主要的思想是:设置三个节点分别指向原来链表的...

最长递归子序列的O(N*logN)

解法一:O(N*N)数组h[N]: h[i]保存的值是:当前位置最长递归子序列的长度值 即: 依次遍历arr[i],求得所有的比arr[i]小的数arr[j1],arr[j2],......
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)