873. 最长的斐波那契子序列的长度【中等题】【每日一题】
思路:【动态规划】【官解注释】
定义二维dp
数组,dp[j][i]
表示以 arr[j]、arr[i]
结尾的斐波那契子序列的长度,我们正序遍历数组,固定下标 i
,然后从下标i-1
开始,倒序遍历数组,固定下标 j
,根据数组严格递增的性质进行剪枝,arr[j] * 2 < arr[i]
,当下标 j
和 i
都固定好之后,我们判断数组中是否存在满足 arr[k] + arr[j] = arr[i] 的arr[k]
,并求出其下标k
,如果不存在,则将k
置为-1
,通过将数组元素及其对应的下标丢入哈希map
中来快速判断是否存在arr[k]
。
如果arr[k]
存在,那么此时判断dp[k][j]
的值,如果小于3
,说明不存在以arr[k]、arr[j]
结尾的斐波那契子序列,但由于此时 arr[k] + arr[j] = arr[i]
,所以dp[j][i] = 3
;如果大于等于3
,那么此时 dp[j][i] = dp[k][j] +1
。
整理可得 如果arr[k]
存在,那么 dp[j][i] = Math.max(dp[k][j]+1,3);
每找到一个dp[j][i]
,就用它尝试去更新最大长度ans
,保持ans
是长度最大的斐波那契子序列。
代码:
class Solution {
public int lenLongestFibSubseq(int[] arr) {
int n = arr.length;
if (n < 3){
return 0;
}
//存储数组元素及其对应的下标
Map<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < n; i++) {
map.put(arr[i],i);
}
//定义dp数组,dp[j][i]表示 以 arr[j]、arr[i]作为最后两个数字的斐波那契子序列长度
int[][] dp = new int[n][n];
//定义ans变量来记录所有可能的斐波那契子序列的最长长度
int ans = 0;
//正序遍历数组,遍历到的下标i作为可能存在的斐波那契子序列的最后一个数字
for (int i = 0; i < n; i++) {
//确定下标i之后,从i-1开始反向遍历数组,遍历到的下标j作为可能存在的斐波那契子序列的倒数第二个数字
//由于数组是严格单调递增的 且倒数第三个斐波那契子序列的假想数字 arr[k] 一定小于 arr[j] < arr[i]
//即 arr[k] < arr[j] < arr[i] => arr[j] > arr[i] /2 => arr[j] * 2 > arr[i]
for (int j = i-1; j >= 0 && arr[j] * 2 > arr[i]; j--) {
//确定好下标j之后,为了确保这是一个斐波那契子序列,则应保证 arr[k] + arr[j] = arr[i]
//通过哈希map确认数组中是否存在满足上述等式的arr[k],如果存在,求出其数组下标k,即arr[k]在map中的value
//如果不存在,则将k赋值为 -1
int k = map.getOrDefault(arr[i]-arr[j],-1);
//如果k>=0说明 k存在,对其dp[j][i]进行更新,否则说明 k不存在,对dp[j][i]不做处理
if (k >= 0){
//由于存在arr[k]满足 arr[k] + arr[j] = arr[i],于是 dp[j][i]的值由 dp[k][j]决定
//转移方程为 dp[j][i] = dp[k][j]+1 , dp[k][j] >= 3
// = 3 ,dp[k][j] < 3
//dp[k][j]小于3表示以arr[k]、arr[j]结尾的斐波那契数列不存在,于是 以 arr[j]、arr[i]结尾的斐波那契数列为起始序列 长度为3
dp[j][i] = Math.max(dp[k][j]+1,3);
}
//用当前的dp[j][i]去更新ans,保持ans是最大的斐波那契子序列
ans = Math.max(ans,dp[j][i]);
}
}
return ans;
}
}