头像

zjian

Just Code!

帖子照片墙关于

LeetCode 300:最长递增子序列题解

2026-05-13 02:37

因为刚好最近在研究 Vue3 的原理,刚好涉及到 LIS 的内容,之前做题直接 DP 算出来就没继续研究了,Vue3 有用贪心的策略, 这边重新回来看下这个问题。

题目描述

给定一个整数数组 nums,找到其中最长严格递增子序列的长度。

子序列不要求在原数组中连续,但需要保持原有的相对顺序。

例如:

const nums = [10, 9, 2, 5, 3, 7, 101, 18]

其中一个最长递增子序列是:

[2, 3, 7, 101]

所以答案是 4

dp 思路

dp 思路比较简单, 很容易想到状态转移方程是:

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

实现代码是:

function lengthOfLIS(nums: number[]): number {
    const dp = [];
    for (let i=0; i<nums.length; i++) {
        let maxDp = 1;
        dp[i] = 1;
        for (let j=0; j<i; j++) {
            if (nums[j] >= nums[i]) {
                continue;
            }
            maxDp = Math.max(dp[j]+1, maxDp)
        }
        dp[i] = maxDp;
    }
    return Math.max(...dp);
};

贪心策略

大致思路是: 维护一个数组tails[i], 表示长度为 i + 1 的递增子序列中,末尾的最小元素

然后遍历 nums[i] 时,在 tails 上找到第一个大于 nums[i] 的位置,用 nums[i] 替换该值。如果所有元素都小于 nums[i] ,则 nums[i] 直接添加到 tails 后面。

最后,最长子序列的长度,就是 tails 的长度了。

然后,还可以优化下,找到第一个大于 nums[i] 的位置,可以用二分查找,这样整体的时间复杂度可以降低为 O(NlogN)

实现的代码是:

function lengthOfLIS(nums: number[]): number {
    if (nums.length ===0) {
        return 0;
    }
    const tails = [nums[0]];
    for (let i=1; i<nums.length; i++) {
        if (nums[i] > tails[tails.length - 1]) {
            tails.push(nums[i]);
        } else {
            // 二分查找第一个大于nums[i]的
            let l = 0, r = tails.length-1;
            let pos = -1;
            while (l <= r) {
                let mid =  Math.floor((l + r) / 2);
                if (tails[mid] < nums[i]) {
                    l = mid +1;
                    pos = mid;
                } else {
                    r = mid -1;
                }
            }
            tails[pos+1] = nums[i];
        }
    }
    return tails.length;
};