873. 最长的斐波那契子序列的长度 : 经典序列 DP 运用题

秦业
• 阅读 438

题目描述

这是 LeetCode 上的 873. 最长的斐波那契子序列的长度 ,难度为 中等

Tag : 「序列 DP」、「哈希表」、「动态规划」

如果序列 $X_1, X_2, ..., X_n$ 满足下列条件,就说它是 斐波那契式 的:

  • n >= 3
  • 对于所有 i + 2 <= n,都有 $X_i + X_{i+1} = X_{i+2}$

给定一个严格递增的正整数数组形成序列 arr,找到 arr 中最长的斐波那契式的子序列的长度。如果一个不存在,返回  $0$ 。

(回想一下,子序列是从原序列 arr 中派生出来的,它从 arr 中删掉任意数量的元素(也可以不删),而不改变其余元素的顺序。例如, [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] <= 10^9$

序列 DP

定义 $f[i][j]$ 为使用 $arr[i]$ 为斐波那契数列的最后一位,使用 $arr[j]$ 为倒数第二位(即 $arr[i]$ 的前一位)时的最长数列长度。

不失一般性考虑 $f[i][j]$ 该如何计算,首先根据斐波那契数列的定义,我们可以直接算得 $arr[j]$ 前一位的值为 $arr[i] - arr[j]$,而快速得知 $arr[i] - arr[j]$ 值的坐标 $t$,可以利用 arr 的严格单调递增性质,使用「哈希表」对坐标进行转存,若坐标 $t$ 存在,并且符合 $t < j$,说明此时至少凑成了长度为 $3$ 的斐波那契数列,同时结合状态定义,可以使用 $f[t][j]$ 来更新 $f[i][j]$,即有状态转移方程:

$$ f[i][j] = \max(3, f[j][t] + 1) $$

同时,当我们「从小到大」枚举 $i$,并且「从大到小」枚举 $j$ 时,我们可以进行如下的剪枝操作:

  • 可行性剪枝:当出现 $arr[i] - arr[j] >= arr[j]$,说明即使存在值为 $arr[i] - arr[j]$ 的下标 $t$,根据 arr 单调递增性质,也不满足 $t < j < i$ 的要求,且继续枚举更小的 $j$,仍然有 $arr[i] - arr[j] >= arr[j]$,仍不合法,直接 break 掉当前枚举 $j$ 的搜索分支;
  • 最优性剪枝:假设当前最大长度为 ans,只有当 $j + 2 > ans$,我们才有必要往下搜索,$j + 2$ 的含义为以 $arr[j]$ 为斐波那契数列倒数第二个数时的理论最大长度。

代码:

class Solution {
    public int lenLongestFibSubseq(int[] arr) {
        int n = arr.length, ans = 0;
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < n; i++) map.put(arr[i], i);
        int[][] f = new int[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = i - 1; j >= 0 && j + 2 > ans; j--) {
                if (arr[i] - arr[j] >= arr[j]) break;
                int t = map.getOrDefault(arr[i] - arr[j], -1);
                if (t == -1) continue;
                f[i][j] = Math.max(3, f[j][t] + 1);
                ans = Math.max(ans, f[i][j]);
            }
        }
        return ans;
    }
}
  • 时间复杂度:存入哈希表复杂度为 $O(n)$;DP 过程复杂度为 $O(n^2)$。整体复杂度为 $O(n^2)$
  • 空间复杂度:$O(n^2)$

最后

这是我们「刷穿 LeetCode」系列文章的第 No.873 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSou...

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

本文由mdnice多平台发布

