LeetCode 最长的斐波拉契子序列的长度(hash表+动态规划)

版权声明:本文为博主原创文章,博客地址:https://blog.csdn.net/qq_41855420,未经博主允许不得转载。 https://blog.csdn.net/qq_41855420/article/details/91390692

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

n >= 3
对于所有 i + 2 <= n,都有 X_i + X_{i+1} = X_{i+2}
给定一个严格递增的正整数数组形成序列,找到 A 中最长的斐波那契式的子序列的长度。如果一个不存在,返回 0 。

(回想一下,子序列是从原序列 A 中派生出来的,它从 A 中删掉任意数量的元素(也可以不删),而不改变其余元素的顺序。例如, [3, 5, 8] 是 [3, 4, 5, 6, 7, 8] 的一个子序列)

示例 1:

输入: [1,2,3,4,5,6,7,8]
输出: 5
解释:
最长的斐波那契式子序列为:[1,2,3,5,8] 。

示例 2:

输入: [1,3,7,11,12,14,18]
输出: 3
解释:
最长的斐波那契式子序列有:
[1,11,12],[3,11,14] 以及 [7,11,18] 。

提示:

3 <= A.length <= 1000
1 <= A[0] < A[1] < ... < A[A.length - 1] <= 10^9
(对于以 Java,C,C++,以及 C# 的提交,时间限制被减少了 50%)

\color{blue}思路分析: 对于斐波拉契数列,我们只要知道前两个元素的值,那么这个数列就是唯一确定的。比如我们知道某个斐波拉契数列第一个、第二个元素值为1,2,那么它后面的元素必定是3,5,… (根据X_i + X_{i+1} = X_{i+2}规则,可以无线推出下一个元素)。那么我们就会想两个for循环暴力确定斐波拉契数列的前两个元素,然后找出对应的长度,最后更新最大长度。这样做估计会超时,我们没有试,有兴趣可以试试。这种算法就是典型的蛮力法。

现在我们来想想怎么优化这个算法。
首先由示例1中的最长的斐波那契式子序列[1,2,3,5,8],如果我们进行暴力穷举斐波拉契数列的前两个元素,
那么以[1,2]开头会被穷举一次得到[1,2,3,5,8],
以[2,3]开头也会被穷举一次得到[2,3,5,8],
以[3,5]也会被穷举一次得到[3,5,8],
以[5,8]也会被穷举一次得到[5,8],
这些穷举除了第一个穷举,都是在这个最长的斐波拉契数列[1,2,3,5,8]的子序列。

求出来虽然没有错,蛋式没有必要,因为我们第一次把[1,2]作为首元素即可得到这个最长的斐波拉契数列。因此我们需要在求以[1,2]开头的斐波拉契数列时,将[2,3]、[3,5]、[5,8]开头的斐波拉契数列标记已经求解过了。

由于斐波拉契数列是由之前的元素往后推,所以我们将寻找以A[i]、A[j]为起始的斐波拉契数列修改为寻找以A[i]、A[j]为结尾的斐波拉契数列,因此当在[1,2,3,5,8]中A[0] = 1, A[1] = 2时,我们得标记后面的否求解过。

由于我们只知道斐波拉契数列的最后两个元素A[first]、A[second]求和推出下一个元素sum =A[first] + A[second],这时我们需要判断sum是否存在,这样才能继续往下推。因此这时我们就可以使用hash表将每个元素值与下标关联起来,这样方便查找。

class Solution {
public:
    int lenLongestFibSubseq(vector<int>& A) {
        int Asize = A.size(), maxLen = 2;//最长的斐波拉契数列长度初始化为2
        unordered_map<int, int> hashMap;//hashMap[num]记录num在A中的下标
        //将A中的每个元素值与下标关联起来
        for (int i = 0; i < Asize; ++i){
            hashMap[A[i]] = i;
        }
        //dp[i][j]记录以元素i、j结尾的斐波拉契数列的长度
        vector<vector<int>> dp(Asize, vector<int>(Asize, 0));
        for (int i = 0; i < Asize; ++i){
            for (int j = i + 1; j < Asize; ++j){
                if (dp[i][j] == 0){//只有没有查找过的时候才有必要查找,否则会重复
                    int first = i, second = j, nextVal = A[first] + A[second];
                    dp[first][second] = 2;
                    while (hashMap.count(nextVal)){
                        //如果A中存在nextVal(下一个元素),继续往下递推出下一个元素
                        int three = hashMap[nextVal];//获取下一个元素的下标
                        dp[second][three] = dp[first][second] + 1;//由于又寻找到了一个元素,所以标记以A[second]、A[three]结尾的元素已经找到
                        first = second;//更新下标,为下一次求下一个元素的下一个元素做准备
                        second = three;
                        nextVal = A[first] + A[second];//当前下一个需要寻找的元素
                    }
                    if (dp[first][second] > maxLen){//更新最大长度
                        maxLen = dp[first][second];
                    }
                }
            }
        }
        return maxLen == 2 ? 0 : maxLen;
    }
};

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41855420/article/details/91390692