赞
踩
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列。
可以看出数组 [0,3,1,6,2,2,7]的最长递增子序列为[0,1,2,7]或者[0,3,6,7],其长度为4,故
最长严格递增子序列的长度为4。
让我们想想,我们是如何看出来最长严格递增子序列的,是不是从第一位数开始,看看后面的数如果比前一位数大,那就长度加1,然后一直找到最后没有数比该位数大就停止,所有情况的max即为答案;
例如:
(1)0>>3>>6>>7 || 0>>1>>6>>7等等
当然这是我们用眼睛看出来的,让我们想想该如何用代码实现呢,这里就用到了递归的想法
假如你要求n位数组中最长严格递增子序列的长度 是不是 可以先求前n-1位数组中最长严格递增子序列的长度max ,求n-1则需要求n-2的长度max,一直到前1位数组。最后我们就能得出答案;当然递归时间肯定太大了,我们转换为动态规划,用数组存放每一层递归的结果。
1.我们定义一个dp数组,用来存储前i位数组中的最长严格递增子序列的长度;
2.如果nums[i]>nums[j],则说明nums[i]严格递增于nums[j],可以补到nums[j]后面增加为更长的子序列,dp[i]=dp[j]+1;又因为我们要求最长子序列长度,所以选取每次符合条件(nums[i]>nums[j])的最大值:dp[i]=max(dp[i],dp[j]+1)
状态转移方程:
- for(j=0;j<i;j++)//遍历i之前的所有元素
- {
- if(nums[j]<nums[i])//i可以作为j的后一位加上去
- {
- dp[i]=max(dp[i],dp[j]+1);//取最大的dp[i];
- }
- }
3.最后求出dp数组里面存放的最大值就是最长子序列长度最大值,返回最大值即可。
- int lengthOfLIS(int* nums, int numsSize){
- int dp[numsSize];
- int i,j;
- memset(dp,0,sizeof(dp));
- if(numsSize==0)
- return 0;
- int max=0;
- for(i=0;i<numsSize;i++)
- {
- dp[i]=1;//因为只要有数,那长度最少为1
- for(j=0;j<i;j++)
- {
- if(nums[j]<nums[i])
- {
- dp[i]=fmax(dp[i],dp[j]+1);
- }
- }
- max=fmax(max,dp[i]);
- }
- return max;
- }
时间复杂度o(n*n):其中 n为数组 nums 的长度。动态规划的状态数为 n,计算状态 dp[i],需要 O(n)的时间遍历 dp[0…i−1] 的所有状态
空间复杂度o(n):开辟了一个一维数组来存放长度
方法一时间复杂度还是有点高,我们看看能不能给他再降一降
因为是求最长递增子序列,我们知道,数字递增越慢,其长度就可能更长。基于上面的贪心思路,我们维护一个数组 d[i] ,表示长度为 i 的最长上升子序列的末尾元素的最小值,用 len 记录目前最长上升子序列的长度,起始时 len 为 1,d[1]=nums[0]。例如
原数组nums = [10,9,2,5,3,7,101,18]
dp[0]=10; [10]
dp[0]=9; [9]
dp[0]=2; [2]
dp[1]=5; [2,5]
dp[1]=3; [2,3]
dp[2]=7; [2,3,7]
dp[3]=101; [2,3,7,101]
dp[3]=18; [2,3,7,18]
最后dp数组里面应该是[2,3,7,18],最大递增子序列长度为4
具体需要列一个状态方程:
如果nums[i]>dp[k],我们就把 nums[i] 存放进dp中,len++
如果nums[i]<dp[k],,用二分查找法前k位数,把num[i]替换到合适位置,代码如下
if(nums[i]>dp[k])
{
k++;
dp[k]=nums[i];
len++;
}
else if(nums[i]<dp[k])
{
dp[twofen(dp,k+1,nums[i])]=nums[i];//twofen(dp,k+1,nums[i])为自定义二分函数
}
看到这里有些同学就会想,为什么这样不按顺序却会得出正确答案 有没有可能会出现错误呢?
其实该过程不会影响dp[i]数组下一位数的选取,只改变前i-1位数中某一位的大小,并不会引起子序列长度的变化
证明: 当 j<i,若 dp[j]>=dp[i],代表较短子序列的尾部元素的值 大于 较长子序列的尾部元素的值。这是没有可能的,因为从长度为 i 的子序列尾部倒序删除 i-j 个元素,剩下的为长度为 j 的子序列,设此序列尾部元素值为 p,则一定有 p<dp[i](即长度为 j 的子序列尾部元素值一定更小)
- int twofen(int *dp,int n,int target)
- {
- int left=0,right=n-1;
- if(n==1)
- return 0;
- else{
- int mid;
- while(left<=right)
- {
- mid=(left+right)/2;
- if(dp[mid]<target)
- {
- left=mid+1;
- }
- else if(dp[mid]>target)
- {
- right=mid-1;
- }
- else
- {
- return mid;
- }
- }
- }
- return left;//最后左边位置就会成为target正确存放位置
- }
- int lengthOfLIS(int* nums, int numsSize){
- int dp[numsSize];
- int i;
- memset(dp,0,sizeof(dp));
- int len=1,k=0;
- dp[0]=nums[0];
- for(i=1;i<numsSize;i++)
- {
- if(nums[i]>dp[k])
- {
- k++;
- dp[k]=nums[i];
- len++;
- }
- else if(nums[i]<dp[k])
- {
- dp[twofen(dp,k+1,nums[i])]=nums[i];
- }
- }
- return len;
- }
时间复杂度O(nlogn):数组 nums的长度为 n,我们依次用数组中的元素去更新 dp 数组,而更新 dp 数组时需要进行O(logn) 的二分搜索,所以总时间复杂度为 O(nlogn)
空间复杂度O(n):需要额外使用长度为 n的dp数组
博主是个新手,如果有错误的地方,请联系我改正,谢谢!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。