LeetCode算法日记 - Day 94: 最长的斐波那契子序列的长度

目录

[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²)
相关推荐
Zz_waiting.2 小时前
统一服务入口-Gateway
java·开发语言·gateway
ada7_2 小时前
LeetCode(python)——49.字母异位词分组
java·python·leetcode
L_09072 小时前
【Algorithm】Day-11
c++·算法·leetcode
DyLatte2 小时前
AI时代的工作和成长
java·后端·程序员
青春不流名2 小时前
nginx
java
薛慕昭3 小时前
C语言核心技术深度解析:从内存管理到算法实现
c语言·开发语言·算法
.ZGR.3 小时前
第十六届蓝桥杯省赛 C 组——Java题解1(链表知识点)
java·算法·链表·蓝桥杯
近津薪荼3 小时前
每日一练 1(双指针)(单调性)
c++·算法
林太白3 小时前
八大数据结构
前端·后端·算法