Facebook Pixel

873. Length of Longest Fibonacci Subsequence

Problem Description

A sequence x₁, x₂, ..., xₙ is called Fibonacci-like if it satisfies two conditions:

  • The sequence has at least 3 elements (n >= 3)
  • Each element is the sum of the two preceding elements: xᵢ + xᵢ₊₁ = xᵢ₊₂ for all valid indices

Given a strictly increasing array arr of positive integers, you need to find the length of the longest Fibonacci-like subsequence that can be formed from arr. If no such subsequence exists, return 0.

A subsequence is formed by deleting any number of elements (including none) from the original array while maintaining the relative order of the remaining elements. For example, [3, 5, 8] is a subsequence of [3, 4, 5, 6, 7, 8].

The key challenge is to identify which elements from the strictly increasing array can form a valid Fibonacci-like pattern when selected as a subsequence. Since the array is strictly increasing, you need to carefully choose elements that satisfy the Fibonacci property where each element equals the sum of the two preceding ones.

For example, if arr = [1, 2, 3, 4, 5, 6, 7, 8], one possible Fibonacci-like subsequence is [1, 2, 3, 5, 8] with length 5, where:

  • 1 + 2 = 3
  • 2 + 3 = 5
  • 3 + 5 = 8
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To find the longest Fibonacci-like subsequence, we need to think about what defines such a sequence. Any Fibonacci-like sequence is uniquely determined by its first two elements - once we fix the first two numbers, the rest of the sequence follows automatically by the addition rule.

This observation leads us to consider using dynamic programming where we track sequences by their last two elements. Why the last two? Because in a Fibonacci sequence, knowing the last two elements tells us what the next element must be (their sum), and we can build sequences forward.

Let's define f[i][j] as the length of the longest Fibonacci-like subsequence ending with arr[j] as the second-to-last element and arr[i] as the last element. Initially, any two elements can form a sequence of length 2, so we start with f[i][j] = 2 for all valid pairs.

Now, for extending a sequence: if we have arr[j] and arr[i] as the last two elements, the element before arr[j] in the sequence must be arr[i] - arr[j]. Let's call this value t. If t exists in our array at some index k where k < j (to maintain the subsequence property), then we've found a longer Fibonacci-like sequence. The sequence ending at positions k, j can be extended to positions j, i, giving us length f[j][k] + 1.

Using a hash table to store the indices of array elements allows us to quickly check if t = arr[i] - arr[j] exists in the array and find its position. This way, we can efficiently build up longer sequences by checking if we can extend existing ones.

The key insight is that we're not trying to build sequences from scratch each time - we're extending existing sequences by one element whenever possible, and the dynamic programming table helps us keep track of the best lengths achieved so far.

Learn more about Dynamic Programming patterns.

Solution Approach

The solution uses dynamic programming with a 2D array to track the longest Fibonacci-like subsequences. Here's the step-by-step implementation:

1. Initialize the DP table and hash map:

  • Create a 2D array f[i][j] where f[i][j] represents the length of the longest Fibonacci-like subsequence ending with arr[j] and arr[i]
  • Build a hash map d that maps each value in arr to its index for O(1) lookup: d = {x: i for i, x in enumerate(arr)}
  • Initialize all f[i][j] where j < i to 2, since any two elements form a valid sequence of length 2

2. Fill the DP table: For each position i from 2 to n-1:

  • For each position j from 1 to i-1:
    • Calculate t = arr[i] - arr[j], which would be the element needed before arr[j] to form a Fibonacci sequence
    • Check if t exists in our hash map d
    • If t exists and its index k = d[t] satisfies k < j (to maintain subsequence order):
      • Update f[i][j] = max(f[i][j], f[j][k] + 1)
      • This extends the sequence ending at (k, j) to now end at (j, i)
      • Update the global answer: ans = max(ans, f[i][j])

3. Return the result:

  • If no Fibonacci-like subsequence of length ≥ 3 is found, ans remains 0
  • Otherwise, return the maximum length found

Time Complexity: O(n²) where n is the length of the array, as we iterate through all pairs (i, j)

Space Complexity: O(n²) for the DP table, plus O(n) for the hash map

The key optimization here is using the hash map for O(1) lookup of whether a required previous element exists, rather than searching through the array each time. The dynamic programming approach ensures we build upon previously computed subsequences efficiently, avoiding redundant calculations.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with arr = [1, 3, 4, 7, 8, 11].

