#19491. 最长等差子序列LAS (Longest Arithmetic Subsequence)
最长等差子序列LAS (Longest Arithmetic Subsequence)
最长等差子序列 (Longest Arithmetic Subsequence)
题目描述 给定一个长度为 的整数序列 。你需要从该序列中选出一个子序列(不要求连续),使得该子序列构成一个等差数列。你的任务是求出这个等差子序列的最大长度。
等差数列是指从第二项起,每一项与它的前一项之差等于同一个常数(称为公差)的数列。
输入格式 第一行包含一个整数 ,表示序列的长度。 第二行包含 个整数,表示序列 。
输出格式 输出一个整数,表示最长等差子序列的长度。
样例输入
5
9 4 7 2 10
样例输出
3
数据范围与提示
- 对于 的数据,, 的取值范围较小。
- 对于 的数据,,。
- 注意:公差可能为负数,且任意两个数均可视为长度为 2 的等差数列。
OI 教练课:从“升维”到“稀疏建模”
1. 为什么一维 DP 不够用了?
在解决最长递增子序列(LIS)时,我们定义 dp[i] 表示以第 个数结尾的最优解。这是因为 LIS 只需要知道“上一个数是谁”。
但等差数列具有三元关系约束。要确定 是否成等差,必须满足 。如果我们只知道以 结尾的最长长度,我们无法得知它的公差是什么,也就无法判断 能否接在后面。
2. 状态的维度升级
为了记录“历史公差”,我们需要增加一维:
- 状态定义:
dp[i][diff]表示以索引 结尾,且公差为diff的等差子序列的最长长度。 - 状态转移:遍历 之前的所有 ():
- 计算当前公差
d = A[i] - A[j]。 - 如果
dp[j][d]已经存在,则dp[i][d] = dp[j][d] + 1。 - 如果不存在,说明 是新数列的开头,
dp[i][d] = 2。
- 计算当前公差
3. 稀疏状态管理(Hash Map 优化)
当 范围达到 时,公差 diff 的范围也会非常大,直接开二维数组 dp[2000][2*10^9] 会导致内存爆炸。
- 观察:对于每个固定的 ,有效的
diff最多只有 个。 - 对策:使用哈希表向量
std::vector<std::unordered_map<int, int>> dp。这样只为实际存在的(i, diff)对分配空间。
4. 类斐波那契序列的拓展
如果题目变为“类斐波那契子序列”(),逻辑依然相同:
- 通过 锁定我们需要寻找的“前项”数值 。
- 利用预处理的数值到索引的映射(
val_to_index)实现 查找,从而将 的暴力枚举优化至 。
算法逻辑流程图 (Mermaid)
graph TD
Start("开始分析") --> Init("初始化 maxLength = 0")
Init --> CreateDP("创建 dp 数组: 元素为哈希表 unordered_map")
CreateDP --> OuterLoop("外层循环 i 从 0 到 n-1")
OuterLoop --> InnerLoop("内层循环 j 从 0 到 i-1")
InnerLoop --> CalcDiff("计算公差 d = A_i - A_j")
CalcDiff --> CheckExist{"dp_j 中是否存在公差 d ?"}
CheckExist -- "是" --> Extend("length = dp_j_d + 1")
CheckExist -- "否" --> NewSeq("length = 2")
Extend --> UpdateDP("更新 dp_i_d = length")
NewSeq --> UpdateDP
UpdateDP --> MaxUpdate("maxLength = max(maxLength, length)")
MaxUpdate --> InnerNext("继续内层循环 j")
InnerNext --> InnerLoop
InnerLoop -- "j 遍历结束" --> OuterNext("继续外层循环 i")
OuterNext --> OuterLoop
OuterLoop -- "i 遍历结束" --> End("返回 maxLength")
关键避坑指南(Must Remember)
- 差一错误 (Off-by-One):
- 记住由
A[j]和A[i]组成的最小等差数列长度是 2。在更新dp[i][d]时,如果找不到前驱,默认值应设为 2。
- 记住由
- 全局最优解更新:
- 答案不一定出现在序列的末尾,必须在内层循环每次计算出新的
length时,立刻更新全局最大值变量。
- 答案不一定出现在序列的末尾,必须在内层循环每次计算出新的
- 时间与空间权衡:
unordered_map虽然平均复杂度是 ,但在极少数数据下可能发生哈希冲突退化到 。在 NOI 竞赛中,如果 范围较小,使用离散化或偏移量数组(Offset Array)通常比哈希表更稳健。
- 重复项处理:
- 如果序列中有重复数字(如
4, 4, 4),公差为 0。本模板通过覆盖更新dp[i][0],可以自然地处理这种情况,无需额外逻辑。
- 如果序列中有重复数字(如