每日一刷经验分享:873中等. 最长的斐波那契子序列的长度

116 阅读2分钟

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. The first move keeps the parity of the element as it is.
  2. The second move changes the parity of the element.
  3. Since the first move is free, if all the numbers have the same parity, the answer would be zero.
  4. Find the minimum cost to make all the numbers have the same parity.

AC代码

👀Java版本: 方法一:Map+暴力

class Solution {
    public int lenLongestFibSubseq(int[] arr) {
        int len = arr.length;
        int[] dp = new int[len];
        Map<Integer,Integer> map = new HashMap<>();
        for(int i=0;i<len;i++) {dp[i]=1;map.put(arr[i],i);}
        int t,i1,j1,index;
        for(int i=0;i<len-2;i++){
            for(int j=i+1;j<len-1;j++){
                i1 = i;
                j1=j;
                index=2;
                while(map.containsKey(arr[i1]+arr[j1])){
                    t = map.get(arr[i1]+arr[j1]);
                    index++;
                    // if(dp[t]>index) break; 2 8 10 18  和 4 14 18 冲突导致4 14 18被过滤掉,所以这个地方无法剪枝
                    dp[t] = Math.max(dp[t],index);
                    i1=j1;
                    j1 = t;
                }
            }
        }
        int res=1;
        for(int i=len-1;i>=0;i--){
            res = Math.max(res,dp[i]);
        }
        return res==1?0:res;
    }
}

方法二:Map+DP

class Solution {
    public int lenLongestFibSubseq(int[] arr) {
        int n = arr.length,
        int res = 0;
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < n; i++) map.put(arr[i], i);
        int[][] dp = 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; //第一部过滤条件
                if (!map.containsKey(arr[i]-arr[j])) continue;//第二步过滤条件
                int t = map.get(arr[i] - arr[j]);
                dp[i][j] = Math.max(3, dp[j][t] + 1);
                res = Math.max(res, f[i][j]);
            }
        }
        return res;
    }
}

分析

  • 方法一分析 从题意上理解,可知找出斐波那契数组最长的值,最直接简单的想法是直接找出前两个往后遍历,直到找到最长的斐波那契数列,为了能够根据具体的值找到其对应的位置,我们使用HashMap存储其对应的值和对应的位置,在查询的时候方便遍历,但是这种想法有一个问题,就是会有很多重复的遍历,在查看剪枝策略的时候,没有很好的策略能降低时间复杂度和空间复杂度。
  • 方法二分析 由方法一暴露出的问题,我们可以转换策略,使用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[t][j] + 1)

题解过程分析

  • 方法一题解
  1. int[] dp = new int[len]; //定义dp记录以每个节点为结尾的斐波那契数列的长度
  2. Map<Integer,Integer> map = new HashMap<>(); //定义Map记录每个存在的值,及其对应的位置
  3. for(int i=0;i<len-2;i++) for(int j=i+1;j<len-1;j++) //两层for循环遍历以i为第一个,j为第二个斐波那契开始的数组
  • 方法二题解
  1. Map<Integer, Integer> map = new HashMap<>();//定义Map记录每个存在的值,及其对应的位置
  2. int[][] dp = new int[n][n]; dp[i][j]:统计以i最后一个,j为倒数第二个的斐波那契数列是否存在。
  3. int t = map.get(arr[i] - arr[j]); dp[i][j] = Math.max(3, dp[j][t] + 1);遍历的时候比较出最大值。

复杂度分析

  • 时间复杂度 时间复杂度:存入哈希表复杂度为 O(n);DP 过程复杂度为 O(n^2logn).
  • 整体复杂度 整体复杂度为 O(n^2logn)
  • 空间复杂度 空间复杂度:O(n^2)

总结

错误总结

  • 方法一出现的错误

image.png 对于这个错误,当时的代码出想要进行剪枝 测试用例:[2,4,7,8,9,10,14,15,18,23,32,50] // if(dp[t]>index) break; 2 8 10 18 和 4 14 18 冲突导致4 14 18被过滤掉,反而这一步把就较大的给过滤掉了,所以这个地方无法剪枝,

总体方向

根据题意:Map+DP,Map+二分,Map+暴力均可,因为这道题的数据不是很大。

  • 耗费时间分析 image.png
  • 耗费内存分析

image.png

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