Step 1: Initialize

  • Create hash map: d = {1:0, 3:1, 4:2, 7:3, 8:4, 11:5}
  • Create DP table f[i][j] (initially all 2 for valid pairs where j < i):
    j=0  j=1  j=2  j=3  j=4
i=1  2    -    -    -    -
i=2  2    2    -    -    -
i=3  2    2    2    -    -
i=4  2    2    2    2    -
i=5  2    2    2    2    2

Step 2: Process each position

When i=2 (arr[2]=4):

  • j=1 (arr[1]=3): t = 4-3 = 1, exists at k=0, k<j ✓
    • f[2][1] = f[1][0] + 1 = 2 + 1 = 3
    • Found sequence: [1,3,4] with length 3

When i=3 (arr[3]=7):

  • j=1 (arr[1]=3): t = 7-3 = 4, exists at k=2, but k≥j ✗
  • j=2 (arr[2]=4): t = 7-4 = 3, exists at k=1, k<j ✓
    • f[3][2] = f[2][1] + 1 = 3 + 1 = 4
    • Extended sequence: [1,3,4,7] with length 4

When i=4 (arr[4]=8):

  • j=1 (arr[1]=3): t = 8-3 = 5, doesn't exist ✗
  • j=2 (arr[2]=4): t = 8-4 = 4, exists at k=2, but k≥j ✗
  • j=3 (arr[3]=7): t = 8-7 = 1, exists at k=0, k<j ✓
    • f[4][3] = f[3][0] + 1 = 2 + 1 = 3
    • Found sequence: [1,7,8] with length 3

When i=5 (arr[5]=11):

  • j=1 (arr[1]=3): t = 11-3 = 8, exists at k=4, but k≥j ✗
  • j=2 (arr[2]=4): t = 11-4 = 7, exists at k=3, but k≥j ✗
  • j=3 (arr[3]=7): t = 11-7 = 4, exists at k=2, k<j ✓
    • f[5][3] = f[3][2] + 1 = 4 + 1 = 5
    • Extended sequence: [1,3,4,7,11] with length 5
  • j=4 (arr[4]=8): t = 11-8 = 3, exists at k=1, k<j ✓
    • f[5][4] = f[4][1] + 1 = 2 + 1 = 3
    • Found sequence: [3,8,11] with length 3

Step 3: Return result The maximum length found is 5, corresponding to the subsequence [1,3,4,7,11]:

  • 1 + 3 = 4 ✓
  • 3 + 4 = 7 ✓
  • 4 + 7 = 11 ✓

Solution Implementation

