当前位置:   article > 正文

最长递增子序列c语言版(动态规划或贪心+二分查找)_c语言最长递增子序列

c语言最长递增子序列

300. 最长递增子序列

题目:

给你一个整数数组 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)

状态转移方程:

  1. for(j=0;j<i;j++)//遍历i之前的所有元素
  2. {
  3. if(nums[j]<nums[i])//i可以作为j的后一位加上去
  4. {
  5. dp[i]=max(dp[i],dp[j]+1);//取最大的dp[i];
  6. }
  7. }

 3.最后求出dp数组里面存放的最大值就是最长子序列长度最大值,返回最大值即可。

 代码如下:

  1. int lengthOfLIS(int* nums, int numsSize){
  2. int dp[numsSize];
  3. int i,j;
  4. memset(dp,0,sizeof(dp));
  5. if(numsSize==0)
  6. return 0;
  7. int max=0;
  8. for(i=0;i<numsSize;i++)
  9. {
  10. dp[i]=1;//因为只要有数,那长度最少为1
  11. for(j=0;j<i;j++)
  12. {
  13. if(nums[j]<nums[i])
  14. {
  15. dp[i]=fmax(dp[i],dp[j]+1);
  16. }
  17. }
  18. max=fmax(max,dp[i]);
  19. }
  20. return max;
  21. }

 复杂度:

时间复杂度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 的子序列尾部元素值一定更小)

最终代码:

  1. int twofen(int *dp,int n,int target)
  2. {
  3. int left=0,right=n-1;
  4. if(n==1)
  5. return 0;
  6. else{
  7. int mid;
  8. while(left<=right)
  9. {
  10. mid=(left+right)/2;
  11. if(dp[mid]<target)
  12. {
  13. left=mid+1;
  14. }
  15. else if(dp[mid]>target)
  16. {
  17. right=mid-1;
  18. }
  19. else
  20. {
  21. return mid;
  22. }
  23. }
  24. }
  25. return left;//最后左边位置就会成为target正确存放位置
  26. }
  27. int lengthOfLIS(int* nums, int numsSize){
  28. int dp[numsSize];
  29. int i;
  30. memset(dp,0,sizeof(dp));
  31. int len=1,k=0;
  32. dp[0]=nums[0];
  33. for(i=1;i<numsSize;i++)
  34. {
  35. if(nums[i]>dp[k])
  36. {
  37. k++;
  38. dp[k]=nums[i];
  39. len++;
  40. }
  41. else if(nums[i]<dp[k])
  42. {
  43. dp[twofen(dp,k+1,nums[i])]=nums[i];
  44. }
  45. }
  46. return len;
  47. }

 复杂度:

时间复杂度O(nlog⁡n):数组 nums的长度为 n,我们依次用数组中的元素去更新 dp 数组,而更新 dp 数组时需要进行O(logn) 的二分搜索,所以总时间复杂度为 O(nlogn)

空间复杂度O(n):需要额外使用长度为 n的dp数组

博主是个新手,如果有错误的地方,请联系我改正,谢谢!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/524545
推荐阅读
相关标签
  

闽ICP备14008679号