点赞
收藏
评论区
推荐文章
一只编程熊 一只编程熊
3年前
【LeetCode每日一题 Day 5】5. 最长回文子串
大家好,我是编程熊,今天是LeetCode每日一题的第五天,一起学习LeetCode第五题《最长回文子串》。题意给你一个字符串s,找到s中最长的回文子串。示例txt输入:s"babad"输出:"bab"解释:"aba"同样是符合题意的答案。题解方法一采用简单暴力的方法,枚举每一个位置为单独回文中心(长度为奇数的子串,如aba)或者回文中心
Wesley13 Wesley13
3年前
java.util.ConcurrentModificationException异常原因及解决方法
今天在做一个很简单的Java练习题的时候遇到这个问题。题目:一对兔子在出生第三个月的时候开始,每个月会生一对小兔子。当小兔子在它们第三个月的时候,同理。问:每个月的兔子总数。这是一个很简单的题,前6个月的兔子数量是,1,1,2,3,5,8.斐波拉契数列。按这个规律就可以得出结果。但是我是用创建对象的方法来做。代码:publiccla
Wesley13 Wesley13
3年前
java基础编程练习题
1、題目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?123456711235813这就是斐波那契数列(Fibonaccisequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Le
Stella981 Stella981
3年前
Scala
欢迎大家关注:scala工具库(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgithub.com%2Fjacksu%2Futils4s),里面包含各种库的测试用例和使用说明文档模式匹配使用用模式匹配实现斐波那契deffibonacci(in:Any):
Wesley13 Wesley13
3年前
Java面试不得不知的程序(二)
【程序1】 题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?斐波那契数列:前面相邻两项之和,构成了后一项通项公式注:此时a11,a21,ana(n1)a(n2)(n3,n∈N\)通项公式的推导斐波那契数列:1、1
Wesley13 Wesley13
3年前
JAVA学习入门篇_递归结构
递归结构递归是一种常见的解决问题的方法,即把问题逐渐简单化。递归的基本思想就是“自己调用自己”,一个使用递归技术的方法将会直接或者间接的调用自己。​利用递归可以用简单的程序来解决一些复杂的问题。比如:斐波那契数列的计算、汉诺塔、快排等问题。​递归结构包括两个部分:​1.定义递归头。解答:什么
菜园前端 菜园前端
1年前
什么是动态规划?
原文链接:什么是动态规划?动态规划也是算法设计的一种方法/思想。它将一个问题分解为相互重叠的子问题,通过反复求解子问题,来解决原来的问题。基础案例场景一斐波那契数列当前数等于前面两个数的和。定义子问题:f(n)f(n1)f(n2)
贾蔷 贾蔷
1个月前
力扣1137题 解题思路和步骤 C++代码实现,力扣一共多少题
一、题目分析力扣1137题要求我们找到第N个泰波那契数。泰波那契数的定义是:T00,T11,T21,且在n0的条件下Tn3TnTn1Tn2。,当n4时,T4T3T2T14。这道题主要考查我们对递归或动态规划的理解和运用。在思考解题方法时,我们
贾蔷 贾蔷
2星期前
蓝桥杯2023接龙数列(洛谷P9242)题解:动态规划与数字首尾匹配的完美应用
一、题目解读这道蓝桥杯省赛真题要求找出数字序列中最长的接龙子序列(每个数字的首位等于前一个数字的末位),并计算需要删除的最少数字个数。题目考察动态规划的实际应用能力,是理解数字特征处理和状态转移的典型案例。二、解题步骤1.处理n1的特殊边界情况2.读取输入
贾蔷 贾蔷
2星期前
洛谷P1255题 解题思路和步骤 C++实现带注释,c++入门基础题
一、问题描述与递推关系建立洛谷P1255数楼梯问题要求计算n级台阶的不同走法数,每次可以跨1级或2级。这本质上是斐波那契数列的变种问题,递推公式为f(n)f(n1)f(n2)。当n≤50时可用普通整型存储,但题目中n可能达到5000,这就必须使用高精度运
贾蔷 贾蔷
3天前
牛客13279题解:利用递归与深度优先搜索计算树的最大高度(附完整代码)
一、题目解读牛客13279题要求计算给定树的最大高度。题目输入一棵以邻接表形式表示的树(节点从0开始编号),需要输出从根节点到最深叶节点的最长路径长度。树的结构由n个节点和n1条边构成,保证为连通无环图。理解题目核心在于准确获取树的拓扑关系,并设计算法遍历
秦业
秦业
Lv1
试问岭南好不好。却道。此心安处是吾乡。
文章
4
粉丝
0
获赞
0