1class Solution:
2    def lenLongestFibSubseq(self, arr: List[int]) -> int:
3        """
4        Find the length of the longest Fibonacci-like subsequence in a sorted array.
5        A Fibonacci-like sequence is one where each element is the sum of the two preceding ones.
6      
7        Args:
8            arr: A strictly increasing array of positive integers
9          
10        Returns:
11            The length of the longest Fibonacci-like subsequence, or 0 if none exists (length >= 3)
12        """
13        n = len(arr)
14      
15        # dp[i][j] represents the length of the longest Fibonacci-like subsequence 
16        # ending with arr[j] and arr[i] (where j < i)
17        dp = [[0] * n for _ in range(n)]
18      
19        # Create a hash map for O(1) lookup of element indices
20        value_to_index = {value: index for index, value in enumerate(arr)}
21      
22        # Initialize all pairs to length 2 (any two elements can start a sequence)
23        for i in range(n):
24            for j in range(i):
25                dp[i][j] = 2
26      
27        max_length = 0
28      
29        # Iterate through all possible ending positions
30        for i in range(2, n):
31            # Iterate through all possible second-to-last positions
32            for j in range(1, i):
33                # Calculate what the third-to-last element should be
34                # In a Fibonacci sequence: arr[k] + arr[j] = arr[i]
35                required_value = arr[i] - arr[j]
36              
37                # Check if the required value exists and appears before position j
38                if required_value in value_to_index and (k := value_to_index[required_value]) < j:
39                    # Extend the sequence ending at (j, k) by adding arr[i]
40                    dp[i][j] = max(dp[i][j], dp[j][k] + 1)
41                    max_length = max(max_length, dp[i][j])
42      
43        return max_length
44
1class Solution {
2    public int lenLongestFibSubseq(int[] arr) {
3        int n = arr.length;
4      
5        // dp[i][j] represents the length of fibonacci-like subsequence ending at arr[i] and arr[j]
6        // where i > j
7        int[][] dp = new int[n][n];
8      
9        // Map to store value -> index for O(1) lookup
10        Map<Integer, Integer> valueToIndex = new HashMap<>();
11      
12        // Initialize the map and base cases
13        for (int i = 0; i < n; ++i) {
14            valueToIndex.put(arr[i], i);
15            // Any two elements can form a subsequence of length 2
16            for (int j = 0; j < i; ++j) {
17                dp[i][j] = 2;
18            }
19        }
20      
21        int maxLength = 0;
22      
23        // Iterate through all possible ending pairs (arr[j], arr[i])
24        for (int i = 2; i < n; ++i) {
25            for (int j = 1; j < i; ++j) {
26                // For fibonacci sequence: arr[k] + arr[j] = arr[i]
27                // So we need to find arr[k] = arr[i] - arr[j]
28                int target = arr[i] - arr[j];
29                Integer k = valueToIndex.get(target);
30              
31                // Check if target exists and maintains order k < j < i
32                if (k != null && k < j) {
33                    // Extend the fibonacci subsequence ending at (arr[k], arr[j])
34                    // by adding arr[i]
35                    dp[i][j] = Math.max(dp[i][j], dp[j][k] + 1);
36                    maxLength = Math.max(maxLength, dp[i][j]);
37                }
38            }
39        }
40      
41        return maxLength;
42    }
43}
44
1class Solution {
2public:
3    int lenLongestFibSubseq(vector<int>& arr) {
4        int n = arr.size();
5      
6        // dp[i][j] represents the length of fibonacci-like subsequence ending with arr[j] and arr[i]
7        // where j < i
8        int dp[n][n];
9        memset(dp, 0, sizeof(dp));
10      
11        // Hash map to store value -> index mapping for O(1) lookup
12        unordered_map<int, int> valueToIndex;
13      
14        // Initialize the hash map and base cases
15        for (int i = 0; i < n; ++i) {
16            valueToIndex[arr[i]] = i;
17            // Any two elements form a sequence of length 2
18            for (int j = 0; j < i; ++j) {
19                dp[i][j] = 2;
20            }
21        }
22      
23        int maxLength = 0;
24      
25        // Iterate through all possible ending pairs (arr[j], arr[i])
26        for (int i = 2; i < n; ++i) {
27            for (int j = 1; j < i; ++j) {
28                // Calculate the previous element needed for fibonacci sequence
29                // If sequence is: ..., prevValue, arr[j], arr[i]
30                // Then: prevValue + arr[j] = arr[i]
31                // So: prevValue = arr[i] - arr[j]
32                int prevValue = arr[i] - arr[j];
33              
34                // Check if the previous value exists in the array
35                auto it = valueToIndex.find(prevValue);
36              
37                // Valid fibonacci sequence requires prevValue to exist and appear before arr[j]
38                if (it != valueToIndex.end() && it->second < j) {
39                    int k = it->second;
40                    // Extend the sequence ending with (arr[k], arr[j]) by adding arr[i]
41                    dp[i][j] = max(dp[i][j], dp[j][k] + 1);
42                    maxLength = max(maxLength, dp[i][j]);
43                }
44            }
45        }
46      
47        return maxLength;
48    }
49};
50
1/**
2 * Finds the length of the longest Fibonacci-like subsequence in the array
3 * A Fibonacci-like sequence is one where each element is the sum of the two preceding ones
4 * @param arr - A strictly increasing array of positive integers
5 * @returns The length of the longest Fibonacci-like subsequence, or 0 if none exists with length >= 3
6 */
7function lenLongestFibSubseq(arr: number[]): number {
8    const arrayLength: number = arr.length;
9  
10    // dp[i][j] represents the length of Fibonacci-like subsequence ending with arr[j] and arr[i]
11    // where j < i
12    const dp: number[][] = Array.from(
13        { length: arrayLength }, 
14        () => Array(arrayLength).fill(0)
15    );
16  
17    // Map to store value -> index for O(1) lookup
18    const valueToIndex: Map<number, number> = new Map();
19  
20    // Initialize the map and base cases for dp array
21    for (let i = 0; i < arrayLength; ++i) {
22        valueToIndex.set(arr[i], i);
23      
24        // Any two elements can form a sequence of length 2
25        for (let j = 0; j < i; ++j) {
26            dp[i][j] = 2;
27        }
28    }
29  
30    let maxLength: number = 0;
31  
32    // Iterate through all possible endings of Fibonacci-like subsequences
33    for (let i = 2; i < arrayLength; ++i) {
34        for (let j = 1; j < i; ++j) {
35            // For a Fibonacci sequence: arr[k] + arr[j] = arr[i]
36            // So we need to find arr[k] = arr[i] - arr[j]
37            const previousValue: number = arr[i] - arr[j];
38            const previousIndex: number | undefined = valueToIndex.get(previousValue);
39          
40            // Check if the previous value exists and maintains the order k < j < i
41            if (previousIndex !== undefined && previousIndex < j) {
42                // Extend the sequence ending at (j, previousIndex) by adding arr[i]
43                dp[i][j] = Math.max(dp[i][j], dp[j][previousIndex] + 1);
44                maxLength = Math.max(maxLength, dp[i][j]);
45            }
46        }
47    }
48  
49    return maxLength;
50}
51

