【每天学习一点算法 2026/04/02】最长递增子序列

每天学习一点算法 2026/04/02

题目:最长递增子序列

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

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

动态规划

动态规划就是先找规律

我们设 dp[i] 是截至下标 i 处最长严格递增子序列的长度,那么 dp[i] 的值应该等于他之前的最长严格递增子序列长度 + 1

我们需要向前找到 nums[j] < nums[i] 的项,找出其中最大的 dp 值。

typescript 复制代码
function lengthOfLIS(nums: number[]): number {
  const dp = new Array(nums.length).fill(1) // 初始化dp数组,最短子序列都是1
  // 遍历数组计算 dp 值
  for (let i = 1; i < nums.length; i++) {
    // 往前遍历元素,计算当前位置最长子序列长度
    for (let j = i - 1; j >= 0 ; j--) {
      if (nums[j] < nums[i]) {
        // 只有 nums[j] < nums[i] 时,才有可能是子序列
        // 找到前方最长的递增子序列 + 1
        dp[i] = Math.max(dp[i], dp[j] + 1)
      }
    }
  }
  return Math.max(...dp) // 返回最长的子序列长度
};
贪心算法 + 二分法

这个方法是看了官方题解才知道的

这个方法的核心就是:我们如果要保证子序列最长递增,那就得使每个上升的元素也就是下一个元素,尽可能的小,于是我们可以维护一个数组,使这个数组的最后一项为递增子序列的最小可能值。

我们以这个 [10,9,2,5,3,7,101,18] 为例

  • 第一项 10 只有一项肯定最短 [10]
  • 接下来 9,它是小于 10 , 后面大于 9 的数肯定是比大于 10 的数多的,直接替换 [9]
  • 接下来 2,同理直接替换 [2]
  • 接下来 5,他是大于我们最后一项 2 的,那么我们可以直接将它插入最长子序列 [2, 5]
  • 接下来 3,他是小于 5 的,那我们就需要在维护的数组里找到第一个大于它的元素,也就是 5 ,替换掉它 [2, 3]
  • 接下来 7,直接插入 [2, 3, 7]
  • 接下来 101,直接插入 [2, 3, 7, 101]
  • 接下来 18,替换掉第一个大于大的 101 [2, 3, 7, 18]

其实我们发现,这个流程下来得到的结果并不是我们最终的最长子序列,比如我们把 3 换成 1,最终结果就会变成 [1, 5, 7, 18],本来应该是 [2, 5, 7, 18],但是我们可以看出遇到小于最后一位我们只做了替换操作不会影响数组的长度,因为我们无法保证后续元素中小于末尾的元素个数,上面的例子是一个理想的情况,感觉只需要替换最后一个元素就行了,觉得有疑问的可以尝试一下 [9, 10, 1, 2, 3, 4],我们只有不断的往前替换元素,才能保证数组的最后一位是可能子序列的最小末尾数。

typescript 复制代码
function lengthOfLIS(nums: number[]): number {
  const res = [nums[0]] // 
  for (let i = 1; i < nums.length; i++) {
    if (res[res.length - 1] >= nums[i]) {
      let left = 0, right = res.length - 1, pos = nums.len - 1 // 默认替换末尾
      while (left <= right) {
        const mid = Math.floor((left + right) / 2)
        if (res[mid] >= nums[i]) {
          // 由于是向下取整的,所以需要在左边处理时记录当前位置为替换位置
          pos = mid
          right = mid - 1
        } else {
          // 第一个大于 nums[i] 右侧
          left = mid + 1
        }
      }
      res[pos] = nums[i] // 修改对应位置
    } else {
      res.push(nums[i]) // 末尾添加
    }
  }
  return res.length // 返回最终长度
};

题目来源:力扣(LeetCode)

相关推荐
三品吉他手会点灯2 小时前
C语言学习笔记 - 20.C编程预备计算机专业知识 - 变量为什么必须的初始化【重点】
c语言·笔记·学习
sakiko_3 小时前
UIKit学习笔记1-创建项目(使用UIKit)、使用组件
笔记·学习
Old Uncle Tom3 小时前
OpenClaw 记忆系统 -- 记忆预加载
java·数据结构·算法·agent
会编程的土豆3 小时前
洛谷题单入门1 顺序结构
数据结构·算法·golang
生信碱移3 小时前
PACells:这个方法可以鉴定疾病/预后相关的重要细胞亚群,作者提供的代码流程可以学习起来了,甚至兼容转录组与 ATAC 两种数据类型!
人工智能·学习·算法·机器学习·数据挖掘·数据分析·r语言
智者知已应修善业3 小时前
【51单片机中的打飞机设计】2023-8-25
c++·经验分享·笔记·算法·51单片机
星幻元宇VR5 小时前
VR航空航天科普设备【VR时空直升机】
科技·学习·安全·生活·vr
_李小白5 小时前
【android opencv学习笔记】Day 2: Mat类(图片数据结构体)
android·opencv·学习
智者知已应修善业5 小时前
【51单片机按键调节占空比3位数码管显示】2023-8-24
c++·经验分享·笔记·算法·51单片机
harder3216 小时前
RMP模式的创新突破
开发语言·学习·ios·swift·策略模式