什么是最长上升子序列
网上流传着一个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之间。因为求的是最长上升子序列,而你的序列是235,这样显然是不符合题意的。
所以这样思考,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)进行比较,如果a[i]>a[k],那就是dp[i]=dp[k] + 1,如果相等就是dp[i] = dp[k],如果比他小就只能弃掉了
上代码
//朴素dp o n^2
int 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();
}