873. 最长的斐波那契子序列的长度
考点
- 线性DP
思路
1. 问题描述与建模
给定一个严格递增的整数数组: \[ \text{arr} = [a_0, a_1, \dots, a_{n-1}] \] 要求找出其中最长的 Fibonacci-like 子序列长度。
Fibonacci-like 子序列满足以下条件:
- 子序列长度至少为 \(3\);
- 对任意连续三个元素 \((x, y, z)\),满足:
\[ x + y = z \]
- 子序列不要求连续,但必须保持原数组中的相对顺序。
2. 关键观察:为什么一维状态不够?
一个自然但错误的直觉是尝试定义:
“以某个值结尾的最长 Fibonacci 子序列长度”
例如设: \[ f[x] = \text{以值 } x \text{ 结尾的最长 Fibonacci-like 子序列长度} \] 但这种状态信息不足,原因在于:
- Fibonacci 递推关系依赖的是最后两个数,而不是最后一个数;
- 即使两个子序列都“以 \(x\) 结尾”,它们的倒数第二个元素不同,能否继续扩展完全不同。
换言之:
是否可以接上下一个数 \(z\),取决于 \((\text{倒数第二个}, \text{倒数第一个})\) 这一个有序对。
因此,仅记录最后一个元素会丢失关键信息,必然导致错误拼接。
3. 正确建模:二维状态定义
为完整描述 Fibonacci 子序列的结构,必须显式记录最后两个元素。
状态定义
设: \[ f[j][i] = \text{以 } a_j, a_i \text{ 为最后两个元素的最长 Fibonacci-like 子序列长度} \] 其中:
- \(0 \le j < i < n\);
- 默认情况下,任意一对 \((a_j, a_i)\) 至少可以构成长度为 \(2\) 的“序列”。
因此初始化为: \[ f[j][i] = 2 \]
4. 状态转移方程推导
考虑如何从已有状态转移到 \(f[j][i]\)。
转移条件
若存在一个下标 \(k\),满足: \[ a_k + a_j = a_i \] 且: \[ k < j < i \] 则可以将以 \((a_k, a_j)\) 结尾的 Fibonacci 子序列,扩展一个元素 \(a_i\)。
转移方程
此时有: \[ f[j][i] = f[k][j] + 1 \] 这是一个标准的“接在末尾”型 DP 转移。
下标合法性的保证
由于原数组严格递增: \[ a_0 < a_1 < \dots < a_{n-1} \] 若: \[ a_k = a_i - a_j < a_j \] 则必然有: \[ k < j \] 因此在实现中,只要保证: \[ a_j \times 2 > a_i \] 就可以隐式保证 \(k < j\),并进行剪枝优化。
5. 算法流程
整体流程如下:
- 使用哈希表
pos,将数组值映射到其下标,便于 \(O(1)\) 查询; - 初始化二维 DP 数组
f[j][i] = 2; - 枚举右端点 \(i\);
- 枚举左端点 \(j < i\),并利用有序性剪枝;
- 若存在 \(k\) 使 \(a_k + a_j = a_i\),更新:
\[ f[j][i] = f[k][j] + 1 \]
- 全程维护最大值作为答案。
6. 时间与空间复杂度
- 时间复杂度: 最外层 \(O(n)\),内层 \(O(n)\),总体为:
\[ O(n^2) \]
- 空间复杂度:
\[ O(n^2) \]
用于存储二维 DP 状态。
7. AC代码
1 | class Solution { |