Time and Space Complexity

The time complexity is O(n²), where n is the length of the array arr. This is because the code uses two nested loops - the outer loop iterates from index 2 to n-1, and the inner loop iterates from index 1 to i-1. Although there's an additional dictionary lookup operation t in d and accessing d[t] inside the nested loops, these are O(1) operations for dictionary lookups. Therefore, the overall time complexity remains O(n²).

The space complexity is O(n²), where n is the length of the array arr. This is due to the 2D array f which has dimensions n × n, requiring O(n²) space. Additionally, there's a dictionary d that stores the mapping of array values to their indices, which takes O(n) space. Since O(n²) + O(n) = O(n²), the overall space complexity is O(n²).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect DP State Definition

A common mistake is defining dp[i][j] to represent a sequence ending at indices i and j where i < j. This creates confusion because you're looking backward in the sequence, but the indices suggest forward progression.

Problem Example:

# Incorrect: dp[i][j] where i < j
dp = [[0] * n for _ in range(n)]
for i in range(n-1):
    for j in range(i+1, n):
        dp[i][j] = 2  # Wrong initialization pattern

Solution: Always maintain dp[i][j] where j < i, representing a sequence ending with arr[j] followed by arr[i]. This makes the logic clearer:

# Correct: dp[i][j] where j < i
for i in range(n):
    for j in range(i):
        dp[i][j] = 2

2. Forgetting to Check Index Ordering

When looking for the third element in the Fibonacci sequence, developers often forget to verify that the required element appears before the current pair in the original array.

Problem Example:

# Missing index check
required_value = arr[i] - arr[j]
if required_value in value_to_index:  # Only checking existence
    k = value_to_index[required_value]
    dp[i][j] = dp[j][k] + 1  # May access invalid indices

Solution: Always verify that k < j < i to maintain subsequence order:

if required_value in value_to_index:
    k = value_to_index[required_value]
    if k < j:  # Essential check
        dp[i][j] = max(dp[i][j], dp[j][k] + 1)

3. Returning Wrong Value for No Valid Sequence

The problem requires returning 0 when no Fibonacci-like subsequence of length ≥ 3 exists, but some implementations return 2 (the default pair length).

Problem Example:

max_length = 2  # Wrong initial value
for i in range(2, n):
    for j in range(1, i):
        # ... update logic
return max_length  # Returns 2 even when no valid sequence exists

Solution: Initialize max_length to 0 and only update it when finding sequences of length ≥ 3:

max_length = 0  # Correct initialization
for i in range(2, n):
    for j in range(1, i):
        # ... calculate required_value
        if required_value in value_to_index and k < j:
            dp[i][j] = dp[j][k] + 1
            max_length = max(max_length, dp[i][j])  # Only update for valid sequences

4. Edge Case: Array Too Small

Not handling arrays with fewer than 3 elements gracefully.

Problem Example:

def lenLongestFibSubseq(self, arr: List[int]) -> int:
    n = len(arr)
    # Directly proceeding without checking n
    dp = [[0] * n for _ in range(n)]
    # May cause issues with range(2, n) when n < 3

Solution: Add an early return for small arrays:

def lenLongestFibSubseq(self, arr: List[int]) -> int:
    n = len(arr)
    if n < 3:
        return 0  # No Fibonacci-like sequence possible
    # ... rest of the implementation
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the minimum element can be found in:


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More