给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例: 输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的长度是4。
说明: 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。 你算法的时间复杂度应该为 O(n2) 。 进阶:
你能将算法的时间复杂度降低到 O(nlogn) 吗?
思路一:动态规划
难得自己亲手做出一道动态规划题目。也逐渐发现了做动态规划的思路,便是自己创造一个一般复杂的用例,然后自己动手写出dp[]的值,最后总结规律。当然了,也只是目前阶段我浅薄的理解。
因为最长上升子序列,没有要求是连续的,所以对于nums[i],找到从nums[0]到nums[i-1]所有小于nums[i]的数,然后选取其中dp最大的+1即可。
- 时间复杂度:O(n2),因为嵌套两层循环
- 空间复杂度:O(n),创建一个dp数组
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums.length==0) return 0;
int[] dp = new int[nums.length];
dp[0] = 1;
for(int i=1;i<nums.length;i++){
int max = 0;
for(int j=i-1;j>=0;j--){
if(nums[j]<nums[i] && dp[j]>max){
max = dp[j];
}
}
dp[i] = max+1;
}
int res = 0;
for(int n:dp){
if(n>res) res=n;
}
return res;
}
}
思路二:二分法
进阶要求中说明可以用O(nlogn)的时间复杂度做到。不过遍历数组的O(n)是无法避免的,只能从内层循环下手了,假如dp是有序的,那么就可以用二分查找来使其复杂度降为O(logn)。但可惜不是。
所以,换个思路,设置一个数组cell
,使其有序存储当前的最长子序列。要想做到有序就要这样操作:
- 对原序列进行遍历,将每位元素二分插入 cell 中。
- 如果 cell 中元素都比它小,将它插到最后
- 否则,用它覆盖掉比它大的元素中最小的那个。
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums.length==0) return 0;
int[] cell = new int[nums.length];
cell[0] = nums[0];
int tail = 1; //维护最长子序列的长度
for(int i=1;i<nums.length;i++){
if(nums[i]>cell[tail-1]){
cell[tail] = nums[i];
tail++;
continue;
}
int left = 0;
int right = tail-1;
while(left<right){
int mid = (left+right)/2;
if(nums[i]<=cell[mid]){
right = mid;
}else{
left = mid+1;
}
}
cell[left] = nums[i];
}
return tail;
}
}