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
题型:哈希表,数组,动态规划
提示:
- The first move keeps the parity of the element as it is.
- The second move changes the parity of the element.
- Since the first move is free, if all the numbers have the same parity, the answer would be zero.
- 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)
题解过程分析
- 方法一题解
- int[] dp = new int[len]; //定义dp记录以每个节点为结尾的斐波那契数列的长度
- Map<Integer,Integer> map = new HashMap<>(); //定义Map记录每个存在的值,及其对应的位置
- for(int i=0;i<len-2;i++) for(int j=i+1;j<len-1;j++) //两层for循环遍历以i为第一个,j为第二个斐波那契开始的数组
- 方法二题解
- Map<Integer, Integer> map = new HashMap<>();//定义Map记录每个存在的值,及其对应的位置
- int[][] dp = new int[n][n]; dp[i][j]:统计以i最后一个,j为倒数第二个的斐波那契数列是否存在。
- 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)
总结
错误总结
- 方法一出现的错误
对于这个错误,当时的代码出想要进行剪枝
测试用例:[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+暴力均可,因为这道题的数据不是很大。
- 耗费时间分析
- 耗费内存分析
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