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
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]
wheref[i][j]
represents the length of the longest Fibonacci-like subsequence ending witharr[j]
andarr[i]
- Build a hash map
d
that maps each value inarr
to its index for O(1) lookup:d = {x: i for i, x in enumerate(arr)}
- Initialize all
f[i][j]
wherej < 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 toi-1
:- Calculate
t = arr[i] - arr[j]
, which would be the element needed beforearr[j]
to form a Fibonacci sequence - Check if
t
exists in our hash mapd
- If
t
exists and its indexk = d[t]
satisfiesk < 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])
- Update
- Calculate
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 EvaluatorExample 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
In a binary min heap, the minimum element can be found in:
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!