639 字
3 分钟
最长递增子序列

题目描述#

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

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

解题思路#

  1. 动态规划
    • 定义 dp[i] 表示以 nums[i] 结尾的 LIS 长度
    • 状态转移:遍历 j=0→i-1,若 nums[i]>nums[j],则 dp[i] = max(dp[i], dp[j]+1)
    • 结果取 max(dp)
  2. 贪心+二分优化
    • 维护有序数组 tailstails[k] 表示长度为 k+1 的 LIS 的最小结尾数
    • 遍历时二分查找插入位置:替换首个 ≥ nums[i] 的元素
    • tails 长度即为 LIS 长度

关键洞察#

  1. 贪心策略有效性
    • 让上升序列尽可能慢地增长
    • 小元素替换大元素为未来留空间
  2. tails 数组特性
    • 严格递增(可二分搜索)
    • 长度 = LIS 长度
  3. 二分查找作用
    • 快速定位元素插入位置
    • 保持 tails 有序性
  4. 结尾元素重要性
    • 相同长度的 LIS 只需记录最小结尾值
    • 避免维护完整序列

复杂度分析#

方法时间复杂度空间复杂度适用场景
动态规划O(n²)O(n)小规模数据
贪心+二分O(n log n)O(n)最优解(推荐)

代码实现#

二分法+贪心

/**
* @param {number[]} nums
* @return {number}
*/
var lengthOfLIS = function (nums) {
const tails = [];
for (let num of nums) {
let left = 0, right = tails.length;
while (left < right) {
let mid = (left + right) >> 1;
if (tails[mid] < num) left = mid + 1;
else right = mid;
}
tails[left] = num;
}
return tails.length;
};

动态规划法

/**
* @param {number[]} nums
* @return {number}
*/
var lengthOfLIS = function (nums) {
const n = nums.length
const dp = new Array(n).fill(1)
for (let i = 1; i < n; i++) {
for (let j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1)
}
}
}
// console.log(dp)
return Math.max(...dp);
};

相似题目#

最长递增子序列
https://website-truelovings-projects.vercel.app/posts/algorithms/最长递增子序列/
作者
欢迎来到StarSky的网站!
发布于
2025-08-16
许可协议
CC BY-NC-SA 4.0