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. 得到原数组中从每个位置开始的最长严格递减子序列长度。
相关推荐
李航19831 小时前
用 DeepDraw 几何引擎开发建筑设计软件(六):创建画线工具
python·软件构建
AI 编程助手GPT1 小时前
Python 备份 SQLite:为什么复制了 .db,恢复后还是少数据?
人工智能·python·ai·chatgpt
Zane19941 小时前
冒泡、选择、插入排序都是O(n²),希尔排序凭什么说自己能更快
算法·排序算法
狗都不学爬虫_1 小时前
AI逆向 - 天御无感点击验证(补+纯)
爬虫·python·网络爬虫
529宝宝起名网2 小时前
用 Python 开发历史名字查询与起名灵感工具:从古籍人物数据库到名字文化故事生成
开发语言·前端·python
余额瞒着我当琳2 小时前
C++多态深入解析:多态概念,多态实现条件,虚函数表与内存分布,常见问题及注意事项
c++·算法
用户298698530142 小时前
Python 将 HTML 转换为 Word 文档的实践指南
python·html·api
维克兜率天2 小时前
【维克】模块3总结:从一行空数据,到一个能跑的模型
python·深度学习·算法
飞Link2 小时前
定积分理论与 Python 仿真完全指南
python·算法