【Leetcode】873-1911
前言
做完之后看了一下之前的提交记录,感觉之前就是碰运气而已,竟然感觉最应该做出来的DP没有,仅仅用到了枚举+hash
思路
如果已经给定一个斐波那契数列,那么如何判断后续的字符其实就十分确定了,就是判断某些数字是否存在,而又因为题目给定了数组是排好序的,因此,如果我们枚举斐波那契数列的起始两个位置,之后判断后续每个位置之后不断更新即可。据此可以写出暴力破解法的代码
class Solution:
def lenLongestFibSubseq(self, arr: List[int]) -> int:
n = len(arr)
ans = 0
# 1. 暴力破解
for i in range(n):
for j in range(i+1,n):
cur = 2
a,b = i,j
for k in range(j+1,n):
if arr[k]==arr[a]+arr[b]:
a,b = b,k
cur += 1
ans = max(ans, cur)
return ans if ans>=3 else 0
可以看到的是,上述代码的时间复杂度为 \(O(n^3)\),但是经过最初的分析我们可以得到的结论是:如果给定初始斐波那契的两个位置,那么其实后续所有斐波那契数列的数字都是固定的,此时只需要逐个判断这些数字是否存在于原数组即可。
因此可以提前将数组中的数字存储于hash表中,此后枚举初始的两个位置,之后直接判断后续斐波那契数字是否在hash中即可。
class Solution:
def lenLongestFibSubseq(self, arr: List[int]) -> int:
t = dict()
for i,x in enumerate(arr):
t[x] = i
ans = 0
n = len(arr)
for i in range(n):
for j in range(i+1,n):
a,b = arr[i],arr[j]
length = 2
while a+b in t:
a,b = b,a+b
length+=1
ans = max(ans,length)
return ans if ans >= 3 else 0
以上代码的时间复杂度分析,两层枚举为\(n^2\),而枚举后续的看似也是一个\(n\)的时间复杂度,但是实际上由于斐波那契数列的增加不是线性的,而增大到可能存在的最大数字\(10^9\),枚举的次数不超过50次,可以视为常数。因此该算法的时间复杂度为\(O(Cn^2)\),C为常数
此外,从另外一个角度出发,我们考虑 使用DP,其中\(dp[i][j]\)表示以\(arr[i]\)和\(arr[j]\)为斐波那契数列的最后两个数字时候斐波那契数列的长度。
\(dp[i][j]=dp[j][had[arr[i]-arr[j]]]\)
需要注意的是,因此给定的数组一定是严格递增的,因此需要满足\(arr[i]-arr[j] \neq arr[j]\)。此外,有因此如果找到了\(arr[i]-arr[j]\),那么此时最短的斐波那契数列是3,因此需要将公式更新为:
\(dp[i][j]=max(dp[j][had[arr[i]-arr[j]]], 3)\)
class Solution:
def lenLongestFibSubseq(self, arr: List[int]) -> int:
n = len(arr)
ans = 0
# dp[i][j]表示以 arr[i] 和 arr[j] 为最后两个元素的斐波那契子序列的长度
# dp[i][j] = dp[j][had[arr[i]-arr[j]]]
# had[x] 表示 x在arr中的位置
dp = [[0] * n for _ in range(n)]
had = dict()
ans = 0
for i in range(n):
for j in range(i):
if arr[j]*2<=arr[i] or arr[i] - arr[j] not in had:continue
dp[i][j] = max(dp[j][had[arr[i] - arr[j]]]+1, 3)
ans = max(ans, dp[i][j])
had[arr[i]] = i
return ans

浙公网安备 33010602011771号