LeetCode[每日一题]-300.最长上升子序列 非常规线性动态规划+二分查找

这里是题目描述:LeetCode-300.最长上升子序列

动态规划方法 时间复杂度O(n2)

我们首先使用时间复杂度O(N2)的动态规划方法

建立一个和给定数组nums相同长度的一维表dp,dp[i]存储以nums[i]为末尾且包含nums[i]的最长子序列长度。初始化dp中所有值为1,从i=1开始遍历nums[i]并更新dp[i]

dp[i]更新方法:进入第二层循环,令j从0开始直到i-1,遍历dp[j];若dp[i]>dp[j],则由nums[i]为末尾元素,以nums[j]为倒数第二个元素的上升子序列长度可能是dp[I]的值,也就是说,取dp[j]+1和当前dp[i]两者中的较大值作为dp[i]

题解代码:

class Solution {
    //时间复杂度为O(n^2)的动规解法
    public int lengthOfLIS(int[] nums)
    {
        if(nums.length<=1)
        {
            return nums.length;
        }
        int[] dp=new int[nums.length]; //用于动规的一维表,dp[i]存储以nums[i]为末尾且包含nums[i]的最长子序列长度
        dp[0]=1;
        int maxLen=1;
        for(int i=1;i<dp.length;i++)
        {
            int temp=1;
            for(int j=0;j<i;j++)
            {
                if(nums[j]<nums[i])
                {
                    temp=Math.max(temp,dp[j]+1);
                }
            }
            dp[i]=temp;
            maxLen=Math.max(maxLen,temp);
        }
        return maxLen;
    }
}

时间复杂度:因为进行两层循环,所以是O(n2)
空间复杂度:建立一个一维动规表,所以是O(n)

非常规线性动态规划+二分查找 时间复杂度O(nlogn)

这个方法依然需要第一层时间规模为n的外层循环来遍历nums,不同的是在内层循环中使用了二分查找来构建动规表,时间规模是logn,提高了时间效率

通过重新设计状态定义,使整个 dp 为一个排序列表;这样在计算每个 dp[k] 时,就可以通过二分法遍历 [0,k) 区间元素,将此部分复杂度由 O(n) 降至 O(logn)

  • 考虑维护一个列表 tails其中每个元素 tails[k] 的值代表长度为 k+1 的子序列尾部元素的值。
  • 如 [1,4,6] 序列,长度为 1,2,3 的子序列尾部元素值分别为 tails=[1,4,6]
  • 最后返回tail存储的能达到的最大长度

更具体的实现方法请参考:最长上升子序列(动态规划 + 二分查找,清晰图解)

题解代码:

class Solution {
    public int lengthOfLIS(int[] nums)
    {
        if(nums.length<=0)
        {
            return nums.length;
        }
        int[] tail=new int[nums.length]; //用于动规的一维表,tail[i]存储截止当前遍历过的数组中,长度为i+1的上升子序列的尾部元素的最小值
        tail[0]=nums[0];
        int lastIndex=0;
        for(int i=1;i<nums.length;i++)
        {
//            System.out.println("i: "+i);
//            System.out.println(Arrays.toString(tail));
            int left=0,right=lastIndex;
            //int mid;
            while(left<=right) //用二分法查找在tail[0:lastIndex]中nums[i]的大小分界点
            {
                int mid=(left+right)/2;
                if(tail[mid]==nums[i])
                {
                    break;
                }
                else if(tail[mid]<nums[i])
                {
                    left=mid+1;
                }
                else
                {
                    right=mid-1;
                }
            }
            if(left>right) //若由tail[mid]==nums[i]跳出循环无需更新tail
            {
                //left指向tail中第一个大于nums[i]的值
                if(left<=lastIndex) //tail[0:lastIndex]中有大于nums[i]的值,替换
                {
                    //System.out.println("1: left: "+left+" lastIndex: "+lastIndex);
                    tail[left]=nums[i];
                }
                else //left>lastIndex,tail[0:lastIndex]中没有大于nums[i]的值,将nums[i]放在tail[lastIndex]处
                {
                    //System.out.println("2: left: "+left+" lastIndex: "+lastIndex);
                    tail[lastIndex+1]=nums[i];
                    lastIndex+=1;
                }
            }
        }
        //System.out.println(Arrays.toString(tail));
        return lastIndex+1;
    }
}

时间复杂度:外层循环+内层的二分查找 O(nlogn)
空间复杂度:O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值