问题:给定数组arr,返回数组arr的最长递增子序列
分析:
1)求最长递增子序列,首先搞懂子序列,而不是子数组,所以就只是要求,组成的子元素按照原来的顺序排列,而不要求是连续的。
2)首先把话放这,根据题意,最长增长子序列,既然是最长的子序列,那通常来说,序列中的元素都是随机的,那么要做到最长,只有保证子序列中的元素近可能紧密的递增,这样才是最长的。原题解题思路中,紧密围绕着该思想。
3)个人解题思路:既然还是要求子序列,同样可以想象成一个滚动数组一样的解题方法,创造一个数组,在遍历原数组的过程中,放入滚动数组中的数值都是尽可能地紧密递增,那么就会在放入该数值时寻找滚动数组中小于该数值的最大值的位置的后一位,在后面的代码实现中,这个寻找过程显然可以用二分法来实现最优解,理同寻找有序数组中的固定值k。
个人思考误区:本题中,我在看原解题思路的时候,总会考虑如果遍历一部分数组后,已有的滚动数组中如[1,5,8,12,16],此时遍历到一个数值为7,最优解中对于此种情况处理为,将8顶掉换为7。
我的疑问在于此时会不会搞乱其中遍历到此时应有的最长递增子序列和实际得到的子序列,实际上并不会影响,这样处理只是为了让递增序列更紧密,因为后面的7所在的数组中的位置是index = 2,遍历到7后再遍历两个比7大的就会分别把12,16顶掉,那么此时,前面的1,5如果跳过8,12,16;
转而替代的是到7和7后面这些比7大但是比12,16小的,同样最长递增子序列长度为5,只不过是选取的序列不同,而如果是比12,16大的,那么就不会顶掉原来的数组,而是在数组里面扩充一个长度,同时让dp[n] = dp[n-1] + 1,这样我们实际得到的数组虽然是包含7而把8顶掉了,但是得到该最长长度的子序列里面是用8来计算而不是7,所以这种后面出现一个中间值,会把某一个值顶掉的解法,并不会影响整个序列最长递增子序列的值,反而是最优解法。
代码实现:
public int main(int[] arr){
//先定义俩数组,一个用于存储当前最大子序列的排列
//另一个用于存储,
int[] dp = new int[arr.length];
int[] cur = new int[arr.length];
//定义cur的左右值用于二分优化
int l = 0;
int r = 0;
int m = 0;
dp[0] = 1;
for(int i = 0; i < arr.length; i ++){
//什么情况下需要进入二分查找cur数组
//当前数值arr[i] > 序列中的最后一个,怎么知道最后一个有效值是多
//需要一个值来存储目前最长递增子序列的长度max_l
l = 0;
r = max_l;
//二分
while(l < r){
//中间值大于当前值,当前值arr[i]应该放到数组左边
if(cur[m] > arr[i]){
r = m - 1;
}else{
l = m + 1;
}
m = (l+r) >>1;
}//这里二分查找结束
max_l = Math.max(max_l, l);
cur[l] = arr[i];
dp[i] = l+1;
}
return dp[arr.lenth];//??能否做到返回最长递增子序列,个人认为这种方法只能返回长度
}