最长递增子序列 - LeetCode 热题 87

大家好!我是曾续缘💖

今天是《LeetCode 热题 100》系列

发车第 87 天

动态规划第 7 题

❤️点赞 👍 收藏 ⭐再看,养成习惯

最长递增子序列

给你一个整数数组 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(n log(n)) 吗?
难度:💖💖

解题方法

定义f[i]为:长度为k+1的子序列的末尾元素。下标代表了长度,值代表的是原数组中的一个值,作为子序列的末尾元素。

初始化

  • f数组的长度为0,很好理解吧,不存在下标,即不存在任何长度的子序列。

遍历数组进行状态转移

  1. 因为我们是一个数一个数地遍历的,对于当前遍历的数num,尝试把它加到目前最长子序列的末尾。

    • 目前最长子序列的末尾在哪里呢?根据f的定义,最长子序列的长度就是f最大的下标加1,最长子序列的末尾元素就是f[最大的下标]
    • 目前遍历的数num能不能加到目前最长子序列的末尾呢?如果num大于目前最长子序列的末尾元素f[最大的下标],说明递增,可以加入。
    • 加入之后会导致什么呢?最长子序列的长度+1了,末尾元素也改变了,变成了num
    • 最长子序列的长度变化了,怎么办呢,怎么改呢?根据f的定义,f的最大下标要加1了,说明最长子序列长度+1了,同时f[新最大下标]等于num了,因为最长子序列的尾部是当前遍历的num了。
    • f数组的最大下标+1了,也填入值了,原来的f最大下标的值需要改吗?不需要,因为这个下标对应的子序列长度没变化啊,我们也不愿意将num作为它的尾部,因为num更大,不利于贪心。
  2. 如果num不能加到目前最长子序列的末尾,说明最长子序列的长度不会变化。

    • 那我要num有何用?我们可以用num来替换掉其他长度的子序列的尾部啊,让它们变小点,方便后面新增的数能更小的拼接上来。
    • 好,我们从头遍历f数组,不断尝试更新f[i]的尾部元素,和num取min,越小越好。
    • 但是这样算法时间复杂度就变成了 O ( n 2 ) O(n^2) O(n2)
    • 想想我们有必要更新所有的f值吗?比如说长度为1,2,3的子序列的尾部,都能更新为更小的num。我们更新了3,还有必要更新1,2吗?更新了也不会导致子序列的长度变长,就算后面有数拼接上来,我们也是选择拼接在长度为3的子序列上啊,因为贪它更长啊。
    • 所以我们选择更新最后一个能更新的,之前的不用考虑了。
    • 如何找到最后一个能更新的位置呢?这显然和我们的lowerbound方法不谋而合。使用二分的方法,以 O ( l o g n ) O(logn) O(logn)的时间复杂度迅速找到最后一个能将尾部更新为numf数组下标,更新之。
      • 为什么能用二分呢?eeee因为f数组是递增的。

返回值:根据f的定义,最长子序列的长度就是f的最大下标+1,即f数组的长度。

Code

class Solution {
    public int lengthOfLIS(int[] nums) {
        List<Integer> f = new ArrayList<>();
        for (int num : nums) {
            if (f.isEmpty() || num > f.get(f.size() - 1)) {
                f.add(num);
            } else {
                int l = lowerbound(f, num);
                f.set(l, num);
            }
        }
        return f.size();
    }

    public int lowerbound(List<Integer> f, int num) {
        int l = 0, r = f.size() - 1;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (f.get(mid) >= num) {
                r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
        return l;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值