LeetCode LCR 093. 最长的斐波那契子序列的长度
题目描述


题意分析
在严格递增的正整数数组中选择一个子序列,要求长度至少为三,且从第三项开始,每项都等于前两项之和。元素可以不连续,但下标顺序必须保留;不存在这样的子序列时返回零。
只知道末尾一项不足以确定下一项,还必须知道倒数第二项。因此用最后两个下标描述状态。数组严格递增、没有重复值,又使这两个末尾值对应的前一项至多只有一个。
解法:末尾两项定义 DP
核心思路
[!blue]
定义
dp[j][i]为以arr[j]、arr[i]作为最后两项的最长链长度,其中j < i。任意两项都可以作为待扩展的起点,所以这些状态初始为2,但两项本身还不算题目要求的答案。若当前链还能向前接一项,它的值必须是
delta = arr[i] - arr[j]。用“值到下标”的哈希表找到对应下标k;只有k < j,三项才按原数组顺序排列,并且没有复用元素。当前驱存在且顺序合法时,任何以这两项结尾的链都必须从
(k, j)延伸,因此dp[j][i] = dp[k][j] + 1。不存在合法前驱时就保留长度二。这里用减法是为了直接确定前驱值,无需逐个枚举第三个下标。外层按末项下标
i递增,计算(j, i)时,被依赖的(k, j)已在更早的外层轮次完成。只在成功连接前驱时更新答案,保证参与答案的长度至少为三;答案初始为零,无解时自然保持零。
解题步骤
- 建立值到下标的哈希表,并把所有
j < i的dp[j][i]初始化为2。- 按
i从小到大枚举最后一项,再枚举前一项j < i。- 查找
arr[i] - arr[j]。若得到的下标满足k < j,用dp[k][j] + 1更新当前状态和答案。- 返回答案。Java 先判断键是否存在;Go 保存的是下标加一,查不到时减一得到
-1,因此还要检查k >= 0。
代码实现
class Solution {
public int lenLongestFibSubseq(int[] arr) {
int n = arr.length;
// 数组严格递增无重复,值到下标是单射。
Map<Integer, Integer> mp = new HashMap<>();
for (int i = 0; i < n; ++i) {
mp.put(arr[i], i);
}
// dp[j][i]:以 arr[j]、arr[i] 结尾的最长斐波那契式子序列长度,基准为 2。
int[][] dp = new int[n][n];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
dp[j][i] = 2;
}
}
int answer = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
int delta = arr[i] - arr[j];
if (mp.containsKey(delta)) {
int k = mp.get(delta);
// k < j 必须严格:k == j 会复用同一元素,k > j 则顺序颠倒。
if (k < j) {
dp[j][i] = dp[k][j] + 1;
answer = Math.max(answer, dp[j][i]);
}
}
}
}
// 只有转移成功才更新答案,因此无解时自然返回 0。
return answer;
}
}
func lenLongestFibSubseq(arr []int) int {
n := len(arr)
// 存下标加一,查不到时零值 0 减一得 -1,与非法下标统一处理。
mp := make(map[int]int, n)
for i, v := range arr {
mp[v] = i + 1
}
// dp[j][i]:以 arr[j]、arr[i] 结尾的最长斐波那契式子序列长度,基准为 2。
dp := make([][]int, n)
for i := 0; i < n; i++ {
dp[i] = make([]int, n)
for j := 0; j < i; j++ {
dp[j][i] = 2
}
}
answer := 0
for i := 0; i < n; i++ {
for j := 0; j < i; j++ {
// 用减法回溯倒数第三项。
delta := arr[i] - arr[j]
k := mp[delta] - 1
// k >= 0 表示存在,k < j 保证下标严格递增。
if k >= 0 && k < j {
dp[j][i] = dp[k][j] + 1
answer = max(answer, dp[j][i])
}
}
}
return answer
}
复杂度分析
- 时间复杂度:期望 $O(n^2)$,枚举所有末尾下标对,每对做常数次哈希查询与转移。
- 空间复杂度:$O(n^2)$,保存成对状态,哈希表另占 $O(n)$。前驱状态分布在不同列,不能直接按相邻两行滚动。
关键点总结
[!green]
- 最后两项决定唯一的前驱值,严格递增保证该值最多对应一个下标。
- 只有
k < j < i才是合法的子序列延伸,找到数值还不够。- 长度二是递推基准,长度至少三才计入答案。
- 按最后一个下标递增计算,保证依赖的短链已经得到结果。
易错点总结
[!yellow]
- 状态必须包含最后两个下标,因为下一项由最后两个值共同决定。
- 找到倒数第三项后还要检查下标 k<j,防止复用元素或破坏原顺序。
- 两元素状态长度为 2,但答案只有形成至少三项时才更新,无合法链返回 0。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 300. 最长递增子序列 | 中等 | 同样选取保持下标顺序的最长子序列,本题下一项由前两项之和确定,需要成对状态。 |
| 1027. 最长等差数列 | 中等 | 同样以最后两个元素或差值确定可延续条件,原题保持固定差,本题满足斐波那契加法关系。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!