目录
[1. 最长的斐波那契子序列的长度](#1. 最长的斐波那契子序列的长度)
[1.1 题目解析](#1.1 题目解析)
[1.2 解法](#1.2 解法)
[1.3 代码实现](#1.3 代码实现)
1. 最长的斐波那契子序列的长度
https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/description/
如果序列 x1, x2, ..., xn 满足下列条件,就说它是 斐波那契式 的:
- n >= 3
- 对于所有 i + 2 <= n,都有 xi + xi+1 == xi+2
给定一个 严格递增 的正整数数组形成序列 arr ,找到 arr 中最长的斐波那契式的子序列的长度。如果不存在,返回 0 。
子序列 是通过从另一个序列 arr 中删除任意数量的元素(包括删除 0 个元素)得到的,同时不改变剩余元素顺序。例如,[3, 5, 8] 是 [3, 4, 5, 6, 7, 8] 的子序列。
示例 1:
输入:arr =[1,2,3,4,5,6,7,8]
输出:5
解释:最长的斐波那契式子序列为 [1,2,3,5,8] 。
示例 2:
输入:arr =[1,3,7,11,12,14,18]
输出:3
解释: 最长的斐波那契式子序列有 [1,11,12]、[3,11,14] 以及 [7,11,18] 。
提示:
- 3 <= arr.length <= 1000
- 1 <= arr[i] < arr[i + 1] <= 109
1.1 题目解析
题目本质
在严格递增数组中找满足 x[i] + x[i+1] = x[i+2] 的最长子序列。核心是在给定约束下的子序列计数问题。
常规解法
最直观的想法:枚举所有可能的起始两个数 (a, b),然后不断尝试在后面找 a+b,找到就更新 a=b, b=a+b,继续找下一个。
java
public int lenLongestFibSubseq(int[] arr) {
int n = arr.length;
Set<Integer> set = new HashSet<>();
for(int x : arr) set.add(x);
int maxLen = 0;
for(int i = 0; i < n - 1; i++){
for(int j = i + 1; j < n; j++){
int a = arr[i], b = arr[j];
int len = 2;
while(set.contains(a + b)){
int temp = b;
b = a + b;
a = temp;
len++;
}
if(len >= 3) maxLen = Math.max(maxLen, len);
}
}
return maxLen;
}
问题分析
这个解法时间复杂度是 O(n² × L),其中 L 是斐波那契序列的最长长度。虽然能过,但有重复计算:
- 序列 [1,2,3,5] 和 [1,2,3,5,8] 都会从 (1,2) 开始重新计算到 3、5
- 当我们计算以 (2,3) 结尾的序列时,其实已经知道了以 (1,2) 结尾的长度
思路转折
要想高效 → 必须避免重复计算 → 用 DP 记录状态
关键洞察:斐波那契序列由最后两个数唯一确定下一个数。如果知道了 (arr[i], arr[j]) 结尾的序列长度,当找到 arr[k] = arr[i] + arr[j] 时,就能直接扩展。
反向思考:要求以 (arr[i], arr[j]) 结尾的序列,需要找前面是否存在 arr[k],使得 arr[k] + arr[i] = arr[j]。
1.2 解法
算法思想
动态规划 + 哈希表加速查找,定义状态:dp[i][j] = 以 arr[i] 和 arr[j] 为最后两个元素的斐波那契序列的最大长度
状态转移:
- 对于位置 i < j,寻找是否存在 k < i,使得 arr[k] + arr[i] = arr[j]
- 如果存在:dp[i][j] = dp[k][i] + 1
- 如果不存在:dp[i][j] = 2(只有这两个数)
**i)建立哈希表:**用 HashMap 存储 值 → 索引 的映射,实现 O(1) 查找
**ii)初始化 DP 表:**所有 dp[i][j] 初始化为 2(任意两数都能构成"长度为2"的序列开头)
**iii)双层循环枚举结尾对:**外层固定 j,内层枚举 i < j
**iv)查找前驱元素:**计算 target = arr[j] - arr[i],在哈希表中查找
**v)状态转移:**如果找到且满足 target < arr[i](保证索引顺序),则从 dp[k][i] 转移
**vi)更新答案:**维护全局最大值,最后判断是否 ≥3
易错点
- 为什么要判断 a < arr[i](b)? 因为数组严格递增,防止 b a c 这样的顺序破坏了子序列的顺序性。
- 为什么初始化为 2 而不是 0? 因为即使找不到前驱元素,(arr[i], arr[j]) 本身也是一个长度为 2 的合法子序列。初始化为 2 可以简化代码逻辑。
- 最后为什么要判断 ret == 2 ? 0 : ret? 题目要求至少 3 个元素才算斐波那契序列,如果全局最大值还是 2,说明不存在有效序列,返回 0。
- 循环起点为什么是 j=2, i=1? 因为至少需要 3 个元素,所以 j 至少为 2;而 i 需要在 k 和 j 之间,至少为 1。
1.3 代码实现
java
class Solution {
public int lenLongestFibSubseq(int[] arr) {
int n = arr.length;
// dp[i][j]: 以 arr[i] 和 arr[j] 结尾的斐波那契序列长度
int[][] dp = new int[n][n];
// 因为查询的是值(arr[j] - arr[i]),所以用哈希表加快搜索效率
HashMap<Integer, Integer> hash = new HashMap<>();
for(int[] x : dp){
// 初始化为 2,因为可能找不到前驱或前驱在中间,长度就是 2(不存在)
Arrays.fill(x, 2);
}
for(int i = 0; i < n; i++){
hash.put(arr[i], i); // 值 -> 索引
}
int ret = 2;
for(int j = 2; j < n; j++){
for(int i = 1; i < j; i++){
// 计算前驱元素的值,看哈希表是否存在
int target = arr[j] - arr[i];
if(target < arr[i] && hash.containsKey(target)){
// 找到了!直接继承前面序列的长度 + 1
dp[i][j] = dp[hash.get(target)][i] + 1;
}
ret = Math.max(ret, dp[i][j]);
}
}
return ret == 2 ? 0 : ret; // 至少 3 个元素才算有效
}
}
复杂度分析
- **时间复杂度:O(n²),**双层循环枚举所有 (i, j) 对 O(n²),每次哈希表查找和状态转移 O(1)
- **空间复杂度:O(n²),**DP 数组 O(n²)、哈希表 O(n),总体:O(n²)