1. 序列DP:从入门到精通的完整指南
动态规划(DP)是算法领域的核心思想之一,而序列DP则是其中最实用、最高频的类别。作为一名经历过上百场算法面试的老手,我深知序列DP在技术面试中的分量——几乎每场面试都会遇到1-2道相关题目。本文将带你系统掌握序列DP的各类问题解法,从基础概念到高级优化,全是干货。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 序列DP基础概念与核心思想
2.1 什么是序列DP
序列DP特指处理线性序列(如数组、字符串)的动态规划问题。这类问题的共同特点是:
- 输入是一个序列(数字数组或字符串)
- 需要求解与子序列/子串相关的某种最优解
- 问题可以被分解为重叠子问题
典型应用场景包括:
- 文本相似度比较(LCS)
- 基因序列比对(编辑距离)
- 金融数据分析(最大子数组和)
- 自然语言处理(解码方法)
2.2 DP三要素在序列问题中的体现
任何DP问题都包含三个关键要素,序列DP也不例外:
-
状态定义:如何用dp数组表示子问题的解
- 一维dp[i]:通常表示以第i个元素结尾的解
- 二维dp[i][j]:通常表示子序列i到j的解
-
状态转移:如何从小问题的解推导大问题的解
- 核心是找到dp[i]与dp[i-1]或dp[j]之间的关系
- 通常需要比较序列中的元素
-
初始条件:最小子问题的解
- 空序列、单元素序列等边界情况
- 二维DP中常常初始化对角线
提示:90%的序列DP难题都卡在状态定义上。好的状态定义应该能够包含所有必要信息,同时避免冗余。
3. 最长递增子序列(LIS)问题深度解析
3.1 经典LIS问题
LeetCode 300题要求找到最长严格递增子序列的长度。注意子序列不要求连续。
3.1.1 基础DP解法(O(n²))
python复制def lengthOfLIS(nums):
if not nums: return 0
n = len(nums)
dp = [1] * n # 每个元素本身就是一个长度为1的子序列
for i in range(n):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
关键点解析:
- dp[i]表示以nums[i]结尾的LIS长度
- 对于每个i,检查所有j < i的元素
- 如果nums[i] > nums[j],则可以扩展以j结尾的子序列
复杂度分析:
- 时间复杂度:O(n²)(双重循环)
- 空间复杂度:O(n)(dp数组)
3.1.2 贪心+二分优化(O(n log n))
python复制def lengthOfLIS_optimized(nums):
if not nums: return 0
tails = [] # tails[i]表示长度为i+1的LIS的最小末尾值
for num in nums:
# 二分查找第一个≥num的位置
left, right = 0, len(tails)
while left < right:
mid = (left + right) // 2
if tails[mid] < num:
left = mid + 1
else:
right = mid
if left == len(tails):
tai
