问题描述:
给出一个未排序队列nums,如[10, 9, 2, 5, 3, 7, 101, 18]。找出其中最长的增长序列,但不是连续增长序列,如[2, 3, 7, 101]就是对应的最长增长序列LIS,因为序列不唯一,所以要求返回的是长度,如4.
一.动态规划 O(n^2):
比较容易想到的就是复杂度为O(n^2)的算法。这是一个备忘录算法,也是动态规划算法。需要建立一个备忘录dp,备忘录dp[i]记录序列从下标0到下标i最长的子序列长度。对于dp[j]的值则需要在nums序列红中找到0到(j-1)所有比nums[j]小元素,并在这些元素中选择备忘录dp值最大的一个如dp[k],则dp[j]=dp[k]+1;
public int lengthOfLIS(int[] nums) {
if(nums==null || nums.length==0)
return 0;
int[] bigLength=new int[nums.length];
for(int i=0;i<nums.length;i++) bigLength[i]=1;
int maxLength=1;
for(int i=1;i<nums.length;i++){
//从前面找到比 nums[i]小,且dp值最大的那个。加1便是当前的值
for(int j=0;j<i;j++){
if(nums[j]<nums[i])
bigLength[i]=Math.max(bigLength[j]+1,bigLength[i]);
} maxLength=Math.max(bigLength[i],maxLength);
}
return maxLength;
}
二 .二分法查找O(nlg(n))
首先建立一个栈stack来存储遍历到当前时刻 i 的一个最长递增序列(栈内是递增序列,但概念和题目中的递增序列不同)。设当前时刻为 i 则
1.如果元素 i 比栈顶元素大则入栈,stack[top++]=nums[i+1];
2.如果元素 i 比栈顶元素小,则在栈中采用二分查找法找到一个位置j 替换成当前元素nums[i] 。 该做法的目的是如果出现小元素就往栈内部替换,当前替换的结果影响下一次的替换,特别是栈顶元素。栈顶元素的替换需要比较stack[top],stack[top--]及nums[i]三个的值。
3.最后输出 栈的长度。
public int lengthOfLIS(int[] nums) {
if(nums==null || nums.length==0)
return 0; int[] stack= new int[nums.length];
int top=0; for(int num:nums){
if(top==0 || stack[top-1]<num) stack[top++]=num;
else{
//如果在栈中没有对应的元素,则将找到的插入坐标为 j 返回-j-1. 如果找到则返回对应的坐标位置。
int i=Arrays.binarySearch(dp,0,top,num);
i= i<0? -i-1:i;
dp[i]=num;
}
}
return top;
}
另外网上有关于只用栈没用利用二分法查找法的做法,使得复杂度变为O(n),试了下是不行的。他大概的思路是:
1.如果当前栈为空或栈顶元素小于当前元素nums[i],则入栈
2.如果nums[i]<stack[top] 且 nums[i]>stack[top-1] 则替换栈顶元素,stack[top]=nums[i]。
这种做法忽略了stack[top-1]之前的元素对stack[top-1]的影响。算法代码如下:
public int lengthOfLIS(int[] nums) {
int len=nums.length;
Stack<Integer> stack=new Stack<Integer>();
for(int i=len-1; i>=0; i--)
{
if(stack.isEmpty())
{
stack.push(nums[i]);
}else
{
int val= stack.pop();
if(stack.isEmpty())
{
if(nums[i]>=val)
{
val=nums[i];
}else
{
stack.push(val);
val=nums[i];
}
}else
{
int up=stack.peek();
if(nums[i]<val)
{
stack.push(val);
val=nums[i];
}else if(nums[i]>val && nums[i]<up)
{
val=nums[i];
}
}
stack.push(val);
}
}
return stack.size();
}