//我居然为了一个四月勋章提交了题解区的答案,这不是勋章,是耻辱。给你一个整数数组
nums
,找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,
[3,6,2,7]
是数组[0,3,1,6,2,2,7]
的子序列。示例 1:
输入:nums = [10,9,2,5,3,7,101,18] 输出:4 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。示例 2:
输入:nums = [0,1,0,3,2,3] 输出:4示例 3:
输入:nums = [7,7,7,7,7,7,7] 输出:1提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104
进阶:
- 你可以设计时间复杂度为
O(n2)
的解决方案吗?- 你能将算法的时间复杂度降低到
O(n log(n))
吗?
/**
动态规划
dp[j] j位前比nums[j]小的个数
dp[j]={
max(dp[i]),i=0..j&&nums[i]<nums[j]
}
**/
class Solution {
public int lengthOfLIS(int[] nums) {
int n=nums.length;
if(n==0)return 0;
int max=Integer.MIN_VALUE;
int[] dp=new int[n];
dp[0]=1;
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
dp[i]=Math.max(dp[i],dp[j]);//与左边符合条件的长度比较取最长
}
}
dp[i]+=1;//加上本轮j
max=Math.max(max,dp[i]);
}
return n==1?dp[0]:max;
}
}
/**写得最认真的博客没有之一
进阶:算法时间复杂度降到O(nlogn)--想到二分,那么需要实现一个有序数组查找
贪心:使序列最右侧的最大值尽量小可以得到最长子序列
dp[len]表示长度为len时最右一位元素值,需证明此数组单调递增
即证i<j时,dp[i]<dp[j]
反证i<j时,dp[i]>dp[j]:
令长度为j的序列减去后j-i个元素,得到最小元素min<dp[j],因为dp[j]是序列最大元素
而min<dp[j]<dp[i],说明dp[i]不是长度为i的序列的最右侧的最小元素,反证不成立。
算法实现思路:
当前遍历到nums[i]
如果nums[i]>dp[i],dp[++i]=nums[i]
如果nums[i]<dp[i],在已有的dp数组中二分查找比nums[i]小的dp数,替换下一个长度的dp为nums[i]
(这个处理真的好妙啊,最小数的修改维护了已有长度,又给后续长度增长提供了新的最小值
最后一个选择成功的dp[]下标就是最长长度啦
**/
class Solution {
public int lengthOfLIS(int[] nums) {
int n=nums.length;
if(n==0)return 0;
int[] dp=new int[n+1];
int len=1;
dp[len]=nums[0];//长度为1的子序列初始化为数组第一个值
for(int i=1;i<nums.length;i++){
if(nums[i]>dp[len]){
dp[++len]=nums[i];
}else{
int l=1,r=len,pos=0;
while(l<=r){
int mid=(l+r)/2;
if(dp[mid]<nums[i]){//找最大的一个比nums[i]小的
pos=mid;
l=mid+1;
}else{
r=mid-1;
}
}
dp[pos+1]=nums[i];//替换pos+1长度的最小数,如果找不到比nums[i]小的,那么把长度为1的dp置为nums[i]
}
}
return len;
}
}