A sequence X_1, X_2, ..., X_n
is fibonacci-like if:html
n >= 3
X_i + X_{i+1} = X_{i+2}
for all i + 2 <= n
Given a strictly increasing array A
of positive integers forming a sequence, find the length of the longest fibonacci-like subsequence of A
. If one does not exist, return 0.git
(Recall that a subsequence is derived from another sequence A
by deleting any number of elements (including none) from A
, without changing the order of the remaining elements. For example, [3, 5, 8]
is a subsequence of [3, 4, 5, 6, 7, 8]
.)github
Example 1:数组
Input: [1,2,3,4,5,6,7,8] Output: 5 Explanation: The longest subsequence that is fibonacci-like: [1,2,3,5,8].
Example 2:优化
Input: [1,3,7,11,12,14,18] Output: 3 Explanation: The longest subsequence that is fibonacci-like: [1,11,12], [3,11,14] or [7,11,18].
Note:code
3 <= A.length <= 1000
1 <= A[0] < A[1] < ... < A[A.length - 1] <= 10^9
这道题给了咱们一个数组,让找其中最长的斐波那契序列,既然是序列而非子数组,那么数字就没必要挨着,可是顺序仍是须要保持,题目中说了数组是严格递增的,其实博主认为这个条件无关紧要的,反正又不能用二分搜索。关于斐波那契数列,想必咱们都据说过,简而言之,除了前两个数字以外,每一个数字等于前两个数字之和。举个生动的例子,大学食堂里今天的汤是昨天的汤加上前天的汤。哈哈,是否是瞬间记牢了。那么既然要找斐波那契数列,首先要肯定起始的两个数字,以后的全部的数字均可以经过将前面两个数组相加获得,那么比较直接暴力的方法,就是遍历全部的两个数字的组合,以其为起始的两个数字,而后再用个 while 循环,不断检测两个数字之和是否存在,那么为了快速查找,要使用一个 HashSet 先把原数组中全部的数字存入,这样就能够进行常数级时间查找了,每找到一个,cnt 自增1(其初始化为2),而后用 cnt 来更新结果 res 便可。最后须要注意的一点是,若 res 小于3的时候,要返回0,由于斐波那契数列的最低消费是3个,参见代码以下:orm
解法一:htm
class Solution { public: int lenLongestFibSubseq(vector<int>& A) { int res = 0, n = A.size(); unordered_set<int> st(A.begin(), A.end()); for (int i = 0; i < n; ++i) { for (int j = i + 1; j < n; ++j) { int a = A[i], b = A[j], cnt = 2; while (st.count(a + b)) { b = a + b; a = b - a; ++cnt; } res = max(res, cnt); } } return (res > 2) ? res : 0; } };
上面的解法存在着一些重复计算,由于当选定的两个起始数字为以前的某个斐波那契数列中的两个数字时,后面的全部状况在以前其实就已经计算过了,没有必要再次计算。因此可使用动态规划 Dynamic Programming 来优化一下时间复杂度,这道题的 DP 定义式也是难点之一,通常来讲,对于子数组子序列的问题,咱们都会使用一个二维的 dp 数组,其中 dp[i][j] 表示范围 [i, j] 内的极值,可是在这道题,这种定义方式绝对够你喝两壶,基本没法写出状态转移方程,由于这道题有隐藏信息 Hidden Information,就算你知道了子区间 [i, j] 内的最长斐波那契数列的长度,仍是没法更新其余区间,由于没有考虑隐藏信息。再回过头来看一下斐波那契数列的定义,从第三个数开始,每一个数都是前两个数之和,因此若想增长数列的长度,这个条件必定要一直保持,好比对于数组 [1, 2, 3, 4, 7],在子序列 [1, 2, 3] 中以3结尾的斐氏数列长度为3,虽然 [3, 4, 7] 也能够组成斐氏数列,可是以7结尾的斐氏数列长度更新的时候不能用以3结尾的斐氏数列长度的信息,由于 [1, 2, 3, 4, 7] 不是一个正确的斐氏数列,虽然 1+2=3, 3+4=7,可是 2+3!=4。因此每次只能增长一个长度,并且必需要知道前两个数字,正确的 dp[i][j] 应该是表示以 A[i] 和 A[j] 结尾的斐氏数列的长度,很特别吧,以前好像都没这么搞过。blog
接下来看该怎么更新 dp 数组,咱们仍是要肯定两个数字,跟以前的解法不一样的是,先肯定一个数字,而后遍历以前比其小的全部数字,这样 A[i] 和 A[j] 两个数字肯定了,此时要找一个比 A[i] 和 A[j] 都小的数,即 A[i]-A[j],若这个数字存在的话,说明斐氏数列存在,由于 [A[i]-A[j], A[j], A[i]] 是知足斐氏数列要求的。这样状态转移就有了,dp[j][i] = dp[indexOf(A[i]-A[j])][j] + 1,可能看的比较晕,但其实就是 A[i] 加到了以 A[j] 和 A[i]-A[j] 结尾的斐氏数列的后面,使得长度增长了1。这个更新方式感受跟以前那道 Coin Change 有着殊途同归之妙。不过前提是 A[i]-A[j] 必需要在原数组中存在,并且还须要知道某个数字在原数组中的坐标,那么就用 HashMap 来创建数字跟其坐标之间的映射。能够事先把全部数字都存在 HashMap 中,也能够在遍历i的时候创建,由于咱们只关心位置i以前的数字。这样在算出 A[i]-A[j] 以后,在 HashMap 查找差值是否存在,不存在的话赋值为 -1。在更新 dp[j][i] 的时候,咱们看 A[i]-A[j] < A[j] 且 k>=0 是否成立,由于 A[i]-A[j] 是斐氏数列中最小的数,且其位置k必需要存在才能更新。不然的话更新为2。最后仍是要注意,若 res 小于3的时候,要返回0,由于斐波那契数列的最低消费是3个,参见代码以下:ci
解法二:
class Solution { public: int lenLongestFibSubseq(vector<int>& A) { int res = 0, n = A.size(); unordered_map<int, int> m; vector<vector<int>> dp(n, vector<int>(n)); for (int i = 0; i < n; ++i) { m[A[i]] = i; for (int j = 0; j < i; ++j) { int k = m.count(A[i] - A[j]) ? m[A[i] - A[j]] : -1; dp[j][i] = (A[i] - A[j] < A[j] && k >= 0) ? (dp[k][j] + 1) : 2; res = max(res, dp[j][i]); } } return (res > 2) ? res : 0; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/873
相似题目:
Split Array into Fibonacci Sequence
参考资料:
https://leetcode.com/problems/length-of-longest-fibonacci-subsequence/