Length of Longest Fibonacci Subsequence

Problem

Given a strictly increasing array arr of positive integers, return the length of the longest Fibonacci-like subsequence of arr. If one does not exist, return 0.

  • 3 ≤ arr.length ≤ 1000
  • 1 ≤ arr[i] < arr[i+1] ≤ 10⁹

Example

Input: arr = [1,2,3,4,5,6,7,8]
Output: 5

The longest Fibonacci-like subsequence is [1,2,3,5,8]. The algorithm first maps each value to its index for constant-time lookup. It then tracks subsequences using dp[i][j], which represents the length of a Fibonacci-like sequence ending with arr[i] and arr[j]. For each pair (i, j), it determines whether a previous number arr[k] exists such that arr[k] + arr[i] = arr[j]. If so, the subsequence ending at (k, i) can be extended to include arr[j], increasing its length.

Approach

Straightforward Solution

A brute-force approach would try all subsequences and check if they are Fibonacci-like, which is exponential and infeasible for large arrays.

Core Observation

A Fibonacci-like sequence is determined by its last two elements. If arr[i] and arr[j] are the final two numbers of such a sequence, then the previous number must satisfy arr[k] + arr[i] = arr[j]. Rearranging gives arr[k] = arr[j] - arr[i]. This observation allows the algorithm to determine whether a pair (i, j) can extend an existing Fibonacci-like subsequence.

Path to Optimal

Since a Fibonacci-like sequence depends on its last two values, the algorithm tracks subsequences by their final pair of indices. Let dp[i][j] represent the length of the longest Fibonacci-like subsequence ending with arr[i] followed by arr[j]. If a value arr[k] exists such that arr[k] + arr[i] = arr[j] and k < i, then the pair (i, j) can extend the subsequence ending at (k, i).

Optimal Approach

Construct a value-to-index map to allow constant-time lookup of any number's index. Initialize a DP table where dp[i][j] represents the length of the longest Fibonacci-like subsequence ending with arr[i] and arr[j], with all entries starting at 2 since any pair forms a subsequence of length two. For each pair (i, j), compute prev = arr[j] - arr[i]. If prev exists in the array at index k < i, then extend the subsequence by setting dp[i][j] = dp[k][i] + 1 and update the maximum length found.

Time

O(n^2)

The algorithm iterates over all pairs (i, j) in the array, which is O(n^2). Each lookup in the hash map is O(1), so total time is O(n^2).

Space

O(n^2)

The DP table stores lengths for all pairs (i, j), requiring O(n^2) auxiliary space. The hash map requires O(n) space for value-to-index mapping.

Pattern Spotlight

Dynamic Programming (Pair-State Tracking)

When a sequence's validity depends on the previous two elements, represent the state using a pair of indices. Store the best sequence ending with each pair and extend it by locating the element that must precede the pair using a hash map.

Solution

1class Solution:
2 def lenLongestFibSubseq(self, arr):
3 n = len(arr)
4
5 value_to_index = {}
6 for i in range(n):
7 value_to_index[arr[i]] = i
8
9 dp = [[2] * n for _ in range(n)]
10
11 longest = 0
12
13 for j in range(n):
14 for i in range(j):
15
16 prev = arr[j] - arr[i]
17
18 if prev in value_to_index:
19 k = value_to_index[prev]
20
21 if k < i:
22 dp[i][j] = dp[k][i] + 1
23 longest = max(longest, dp[i][j])
24
25 return longest if longest >= 3 else 0

Step-by-Step Solution

1

Build Value-to-Index Map for O(1) Lookup

3n = len(arr)
5value_to_index = {}
6for i in range(n):
7 value_to_index[arr[i]] = i

Objective

To create a hash map that maps each array value to its index for constant-time lookups.

Key Insight

The DP recurrence requires quickly finding if a value exists in the array and its index. A hash map enables O(1) lookups, which is critical to avoid an O(n^3) brute-force search for the previous element in the Fibonacci-like sequence.

Interview Quick-Check

Core Logic

Mapping values to indices allows the algorithm to find the potential previous element in O(1) time, enabling efficient DP updates.

Common Pitfalls & Bugs

Not building this map leads to O(n) searches for each pair, resulting in O(n^3) time complexity.

2

Initialize DP Table with Base Lengths

9dp = [[2] * n for _ in range(n)]

Objective

To set up a 2D DP array where each entry dp[i][j] starts at 2, representing the minimal length of a Fibonacci-like subsequence with two elements.

Key Insight

Every pair of elements can be considered a subsequence of length 2. The DP table stores the length of the longest Fibonacci-like subsequence ending with arr[i] and arr[j]. Initializing with 2 sets the baseline before any extensions.

Interview Quick-Check

Core Logic

Initializing dp[i][j] to 2 reflects that any two elements form a trivial subsequence of length 2, which can be extended if a valid previous element is found.

Complexity

The DP table requires O(n^2) space, which is acceptable given the problem constraints.

3

Iterate Over Pairs and Extend Fibonacci Subsequences

11longest = 0
13for j in range(n):
14 for i in range(j):
16 prev = arr[j] - arr[i]
18 if prev in value_to_index:
19 k = value_to_index[prev]
21 if k < i:
22 dp[i][j] = dp[k][i] + 1
23 longest = max(longest, dp[i][j])

Objective

To update the DP table by checking for a valid previous element that extends the Fibonacci-like subsequence ending at (i, j).

Key Insight

