873. Length of Longest Fibonacci Subsequence
Problem Description
The problem provides us with a sequence of positive integers that is strictly increasing - meaning each number is greater than the previous one. We are asked to find the length of the longest subsequence that is Fibonacci-like within this sequence. A subsequence is considered Fibonacci-like if it satisfies two conditions: It must have at least three numbers, and each number in the subsequence (after the second) must be the sum of the two preceding numbers. To clarify with an example, if our sequence is [1, 2, 3, 4, 5, 6, 7, 8]
, a Fibonacci-like subsequence could be [1, 2, 3, 5, 8]
. If no such subsequence exists, the result should be 0
.
Intuition
The solution uses the concept of dynamic programming to build up a table of solutions to subproblems, which in this case, are the lengths of the longest Fibonacci-like subsequences ending at different positions in the original sequence.
First, we create a mapping of each number in the sequence to its index to speed up the search for a number's existence within the sequence.
Since we need at least three numbers to form a Fibonacci-like sequence, the default length for any pair of starting numbers is 2 (which is not a valid Fibonacci-like subsequence by itself, but is the basis for building longer ones).
The key intuition is that if arr[k] + arr[j] == arr[i]
and k < j < i
, then the value at dp[j][i]
(the length of the subsequence ending with arr[j]
and arr[i]
) can be updated to dp[k][j] + 1
(which extends the subsequence that ends with arr[k]
and arr[j]
by adding arr[i]
).
We iterate through each pair of numbers, updating the dynamic programming table dp
whenever we find two numbers that add up to a third one in the sequence. The answer is the maximum value in the table dp
that represents the length of the longest Fibonacci-like subsequence we have found.
At each step, we are effectively looking backwards from a potential ending number i
to see if we can form a Fibonacci-like sequence by finding two previous numbers j
and k
that add up to arr[i]
. If we find such a pair, we know we can extend the subsequence that ended at arr[j]
to now end at arr[i]
and hence update our dp
table accordingly.
Learn more about Dynamic Programming patterns.
Solution Approach
The solution approach takes advantage of a dynamic programming paradigm and hash mapping. We use these tools to systematically build a solution that finds the longest Fibonacci-like subsequence within an array of strictly increasing positive integers. The following steps outline how the code implements this solution:
-
Hash Map Construction: We first construct a hash map (
mp
) to store each array value (v
) and its respective index (i
). This greatly optimizes our solution as it allows us to check whether a number exists in the array in constant time.mp = {v: i for i, v in enumerate(arr)}
-
Dynamic Programming Table Initialization: We create a 2D table (
dp
) wheredp[j][i]
will hold the maximum length of a Fibonacci-like subsequence which ends with the elementsarr[j]
andarr[i]
. Initially, we set all pairs (j
,i
) indp
to2
, which is the minimum length of any subsequence consisting of two elements.dp = [[0] * n for _ in range(n)] for i in range(n): for j in range(i): dp[j][i] = 2
-
Updating the DP Table: We then use two nested loops to iterate through the array to find and extend existing Fibonacci-like subsequences. For each pair of indices
(j, i)
, we calculate the potential preceding numberd
by subtractingarr[j]
fromarr[i]
.for i in range(n): for j in range(i): d = arr[i] - arr[j]
We then check if this number
d
exists in the array (and thus in the hash map). If it does and the index ofd
(k
) is less thanj
, we are in a position to extend the subsequence that previously ended witharr[k]
andarr[j]
. The length of the new subsequence will bedp[k][j] + 1
.if d in mp and (k := mp[d]) < j: dp[j][i] = max(dp[j][i], dp[k][j] + 1)
-
Finding the Answer: While updating the
dp
table, we keep track of the maximum length found so far (ans
). Whenever we update an entry in thedp
table, we compare it with the current maximum length and updateans
if necessary.ans = max(ans, dp[j][i])
-
Returning the Result: After iterating over all pairs and updating the
dp
table,ans
contains the length of the longest Fibonacci-like subsequence. If no such subsequence exists longer than two elements,ans
will be0
. Therefore, we returnans
as the result.return ans
It's interesting to note that the dynamic programming table is not fully utilized. Most of its default-initialization values remain unchanged, especially near the diagonal where i
and j
are close. The only entries that receive meaningful updates reflect valid subsequences of length greater than two. The efficient use of the hash map allows for quick validation of potential Fibonacci-like sequences, which is central to the dynamic programming update step.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through an example to illustrate the solution approach using a small sequence of positive integers: [1, 3, 7, 11, 12, 14, 18]
.
First, we initialize the hash map mp
to map each number to its index in the array.
mp = {1: 0, 3: 1, 7: 2, 11: 3, 12: 4, 14: 5, 18: 6}
Next, we initialize the dynamic programming table dp
. Since the array length n
is 7, dp
is a 7x7 2D array where all pairs (j
, i
) are initially set to a subsequence length of 2.
Now, we start iterating over the pairs of indices (j, i)
. We're looking for whether the difference d = arr[i] - arr[j]
exists in the array and corresponds to an index k
such that k < j
.
When i = 3
and j = 2
, arr[i] = 11
, arr[j] = 7
, so d = 11 - 7 = 4
. There is no 4
in the array, so we move on.
Next significant update happens when i = 4
and j = 1
. Here arr[i] = 12
and arr[j] = 3
, so d = 12 - 3 = 9
. The number 9
does not exist in the array either. Again, we proceed forward.
When i = 6
and j = 5
, arr[i] = 18
and arr[j] = 14
, so d = 18 - 14 = 4
. Since 4
is not present in our array, no update in dp
occurs.
However, the first update occurs when i = 5
and j = 1
, then arr[i] = 14
, arr[j] = 3
, and hence d = 14 - 3 = 11
. We see that 11
is in the array at index 3
. Therefore, k = 3
and k < j
. We update dp[j][i] = dp[k][j] + 1 = dp[3][1] + 1 = 2 + 1 = 3
.
Now dp
looks like this (a snippet of relevant parts):
dp[1][5] = 3 # This tells us there's a subsequence ending at arr[1] (3) and arr[5] (14) with length 3.
Given this dp
update, our current maximum ans
becomes 3
.
By the end of the iteration, since no other updates result in a longer subsequence, the longest Fibonacci-like subsequence we have is [3, 11, 14]
. Hence, the answer ans
is 3
. If there were no updates that result in a value greater than 2
, ans
would remain 0
, indicating no valid Fibonacci-like subsequences exist beyond two elements.
Finally, we return ans
, which is 3
in this case.
Solution Implementation
1class Solution:
2 def lenLongestFibSubseq(self, arr):
3 # Create a hash map to store value to index mappings for quick access
4 value_to_index = {value: index for index, value in enumerate(arr)}
5 n = len(arr)
6
7 # Initialize a 2D array for dynamic programming, setting initial subsequence sizes to 2
8 dp = [[2 for _ in range(n)] for _ in range(n)]
9
10 # This variable will keep track of the longest fib sequence found
11 longest_fib_sequence = 0
12
13 # Iterate over pairs of numbers in the array to build up sequences
14 for i in range(n):
15 for j in range(i):
16 # Find the previous number in the potential Fibonacci sequence
17 difference = arr[i] - arr[j]
18 # Check if the number exists in our array and precedes the second number (j)
19 if difference in value_to_index and value_to_index[difference] < j:
20 # The k index refers to the position of the previous number
21 prev_index = value_to_index[difference]
22 # Update the DP table by extending the sequence found from prev_index and j
23 dp[j][i] = max(dp[j][i], dp[prev_index][j] + 1)
24 # Update the answer if a longer sequence is found
25 longest_fib_sequence = max(longest_fib_sequence, dp[j][i])
26
27 return longest_fib_sequence
28
1class Solution {
2 public int lenLongestFibSubseq(int[] arr) {
3 // The length of the input array
4 int length = arr.length;
5 // Create a HashMap to store the index of each value in the array for quick lookup
6 Map<Integer, Integer> indexMap = new HashMap<>();
7 for (int i = 0; i < length; ++i) {
8 indexMap.put(arr[i], i);
9 }
10 // Initialize the dynamic programming table where dp[i][j] will store
11 // the length of the Fibonacci-like sequence ending with arr[i] and arr[j]
12 int[][] dp = new int[length][length];
13 // Initialize the table with the minimum possible sequence length, which is 2
14 for (int i = 0; i < length; ++i) {
15 for (int j = 0; j < i; ++j) {
16 dp[j][i] = 2;
17 }
18 }
19 // This will hold the length of the longest Fibonacci-like subsequence
20 int longestSequenceLength = 0;
21 // Iterate over all pairs of indices to build the longest sequence
22 for (int i = 0; i < length; ++i) {
23 for (int j = 0; j < i; ++j) {
24 // The potential previous value in the Fibonacci-like sequence
25 int prevValue = arr[i] - arr[j];
26 // Check if the needed previous value is in the array
27 if (indexMap.containsKey(prevValue)) {
28 // The index k of the needed previous value
29 int k = indexMap.get(prevValue);
30 // Ensure the previous value's index comes before j
31 if (k < j) {
32 // Update the sequence length considering the triplet (arr[k], arr[j], arr[i])
33 dp[j][i] = Math.max(dp[j][i], dp[k][j] + 1);
34 // Update the global maximum length if necessary
35 longestSequenceLength = Math.max(longestSequenceLength, dp[j][i]);
36 }
37 }
38 }
39 }
40 // If the longestSequenceLength remains 2, no Fibonacci-like sequence is found,
41 // so we return 0 as specified in the problem statement
42 return longestSequenceLength > 2 ? longestSequenceLength : 0;
43 }
44}
45
1#include <vector>
2#include <unordered_map>
3using namespace std;
4
5class Solution {
6public:
7 // Function that returns the length of the longest Fibonacci-like subsequence.
8 int lenLongestFibSubseq(vector<int>& arr) {
9 // Hash map that associates each value in arr with its index.
10 unordered_map<int, int> indexMap;
11 int n = arr.size();
12 // Fill the index map with value-index pairs.
13 for (int i = 0; i < n; ++i) {
14 indexMap[arr[i]] = i;
15 }
16
17 // Initialize a 2D vector 'dp' with 2s, where dp[j][i] will store
18 // the length of the longest Fibonacci-like subsequence ending with arr[j] and arr[i].
19 vector<vector<int>> dp(n, vector<int>(n, 2));
20
21 // Variable to store the answer: the length of the longest subsequence found.
22 int maxLength = 0;
23
24 // Double loop to find Fibonacci-like subsequences ending with pairs (arr[j], arr[i]).
25 for (int i = 0; i < n; ++i) {
26 for (int j = 0; j < i; ++j) {
27 // Calculate the potential previous Fibonacci number in the sequence.
28 int prevNum = arr[i] - arr[j];
29 // Check if this number exists in our map (hence, in the array).
30 if (indexMap.count(prevNum)) {
31 // Get the index 'k' of the found previous number.
32 int k = indexMap[prevNum];
33 // Ensure this index 'k' comes before index 'j' to maintain the sequence ordering.
34 if (k < j) {
35 // Update the length of the subsequence ending with (arr[j], arr[i]).
36 dp[j][i] = max(dp[j][i], dp[k][j] + 1);
37 // Update the answer if we found a longer subsequence.
38 maxLength = max(maxLength, dp[j][i]);
39 }
40 }
41 }
42 }
43
44 // If no sequence is found, maxLength would be 2, as initialized. But according to the problem,
45 // the sequence should have at least 3 numbers to count, so return 0 in that case.
46 return maxLength > 2 ? maxLength : 0;
47 }
48};
49
1/**
2 * Returns the length of the longest Fibonacci-like subsequence of `arr`.
3 * If there is no such subsequence, returns 0.
4 * A sequence is considered a Fibonacci-like sequence if `X[i] + X[i-1] = X[i+1]` for all `i + 1 < X.length`.
5 * @param {number[]} arr - An array of positive integers
6 * @return {number} - The length of the longest Fibonacci-like subsequence in `arr`
7 */
8function lenLongestFibSubseq(arr: number[]): number {
9 const indexMap: Map<number, number> = new Map();
10 const n: number = arr.length;
11 const dp: number[][] = Array.from({ length: n }, () => new Array(n).fill(2));
12 let maxSequenceLength: number = 0;
13
14 // Populate the map with the value to index mappings for quick access
15 for (let i = 0; i < n; ++i) {
16 indexMap.set(arr[i], i);
17 }
18
19 // Construct the dp table where dp[j][i] will represent the length of the longest
20 // Fibonacci-like subsequence ending with arr[j] and arr[i].
21 for (let i = 0; i < n; ++i) {
22 for (let j = 0; j < i; ++j) {
23 const potentialPreviousValue: number = arr[i] - arr[j];
24
25 // We only need to investigate if the potential previous value exists in the array and comes before 'j'
26 if (indexMap.has(potentialPreviousValue)) {
27 const k: number = indexMap.get(potentialPreviousValue)!;
28 if (k < j) {
29 dp[j][i] = Math.max(dp[j][i], dp[k][j] + 1);
30 maxSequenceLength = Math.max(maxSequenceLength, dp[j][i]);
31 }
32 }
33 }
34 }
35
36 // If we have only computed the initial value of 2 for all pairs,
37 // it means no Fibonacci-like sequence has been found.
38 return maxSequenceLength > 2 ? maxSequenceLength : 0;
39}
40
Time and Space Complexity
The given code is designed to find the length of the longest Fibonacci-like subsequence in a given array arr
. We analyze both the time complexity and the space complexity of the code.
Time Complexity:
Initially, the given code creates a hashmap (mp
) from each element to its index with a time complexity of O(n)
, where n
is the length of the array arr
.
The main part of the algorithm consists of nested loops. The outer loop runs n
times, and for each iteration, the inner loop runs at most n
times, yielding n^2
iterations for the nested loops.
Inside the inner loop, the code performs constant-time operations such as dictionary look-up (to check if d
exists in mp
), assignments, and arithmetic operations. The look-up in mp
takes O(1)
on average. Therefore, the inner block within the inner loop also executes in constant time.
Considering these nested loops as the dominant factor, the time complexity is O(n^2)
.
Space Complexity:
For space complexity, the code uses:
- A hashmap
mp
with at mostn
entries, thusO(n)
space. - A 2D array
dp
of sizen * n
, accounting forO(n^2)
space.
Therefore, the space complexity is dominated by the 2D array dp
, making the overall space complexity of the code O(n^2)
.
In conclusion, the code has a time complexity of O(n^2)
and a space complexity of O(n^2)
.
Learn more about how to find time and space complexity quickly using problem constraints.
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
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
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!