LIS算法

25年南大智科预推免考察了该知识点 要求用py

LIS 最长上升子序列

LIS = Longest Increasing Subsequence,最长上升子序列

子序列:可以跳过元素,不用连续,但元素相对顺序不能变。

例子:[1,3,2,4] LIS:1,3,4 或者 1,2,4,长度是 3

两个版本

  1. 严格上升:后一个 > 前一个(LIS 常用)
  2. 非递减:后一个 ≥ 前一个

暴力 O (n²) DP 写法(考试常考)

dp[i]:以第 i 个元素结尾的 LIS 长度

复制代码
nums = [1,3,2,4]
n = len(nums)
dp = [1]*n

for i in range(1,n):
    for j in range(i):   # j 在 i前面,j∈[0,i‑1]
        if nums[j] < nums[i]:
            dp[i] = max(dp[i], dp[j]+1)
            
print(max(dp))

dp 数组过程:

  • dp0=1
  • i=1,j=0:nums0 < nums1 → dp1=2
  • i=2,j=0:满足;j=1:3 不小于 2 → dp 2=2
  • i=3,j=0,1,2 都满足 → dp 3=3 max (dp)=3

优化:二分+贪心

核心思想:维护一个数组 tailstails[k] 表示长度为 k+1 的上升子序列,末尾最小的元素 。 同样求严格最长上升子序列nums[j] < nums[i]

python 复制代码
import bisect

def length_of_lis(nums):
    tails = []
    for x in nums:
        # 在tails里找第一个 >= x 的位置
        idx = bisect.bisect_left(tails, x)
        if idx == len(tails):
            tails.append(x)
        else:
            tails[idx] = x
    return len(tails)

print(length_of_lis([1,3,2,4]))   #输出3

bisect_left(a, x):在有序升序数组a里,返回可以插入 x,还能保持数组升序的最靠左的下标 。等价找第一个 ≥ x 的元素的下标如果数组里所有元素都 < x,返回数组长度

idx 代表:把 x 放到 tails 的 idx 位置,代表以 x 结尾的 LIS 长度就是 idx+1

python 复制代码
 if idx == len(tails):
            tails.append(x)

idx等于 tails 数组长度,说明当前 x 比 tails 里面所有元素都大

tails 数组的含义:tails[k] = 长度为k+1的上升子序列的最小末尾值

x 比全部末尾都大,说明可以把 x 接在当前最长子序列后面,生成更长的子序列。 →直接把 x 追加到 tails 末尾。

例子:tails=[1,3],x=4idx=2 == len(tails)=2,执行append(4),tails 变成[1,3,4]

python 复制代码
else: tails[idx] = x

含义:数组中找到了某个位置idx,该处元素≥x。 我们要更新 tails idx 为更小的 x

目的:同样长度的上升子序列,把末尾变得更小,方便后面来的数字更容易接上去。 ⚠注意:这一步不增加 LIS 总长度,只是优化后续的条件

进阶:山峰序列

除了标准的 LIS 问题,还有一种变体要求寻找序列中"先严格递增后严格递减"的最长子序列(即山峰型序列)。这类问题可以通过分别计算正向和反向的 LIS 来求解。

python 复制代码
def solve():
    n = int(input())
    if n == 0:
        return 0
    a = list(map(int, input().split()))

    # dp_inc[i]: 以位置 i 结尾的最长严格递增子序列长度
    dp_inc = [1] * n
    for i in range(1, n):
        for j in range(i):
            if a[j] < a[i]:
                dp_inc[i] = max(dp_inc[i], dp_inc[j] + 1)

    # dp_dec[i]: 从位置 i 开始的最长严格递减子序列长度
    dp_dec = [1] * n
    for i in range(n-2, -1, -1):
        for j in range(i+1, n):
            if a[i] > a[j]:
                dp_dec[i] = max(dp_dec[i], dp_dec[j] + 1)

    # 找到最长的山形子序列
    max_length = 0
    for i in range(n):
        length = dp_inc[i] + dp_dec[i] - 1
        max_length = max(max_length, length)

    return max_length

# 读取输入并输出结果
print(solve())

优化 O (n log n)

维护一个数组,贪心 + 二分,适合 n 很大的情况。

python 复制代码
from bisect import bisect_left


def get_lis_lengths(a):
    """
    ans[i] 表示以 a[i] 结尾的最长严格递增子序列长度。
    """
    tails = []
    ans = []

    for x in a:
        pos = bisect_left(tails, x)

        if pos == len(tails):
            tails.append(x)
        else:
            tails[pos] = x

        ans.append(pos + 1)

    return ans


def solve():
    n = int(input())
    a = list(map(int, input().split()))

    # 以每个位置结尾的最长严格递增子序列
    dp_inc = get_lis_lengths(a)

    # 反转后求 LIS,对应原数组中从每个位置开始的严格递减子序列
    dp_dec = get_lis_lengths(a[::-1])[::-1]

    answer = 0

    for i in range(n):
        answer = max(answer, dp_inc[i] + dp_dec[i] - 1)

    print(answer)


solve()

易混区分

  • 子序列 (subsequence):不要求连续,LIS 就是它

  • 子串 (subarray/substring):必须连续!最长上升子串和 LIS 不一样。

    pos = bisect_left(tails, x)

在有序列表 tails 中查找 x 应该插入的最左位置

在有序数组 a 中,返回第一个大于等于 x的下标。

使用 bisect_left 可以保证处理的是严格递增子序列:

  • 遇到相同元素时,替换原位置。

  • 不会因为相同元素而增加 LIS 长度。

    a[::-1]

Python 切片,表示把列表反转。

完整切片格式是:

复制代码
a[start:stop:step]

这里步长是 -1,所以从后向前取:

复制代码
[1, 2, 3][::-1]

结果是:

复制代码
[3, 2, 1]

后面的:

复制代码
get_lis_lengths(a[::-1])[::-1]

意思是:

  1. 反转原数组。
  2. 计算反转数组中每个位置的 LIS 长度。
  3. 再把计算结果反转回来。
  4. 得到原数组中从每个位置开始的最长严格递减子序列长度。
相关推荐
wuyk5551 小时前
Python零基础入门第五章:元组Tuple(不可变容器详解、列表与元组区别)
开发语言·python
zhanghaha13141 小时前
Python进阶教程:13_math 模块 —— 新手完全指南
数据库·python·机器学习
学学酱快乐2 小时前
2026年PMP考试生命周期选择决策树精讲:从需求判断到混合型统一框架的应试全攻略
算法·决策树·机器学习·pmp新版考试大纲·pmp新题型·pmp培训哪家好·pmp培训机构推荐
aiqianji2 小时前
教AI短篇小说写作的软件操作简单,该怎么挑选呢?
人工智能·python
傅科摆 _ py2 小时前
动态规划复习
算法·动态规划
circuitsosk2 小时前
Python 模块与包管理:import 机制、虚拟环境与 pip 完全指南
开发语言·python·pip·依赖管理·模块与包
Wang's Blog2 小时前
PostgreSQL笔记49:向量检索核心算法、索引调优与过滤策略深度解析
笔记·算法·postgresql
weixin_440730502 小时前
python+request实现接口-小结
开发语言·python