873. 最长的斐波那契子序列的长度

题目描述:

如果序列 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

解题分析及思路:

1、dfs

首先这道题给我的第一直觉就是它能够使用dfs进行解答,事实也是如此。

对于数列序列 X_1, X_2, …, X_n,满足X_i + X_{i+1} = X_{i+2},那么只需要每次将前两个满足条件的数值缓存下来,访问下一个元素,如果满足条件更新两个临时值,结果加一,否则跳过该数值,直到遍历完这个数组

其实是很符合dfs的条件的,无奈运行超时,只能放弃,不过思想还是值得大家借鉴的。最长的斐波那契子序列的长度 🔗

2、dp + hash

对于长度为n的数列,需要为其构建一个n ^ 2的二维数组dp,保存其dp[row][col]位置满足斐波那契序列的组数。

对于dp[row][col],满足 arr[row] + arr[col] = num,并且num确实是存在与arr数组中,其下标为index,那么dp[row][col] = dp[col][index] + 1,代表存在一组这样的数满足条件。

另一个问题就来了,就算找到数值num,那么如何通过num判断它是否存在与arr中并且找到其所对应的下标呢?

可以采用map来存arr中每一个元素和该元素对应的下标,元素num作为key,其下标index作为value,那么问题就解决了。

还需要注意两个地方:

  • 因为是严格递增的数组,所以内部循环无需遍历n次,到外部循环的所在的下标停止即可。
  • 因为设置了dp[row][col] 存放的是满足斐波那契序列的组数,然而题目是返回满足斐波那契序列的元素个数,所以元素个数会比组数多2,在返回结果时加2再返回即可。并且最终结果小于3是无法组成满足斐波那契序列的,返回0即可。

代码分析:

1、dfs

首先看超时的dfs:

var result int
var dfs func(arr []int, start, l, num1, num2, num int)
dfs = func(arr []int, start, l, num1, num2, num int) {
    if result < num && num > 2  {
        result = num
    }
    if start >= l {
        return
    }
    for index := start; index < l; index++ {
        if num1+num2 == arr[index] || num1 == 0 || num2 == 0 {
            num++
            dfs(arr, index+1, l, num2, arr[index], num)
            num--
        }
    }
}

因为起始状态,num1num2并没有存任何的数,这里只需做num1 == 0 || num2 == 0条件的额外判断即可。

2、dp + hash

首先定义map以及初始化dp数组:

l := len(arr)
m := make(map[int]int)
dp := make([][]int, l)
for index := 0; index < l; index++ {
    m[arr[index]] = index
    dp[index] = make([]int, l)
}

dp的核心所在:

从二维数组的右下角开始遍历,如果满足条件,将其结果加1并且保存到最终结果中。

for row := l - 1; row >= 0; row-- {
    for col := l - 1; col > row; col-- {
        if index, ok := m[arr[col]+arr[row]]; ok {
            dp[row][col] = dp[col][index] + 1
            ans = max(ans, dp[row][col])
        }
    }
}

最后就是结果的额外处理:

if ans == 0 {
    return
}
return ans + 2

复杂度:

  • 时间复杂度:O(n ^ 2),动态规划所需时间
  • 空间复杂度:O(n ^ 2),动态规划数组所需空间

执行结果:

  • 执行用时: 192 ms , 在所有 Go 提交中击败了 55.84% 的用户
  • 内存消耗: 17.3 MB , 在所有 Go 提交中击败了 63.64% 的用户

通过次数 54K 提交次数 96.1K 通过率 56.2%

Related Posts

1026. 节点与其祖先之间的最大差值

## 题目描述:给定二叉树的根节点 root,找出存在于 不同 节点 A 和 B 之间的最大值 V,其中 V = |A.val - B.val|,且 A 是 B 的祖先。(如果 A 的任何子节点之一为 B,或者 A 的任何子节点是 B 的祖先,那么我们认为 A 是 B 的祖先)示例 1: ![](/img/leetcode/1026节点与其祖先之间的最大差值/tmp-tre

read more

105. 从前序与中序遍历序列构造二叉树

## 题目描述:给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。示例 1: ``` 输入: preorder = [3,9,20,15,7], inorder = [

read more

106. 从中序与后序遍历序列构造二叉树

## 题目描述:给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。示例 1: ``` 输入:inorder = [9,3,15,20,7], postorder

read more

109. 有序链表转换二叉搜索树

## 题目描述:给定一个单链表的头节点 head ,其中的元素 按升序排序 ,将其转换为高度平衡的二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差不超过 1。示例 1: ``` 输入: head = [-10,-3,0,5,9] 输出: [0,-3,9

read more

114. 二叉树展开为链表

## 题目描述:给你二叉树的根结点 root ,请你将它展开为一个单链表:- 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。 - 展开后的单链表应该与二叉树 先序遍历 顺序相同。示例 1: ``` 输入:root

read more

1161. 最大层内元素和

## 题目描述:给你一个二叉树的根节点 root。设根节点位于二叉树的第 1 层,而根节点的子节点位于第 2 层,依此类推。请返回层内元素之和 最大 的那几层(可能只有一层)的层号,并返回其中 最小 的那个。**测试用例:**示例 1: ``` 输入:root = [1,7,0,7,-8

read more