A sequence `X_1, X_2, ..., X_n` is *fibonacci-like* if:
n >= 3
X_i + X_{i+1} = X_{i+2}
for alli + 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.
(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]
.)
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:
3 <= A.length <= 1000
1 <= A[0] < A[1] < ... < A[A.length - 1] <= 10^9
- (The time limit has been reduced by 50% for submissions in Java, C, and C++.)
這道題給了我們一個數組,讓找其中最長的斐波那契序列,既然是序列而非子數組,那么數字就不必挨着,但是順序還是需要保持,題目中說了數組是嚴格遞增的,其實博主認為這個條件可有可無的,反正又不能用二分搜索。關於斐波那契數列,想必我們都聽說過,簡而言之,除了前兩個數字之外,每個數字等於前兩個數字之和。舉個生動的例子,大學食堂里今天的湯是昨天的湯加上前天的湯。哈哈,是不是瞬間記牢了。那么既然要找斐波那契數列,首先要確定起始的兩個數字,之后的所有的數字都可以通過將前面兩個數組相加得到,那么比較直接暴力的方法,就是遍歷所有的兩個數字的組合,以其為起始的兩個數字,然后再用個 while 循環,不斷檢測兩個數字之和是否存在,那么為了快速查找,要使用一個 HashSet 先把原數組中所有的數字存入,這樣就可以進行常數級時間查找了,每找到一個,cnt 自增1(其初始化為2),然后用 cnt 來更新結果 res 即可。最后需要注意的一點是,若 res 小於3的時候,要返回0,因為斐波那契數列的最低消費是3個,參見代碼如下:
解法一:
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] 結尾的斐氏數列的長度,很特別吧,之前好像都沒這么搞過。
接下來看該怎么更新 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個,參見代碼如下:
解法二:
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/
[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)