最长上升子序列——O (nlogn)算法原因解析!为什么这样可以求出来!(附带动态规划dp + 二分查找讲解)

来源:互联网 发布:postgresql mysql 电商 编辑:程序博客网 时间:2024/05/05 10:11

什么是最长上升子序列




网上流传着一个O (nlogn)算法,大体是这样的。

模拟一个栈,如果当前的数比栈顶元素大,就要入栈,如果比栈顶元素小,就二分查找到刚好比当前数大的数,然后进行替换。本例的流程是这样的。

2 入栈 当前栈:2

5 比2大 入栈 当前栈:2 5

3 比5小 替换5 当前栈:2 3

4 比3大 入栈 当前栈: 2 3 4

1比2小 替换2 当前栈:1 3 4

7比4大 入栈 当前栈:1 3 4 7

6比7小 替换7 当前栈:1 3 4 6


为什么可以这么做?为什么这样就可以求出最大上升子序列的长度?为什么求不出最大上升子序列的最大上升序列?相信所有人看完了都会有这样一个疑问。

其实这个方法很正确,只是这样的表示形式,把人们都误导了。其实正确的思维流程应该是这样的!


初始的2


5



这时是3,比5小,创建一个分支


4只能跟在3后面


1比2小 创建一个分支


7所以的分支后面都可以加


6比7小,创建分支


这样才是正确的思维流程!你已经成功了一半了!


还有一个大问题,就是为什么要这样思考呢?(模拟一个栈,如果当前的数比栈顶元素大,就要入栈,如果比栈顶元素小,就二分查找到刚好比当前数大的数,然后进行替换。)


需要一个具体例子来分析。当目前是 2 5这个情况的时候,我们遇到了3。首先排除一种做法,3插入到2 5 之间。因为求的是最长上升子序列,而你的序列是253,这样显然是不符合题意的。

所以这样思考,23有没有可能才是真正的最优序列?所以我们25也有可能,23也有可能(实际上25已经是不可能了)

当前是2-3 2-5,遇到了4,按照我们之前的思维方式,那么4和5是不是也要比较一下?2-4难道就没有可能吗?

但是我们有一个2-3这个序列了,2-3-4这个情况显然是最优的,所以2-4直接不要了。(印证了 当前的数比栈顶元素大就要入栈 这句话)

最后分析一下1,当前 2-3-4 2-5两个序列。1比2小,当然存在这样一种可能1开头是最好的所以我们如上面的图片一样添加了1这个分支(当然本例的数据不太友好,如果我们 当前存在5-6-7,5-8两个序列呢?你碰到1的时候,完全有可能后面是234啊,所以你1完全有理由成为一个新增的分支)

例子举到这里我们就讲明白了。


所以算法就呼之欲出了。

如果你仅仅是求最长长度的需求,直接用网上的方法就可以。

如果你还需要求出这个具体序列来,毫无疑问你采用空间换时间的方法是最好的。新建一个二维数组,每当遇到要新建分支的情况,就增加一列,简单方便。具体代码我就不写了,毕竟我当前做的题仅仅是求长度而已,嘿嘿,相信理解原理的你可以轻松写出算法来。



下面讲一下这一题朴素动态规划的思路。

这一题我们分析出来的dp思路是,dp[i]代表的是包含当前点位的最长上升子序列,意思就是当前求出的最长上升子序列的最后一位,一定要是i位,这一点对动态规划有了解的人肯定明白,因为这样才能实现状态的转移。


状态是怎么转移的?

开两个循环,不断用当前的a[i]和之前的数a[k](0<k<i)进行比较,如果比他大,那就是dp[i]=dp[k] + 1,如果相等就是dp[i] = dp[k],如果比他小就只能弃掉了


上代码

//朴素dp o n^2int dynamic(int[] a) {    int n = a.length;    int[] dp = new int[n];    dp[0] = 1;    int max = 1;    for (int i = 1; i < n; i ++) {        dp[i] = 1;        for (int j = 0; j < i; j ++) {            if (a[j] < a[i]) {                dp[i] = Math.max(dp[j] + 1, dp[i]);            } else if (a[j] == a[i]) {                dp[i] = Math.max(dp[j], dp[i]);            }        }        max = Math.max(max, dp[i]);    }    return max;}

二分优化dp,关于二分查找我觉得太简单了,就不讲了,即使不查阅资料应该也要知道怎么去实现

//朴素dp优化 二分查找 模拟栈 -空间换时间//二分不难不用学就能写出来,但是要搞明白为什么要这么做int dynamic2(int[] a) {    int n = a.length;    List<Integer> list = new ArrayList<>();    list.add(a[0]);    for (int i = 1; i < n; i ++) {        int topIndex = list.size() - 1;        if (a[i] > list.get(topIndex)) {            list.add(a[i]);        } else {            int startIndex = 0;            int endIndex = topIndex;            while (true) {//end小于start退出                int index = (endIndex + startIndex) / 2;                //对本题而言,不存在相等的情况                //当前元素比中间数小                if (list.get(index) > a[i]) {                    endIndex = index - 1;                    if (endIndex == startIndex) {                        if (a[i] < list.get(startIndex)) {                            list.set(startIndex, a[i]);                        } else {                            list.set(index, a[i]);                        }                        break;                    }                    //如果当前元素比中间数大                } else if (list.get(index) < a[i]) {                    startIndex = index + 1;                    if (startIndex == endIndex) {                        list.set(startIndex, a[i]);                        break;                    }                }            }        }    }    return list.size();}

阅读全文
0 0