给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
来源:力扣(LeetCode)
方法一:
时间复杂度为O(n2),思路很直接,采用动态规划方法记录每个数字作为结尾时的最长上升子序列长度。当遍历到nums[i]时,向前查找所有nums[j]<nums[i]。并记录nums[j]对应的最长上升子序列长度中最大值,得到nums[i]做结尾时的最长长度。因为遍历过程中向前反向遍历,时间复杂度O(n2)。
public int lengthOfLIS(int[] nums) {
int len=nums.length;
if(len<1){
return 0;
}
int max=0;
int[]dp=new int[len];
dp[0]=1;
for(int i=0;i<len;i++){ //遍历数组
int maxV=0;
for(int j=i-1;j>=0;j--){ //向前遍历查找
if(nums[j]<nums[i]){
maxV=Math.max(maxV,dp[j]);
}
}
dp[i]=maxV+1;
max=Math.max(max,dp[i]);
}
return max;
}
方法二:
这是提示我们进行优化的方法,看到log n,想到可能是用二分查找方法。但是二分查找必须是有序数组中才能使用,那么哪里是有序的呢?这里采用辅助数组d[i]来记录长度为i的子序列的末尾的最小数值。
根据定义可以反证,当i>j时,一定有d[i]>d[j]。即这个数组时单调的,所以可以使用二分查找。
假设当前上升序列最长为end,当遍历到nums[i]时,比较d[end]和nums[i]。
如果d[end]<nums[i],那么可以直接添加到后面构成更长的序列,即d[end+1]=nums[i]。
否则就向前二分查找最大的d[j]<nums[i],那么可以令d[j+1]=nums[i]。
这样整个复杂度就降低到 O(n log n) 。
//贪心算法加二分查找
public int lengthOfLIS2(int[] nums){
int len=nums.length;
if(len<1){
return 0;
}
int[] d=new int[len];
int end=0; //记录序列当前最长长度
d[0]=nums[0]; 初始长度为1,这里以i+1表示长度
for(int i=1;i<len;i++){
if(nums[i]>d[end]){
d[end+1]=nums[i];
end++; //如果找到大的数值,延长序列
}else {
int index=biSearch(d,end,nums[i]); //二分查找
d[index+1]=nums[i];
}
}
return end+1; //长度是i+1
}
public int biSearch(int[] nums, int end, int n){
int begin=0;
int mid=0;
if(n<=nums[0]){
return -1; //如果查找的值比最小值还小,返回-1,更新下标0处数值
}
while(begin<end){
mid=(begin+end+1)/2;
if(nums[mid]>=n){
end=mid-1;
}else if(nums[mid]<n){
begin=mid;
}
}
return end;
}
注解:
关于二分查找的边界条件
二分查找的问题很常见,我以为自己很熟悉了。但是一直没有注意过总结边界条件问题,总是有一些特殊case下造成死循环等问题。
/**
* 查找固定值
*/
public static int binarySearch(int[] nums, int n){
int begin=0;
int end=nums.length-1;
int mid=0;
if(n<nums[0]||n>nums[end]){
return -1;
}
while(begin<=end){
mid=(begin+end)/2;
if(nums[mid]==n){
//相等的就返回对应下标
return mid;
}else if(nums[mid]>n){
end=mid-1;
}else if(nums[mid]<n){
begin=mid+1;
}
}
return -1;
}
/**
* 查找第一个小于给定值的位置
*/
public static int binarySearch2(int[] nums, int n){
int begin=0;
int end=nums.length-1;
/*首先要保证初始情况下能满足要求
即begin和左边都是小于的,end和右边都是大于等于的
*/
if(nums[0]>=n){
return -1;
}
if(nums[end]<n){
return end;
}
int mid=0;
while(begin<end){
mid=(begin+end+1)/2;
if(nums[mid]>=n){
end=mid-1;
}else {
begin=mid;
}
}
return end;
}
/**
* 查找第一个大于给定值的位置
*/
public static int binarySearch3(int[] nums, int n){
int begin=0;
int end=nums.length-1;
/*
首先要保证初始情况下能满足要求
即begin和左边都是小于等于的,end和右边都是大于的
*/
if(nums[0]>n){
return 0;
}
if(nums[end]<=n){
return end+1;
}
int mid=0;
while(begin<end){
mid=(begin+end)/2;
if(nums[mid]>n){
end=mid;
}else {
begin=mid+1;
}
}
return begin;
}
如果begin存在不移动的情况,mid=(begin+end+1)/2;如果有变存在不移动情况,mid=(begin+end)/2;避免两个数字的时候原地循环。