For each pair (i, j), the algorithm calculates prev = arr[j] - arr[i]. If prev exists in the array at index k < i, then dp[i][j] can be extended from dp[k][i] by one. This leverages the Fibonacci property and ensures subsequences are strictly increasing in indices.

Interview Quick-Check

Core Logic

The DP update dp[i][j] = dp[k][i] + 1 extends the subsequence by one element if the previous Fibonacci number exists at index k < i.

State & Boundaries

Ensuring k < i maintains the correct order of elements in the subsequence, preventing invalid sequences.

Common Pitfalls & Bugs

Failing to check k < i or not verifying prev in the map leads to incorrect subsequences or index errors.

4

Return the Length of the Longest Valid Subsequence

25return longest if longest >= 3 else 0

Objective

To return the maximum length found if it is at least 3, otherwise return 0 indicating no valid Fibonacci-like subsequence.

Key Insight

A valid Fibonacci-like subsequence must have length at least 3. The variable longest tracks the maximum length found during DP updates. Returning 0 if no such subsequence exists aligns with the problem requirements.

Interview Quick-Check

Core Logic

Returning longest if >= 3 ensures only valid Fibonacci-like subsequences are considered.

State & Boundaries

The check longest >= 3 filters out trivial pairs that do not form a Fibonacci-like sequence.

Line Analysis

This solution has 4 Critical lines interviewers watch for.

Line 22 Critical
dp[i][j] = dp[k][i] + 1

Extend the subsequence ending at (k, i) to include arr[j].

This dynamic programming transition extends an existing Fibonacci-like subsequence. If arr[k], arr[i] already form the end of a valid sequence, adding arr[j] increases its length by one while preserving the Fibonacci relation.

Line 9 Critical
dp = [[2] * n for _ in range(n)]

Initialize the DP table where dp[i][j] stores the length of a subsequence ending with arr[i] and arr[j].

Each DP state represents a subsequence ending with arr[i] and arr[j]. Any pair of elements forms a subsequence of length two, so initializing every state to 2 establishes the base length before any Fibonacci extension occurs.

Line 16 Critical
prev = arr[j] - arr[i]

Compute the value that would precede arr[i] in a Fibonacci-like relation.

For arr[i] and arr[j] to be consecutive elements of a Fibonacci-like sequence, a previous value must satisfy prev + arr[i] = arr[j]. Rearranging yields prev = arr[j] - arr[i], identifying the value required to extend the sequence.

Line 21 Critical
if k < i:

Verify that the previous element occurs before arr[i].

The constraint k < i ensures that the sequence respects the array's ordering. Without this condition, the algorithm might construct invalid subsequences that reuse elements out of order.

Line 5 Core
value_to_index = {}

Create a dictionary that maps each array value to its index.

Efficient extension of Fibonacci-like subsequences requires quickly locating the index of a value. A hash map enables constant-time lookup of the previous element needed to form the Fibonacci relation.

Line 18 Core
if prev in value_to_index:

Check whether the required previous value appears in the array.

Only if this previous value exists in the array can the subsequence be extended. If it does not exist, the pair (i, j) remains a base subsequence of length two.

Line 25 Core
return longest if longest >= 3 else 0

Return the maximum length if it forms a valid sequence, otherwise return zero.

A valid Fibonacci-like subsequence must contain at least three elements. Returning zero indicates that no such sequence was found.

Line 13 Core
for j in range(n):

Iterate through the array treating j as the last element of the subsequence pair.

Fixing j as the second element of the pair allows the algorithm to evaluate all possible preceding elements i, ensuring every potential subsequence ending at j is considered.

Line 14 Core
for i in range(j):

Iterate through indices i that occur before j.

Selecting i < j ensures subsequences respect the original ordering of the array while exploring all valid pairs that could end a Fibonacci-like sequence.

Line 19 Core
k = value_to_index[prev]

Look up the index of the previous value.

Retrieving the index k allows the algorithm to reference the previously computed DP state representing the subsequence ending with arr[k] and arr[i].

Line 7 Standard
value_to_index[arr[i]] = i

Record the current value and its index in the lookup dictionary.

Mapping values to indices allows the algorithm to determine whether the required previous number in a Fibonacci relation exists earlier in the array.

Line 23 Standard
longest = max(longest, dp[i][j])

Update the recorded maximum subsequence length.

Updating the global maximum during iteration ensures the algorithm retains the length of the longest subsequence discovered so far.

Line 6 Standard
for i in range(n):

Iterate through the array indices to populate the value-to-index map.

This loop ensures every value in the array is indexed before the dynamic programming stage begins, enabling fast lookups during DP transitions.

Line 11 Standard
longest = 0

Initialize a variable to track the longest subsequence length discovered.

Maintaining the maximum length during computation avoids an additional pass through the DP table after processing all pairs.

Line 3 Basic
n = len(arr)

Store the number of elements in the array.

Caching the array length avoids repeated calls to len(arr) and provides a clear reference for iteration bounds throughout the algorithm.

Test Your Understanding

Why must the index k of the previous element be less than i when extending the subsequence?

Because the subsequence must be strictly increasing in indices (k < i < j), ensuring the order of elements respects the original array and the Fibonacci-like property.

Related Problems

Dynamic Programming pattern

Don't just read it. Drill it.

Reconstruct Length of Longest Fibonacci Subsequence from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Length of Longest Fibonacci Subsequence drill

or drill a free problem