LeetCode 300.最长上升子序列(超详细)

LeetCode 300.最长上升子序列(超详细)

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:

  • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
  • 你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

一、O(n^2)解法:(动态规划)

  • 证明优化子结构
    假设最长递增子序列中包含元素ak, 那么一定存在一组最优解,
    它包含了a1, a2, …, ak-1 这个序列的最长递增子序列,
    否则可以推出该解不是最优解

  • 重叠子问题

    容易知道子问题重叠

  • 定义dp[i]:从0到i的以num[i]结尾的最长上升子序列

  • 状态转移方程

    对于 j

    dp[i] = max( dp[j] ) + 1, 0 < j < i 且 num[j],num[i]

    dp[i] = 1, 其他情况

  • 代码

    class Solution {
           
        public int lengthOfLIS(int[] nums) {
           
            if(nums.length < 2)
                 return nums.length;
            int[] dp = new int[nums.length];
            dp[0] = 1;
            int ans = 1;
            for(int i = 1;i<nums.length;i++)
            {
           
                int cnt = 0;
                for(int j=0;j<i;j++)
                {
           
                    if(nums[i]>nums[j])
                       cnt = Math.max(cnt,dp[j]);
                }
                dp[i] = cnt+1;
                ans = Math.max(ans,dp[i]);
            }
            return ans;
        }
    }
    

二、O(nlogn)解法(贪心+二分查找)

  • 贪心策略:要让上升子序列尽可能长,就让其上升的缓慢一些,因此我们希望每次加在上升子列最后的那个数在满足条件的情况下尽可能小

  • 定义d[i]:表示长度为 i 的最长上升子序列的末尾元素的最小值

  • 证明d[i]单调

    如果 d[j]>=d[i] 且 j

  • 流程
    设当前已求出的最长上升子序列的长度为 len(初始时为 1,
    因为d[1] = num[0]),从前往后遍历数组num,在遍历到
    num[i] 时:
    如果 num[i]>d[len] ,则直接加入到 d 数组末尾,并更新 len = len+1;
    否则,在 d 数组中二分查找,找到第一个比 num[i]小的数 d[k] ,并更新 d[k+1] = num[i]

    示例:[ 1 , 3 , 2, 5 , 11 , 0]

    第一步插入1 ,d = [1];

    第二步插入3 ,d = [1,3];

    第三步插入2 ,d = [1,2];

    第四步插入3 ,d = [1,2,5];

    第五步插入11 ,d = [1,2,5,11];

    第六步插入0 ,d = [0,2,5,11];

    第六步看起来可能有问题,但是其不仅不会影响返回值,并且保证了贪心策略正确性,因为新来的并不影响已有的最大长度,但是可以更新为更好(更慢增长)的组合。

  • 代码

    class Solution {
           
        public int lengthOfLIS(int[] nums) {
           
            if(nums.length < 2)
                 return nums.length;
            int[] d = new int[nums.length];
            int j=0;
            d[j] = nums[0];
           for(int i=1;i<nums.length;i++)
           {
           
               if(nums[i]>d[j])
                   d[++j] = nums[i];
                else
                {
           
                    int l = 0,r=j,pos=0;
                    boolean falg=false;
                    int mid =(l+r)>>1;
                    while(l<=r)
                    {
           
                        mid =(l+r)>>1;
                        if(d[mid]<nums[i])
                        {
           
                            falg = true;
                            pos = mid;
                            l = mid+1;
                        }
                        else 
                           r =mid-1;
                    }
                    if(falg)
                       d[pos+1] = nums[i];
                    else
                        d[pos] = nums[i];
                }
           }
           return j+1;
        }
    }
    

你可能感兴趣的:(LeetCode,leetcode,java)