一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ..., aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
思路:dp,即求N个序列的这个状态可以由K(1<=k<=n-1)个序列的状态推导得出
dp[n]:表示以第n个元素结尾的子序列的最长序列长度
if a[n]>a[k] dp[n] = max{1,1 + dp[k]} (1<=k<=n-1)
解法一:O(n2)
N2的时间复杂度关键在于每次求第N个元素的时候都遍历一遍前N-1个元素,然后取最大值。
解法二:O(nlgn)
nlgn的快速之处在于不是每次去遍历前N-1个元素,而是从一开始就维护一个栈,这个栈中保存的是当前序列的“伪最长子序列”。
举例:原序列为1,5,8,3,6,7
栈为1,5,8,此时读到3,用3替换5,得到1,3,8;
再读6,用6替换8,得到1,3,6;
再读7,得到最终栈为1,3,6,7。
最长递增子序列为长度4。
可以发现栈中的序列不一定是正确的,但是长度肯定是正确的。
因为维护栈的策略是(假设栈顶元素是top, 新元素是temp)
if temp > top, temp入栈
if temp <= top, 首先找出第一个大于等于temp的栈中元素,并用temp替代它。
public void LIS(int[] nums){ int[] stack = new int[nums.length]; int size = 0; int[] dp = new int[nums.length]; for(int i = 0; i < nums.length; i ++){ if(i == 0){ dp[i] = 1; stack[size] = nums[i]; size ++; } else{ if(nums[i] > stack[size - 1]){ stack[size] = nums[i]; size ++; dp[i] = size; } else if(nums[i] == stack[size - 1]){ dp[i] = size; } else{ int low = 0; int high = size - 1; while(low <= high){ int mid = (low + high) / 2; if(stack[mid] == nums[i]){ high = mid - 1; } else if(stack[mid] > nums[i]){ high = mid - 1; } else{ low = mid + 1; } } stack[low] = nums[i]; dp[i] = low + 1; } } } for(int i = 0; i < size; i ++){ System.out.print(stack[i]+" "); } }