842. Split Array into Fibonacci Sequence
Problem Description
You are given a string of digits num
(like "123456579"
), and you need to split it into a Fibonacci-like sequence.
A Fibonacci-like sequence must satisfy these conditions:
- It contains at least 3 numbers
- Each number must fit in a 32-bit signed integer (between 0 and 2^31 - 1)
- For every position
i
in the sequence (except the last two), the sum of two consecutive numbers equals the next number:f[i] + f[i+1] = f[i+2]
When splitting the string:
- Numbers cannot have leading zeros (except for the number 0 itself)
- For example,
"01"
cannot be split as01
, but"0"
can represent 0
The task is to find any valid Fibonacci-like sequence from the given string. If no such sequence exists, return an empty list.
Example:
- Input:
"123456579"
- One possible output:
[123, 456, 579]
- This works because 123 + 456 = 579
The solution uses a depth-first search (DFS) approach with backtracking. It tries different ways to split the string by:
- Starting from the beginning, trying different lengths for the first number
- For each potential number, checking if it violates the constraints (too large, has leading zeros)
- If we have less than 2 numbers, adding it to our sequence
- If we have 2 or more numbers, only adding it if it equals the sum of the previous two
- Recursively continuing this process
- Backtracking when a path doesn't lead to a valid solution
- Returning the sequence when we successfully use all digits in a valid Fibonacci-like pattern
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- No: The problem involves splitting a string of digits into numbers, not working with nodes and edges of a graph structure.
Need to solve for kth smallest/largest?
- No: We're not looking for the kth smallest or largest element. We need to find any valid Fibonacci-like sequence split.
Involves Linked Lists?
- No: The problem works with a string of digits, not linked list data structures.
Does the problem have small constraints?
- Yes: While the string can be up to a certain length, we need to explore different ways to split it. The search space involves trying different combinations of how to partition the string into numbers, which requires exploring multiple possibilities systematically.
Brute force / Backtracking?
- Yes: We need to try different ways to split the string and backtrack when a particular split doesn't lead to a valid Fibonacci-like sequence. This involves:
- Trying different lengths for the first number
- Trying different lengths for the second number
- Checking if subsequent numbers follow the Fibonacci property
- Backtracking when we hit invalid states (number too large, leading zeros, doesn't satisfy Fibonacci property)
- Exploring all possible valid splits until we find one that works
Conclusion: The flowchart correctly leads us to use a Backtracking approach. This matches the solution, which uses DFS with backtracking to explore different ways to split the string, building up a potential Fibonacci sequence and backtracking when constraints are violated.
Intuition
The key insight is that once we fix the first two numbers in a Fibonacci-like sequence, the entire sequence is determined. This is because each subsequent number must be the sum of the previous two: f[i] = f[i-1] + f[i-2]
.
Think of it like this: we're standing at the beginning of the string and need to make decisions about where to "cut" to form numbers. The challenge is we don't know:
- How many digits should the first number have?
- How many digits should the second number have?
- Will the rest of the string follow the Fibonacci property once we make these choices?
This naturally leads to a "try and verify" approach. We can:
- Try taking 1 digit for the first number, then try different lengths for the second number
- Try taking 2 digits for the first number, then try different lengths for the second number
- Continue this pattern...
For each combination of first and second numbers, we can deterministically check if the rest of the string can form a valid Fibonacci sequence. If at any point we can't form the next expected number (either it's too large, has leading zeros, or doesn't match what we need), we know this path won't work and we backtrack to try a different split.
The beauty of backtracking here is that we can prune invalid paths early. For example:
- If a number exceeds
2^31 - 1
, we stop exploring that branch - If we encounter an invalid leading zero, we stop
- If we've already chosen two numbers and the next number we're forming is already larger than their sum, we can stop early
This "build as we go" strategy with the ability to abandon bad paths is exactly what backtracking provides - we build up a candidate solution piece by piece, and when we realize we're on the wrong path, we undo our last choice and try something else.
Learn more about Backtracking patterns.
Solution Approach
The solution uses a depth-first search (DFS) with backtracking to explore all possible ways to split the string into a valid Fibonacci-like sequence.
Core Algorithm Structure:
The main function initializes an empty answer list ans
and calls a recursive DFS function starting from index 0. The DFS function works as follows:
-
Base Case: When we reach the end of the string (
i == n
), we check if we have at least 3 numbers in our sequence. If yes, we've found a valid split. -
Number Formation: Starting from the current position
i
, we try to form numbers of different lengths:- We build the number digit by digit:
x = x * 10 + int(num[j])
- We try all possible ending positions
j
fromi
ton-1
- We build the number digit by digit:
-
Pruning Conditions: We stop exploring a branch early if:
- Leading zeros: If we're trying to form a multi-digit number starting with '0' (
j > i and num[i] == '0'
) - Integer overflow: If the number exceeds
2^31 - 1
- Fibonacci violation: If we already have 2+ numbers and the current number is larger than the sum of the last two (
x > ans[-2] + ans[-1]
)
- Leading zeros: If we're trying to form a multi-digit number starting with '0' (
-
Adding Numbers to Sequence:
- If we have less than 2 numbers in
ans
, we can add any valid number - If we have 2 or more numbers, we can only add
x
if it equalsans[-2] + ans[-1]
- If we have less than 2 numbers in
-
- After adding a number to
ans
, we recursively call DFS for the next position (j + 1
) - If the recursive call returns
True
, we've found a valid sequence and propagate the success - If it returns
False
, we remove the last added number (ans.pop()
) and try the next possibility
- After adding a number to
Why This Works:
The algorithm systematically explores all valid ways to split the string. For the example "123456579"
:
- It might first try
[1, 2, 3, ...]
but find that doesn't work - Then try
[1, 23, 24, ...]
which also fails - Eventually try
[123, 456, 579]
which satisfies all conditions
The backtracking ensures we don't miss any valid solution while the pruning conditions help us avoid exploring obviously invalid paths, making the algorithm efficient despite its exponential worst-case nature.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with the string "11235"
to find the Fibonacci-like sequence [1, 1, 2, 3, 5]
.
Initial State: num = "11235"
, ans = []
, starting at index i = 0
Step 1: Choose first number
- At index 0, we try different lengths for the first number
- Try taking 1 digit:
x = 1
- Since
ans
is empty (< 2 numbers), we can add any valid number - Add
1
toans
:ans = [1]
- Recursively call DFS with
i = 1
Step 2: Choose second number
- At index 1, try different lengths for the second number
- Try taking 1 digit:
x = 1
- Since
ans
has only 1 number (< 2 numbers), we can add any valid number - Add
1
toans
:ans = [1, 1]
- Recursively call DFS with
i = 2
Step 3: Verify third number
- At index 2, we now have 2 numbers, so we must check the Fibonacci property
- We need the next number to be
ans[-2] + ans[-1] = 1 + 1 = 2
- Try taking 1 digit:
x = 2
- Check: Is
x == 2
? Yes! ✓ - Add
2
toans
:ans = [1, 1, 2]
- Recursively call DFS with
i = 3
Step 4: Verify fourth number
- At index 3, we need
ans[-2] + ans[-1] = 1 + 2 = 3
- Try taking 1 digit:
x = 3
- Check: Is
x == 3
? Yes! ✓ - Add
3
toans
:ans = [1, 1, 2, 3]
- Recursively call DFS with
i = 4
Step 5: Verify fifth number
- At index 4, we need
ans[-2] + ans[-1] = 2 + 3 = 5
- Try taking 1 digit:
x = 5
- Check: Is
x == 5
? Yes! ✓ - Add
5
toans
:ans = [1, 1, 2, 3, 5]
- Recursively call DFS with
i = 5
Step 6: Base case reached
i = 5
equals the length of the string (5)- Check: Do we have at least 3 numbers? Yes, we have 5 numbers ✓
- Return
True
to indicate success
The algorithm successfully finds [1, 1, 2, 3, 5]
as a valid Fibonacci-like sequence.
Key Observations:
- Once we fixed the first two numbers as
[1, 1]
, the rest of the sequence was determined - The algorithm would also explore other starting combinations like
[11, 2, ...]
but would fail when trying to find13
in the remaining string - The pruning conditions prevent us from exploring obviously invalid paths, like trying to form numbers with leading zeros or exceeding the integer limit
Solution Implementation
1class Solution:
2 def splitIntoFibonacci(self, num: str) -> List[int]:
3 def backtrack(start_index):
4 # Base case: if we've processed the entire string
5 # Return true if we have at least 3 numbers in our sequence
6 if start_index == string_length:
7 return len(fibonacci_sequence) >= 3
8
9 current_number = 0
10 # Try all possible numbers starting from current index
11 for end_index in range(start_index, string_length):
12 # Skip numbers with leading zeros (except single digit 0)
13 if end_index > start_index and num[start_index] == '0':
14 break
15
16 # Build the current number digit by digit
17 current_number = current_number * 10 + int(num[end_index])
18
19 # Check if number exceeds 32-bit integer limit
20 if current_number > 2**31 - 1:
21 break
22
23 # If we already have 2+ numbers, check if current number is too large
24 # (optimization: no need to continue if it's already larger than sum)
25 if len(fibonacci_sequence) >= 2 and current_number > fibonacci_sequence[-2] + fibonacci_sequence[-1]:
26 break
27
28 # Check if current number can be part of Fibonacci sequence
29 # Either we have less than 2 numbers, or it equals the sum of last two
30 if len(fibonacci_sequence) < 2 or fibonacci_sequence[-2] + fibonacci_sequence[-1] == current_number:
31 # Add current number to sequence and continue recursion
32 fibonacci_sequence.append(current_number)
33
34 # If we found a valid split, return True
35 if backtrack(end_index + 1):
36 return True
37
38 # Backtrack: remove the number if this path didn't work
39 fibonacci_sequence.pop()
40
41 return False
42
43 # Initialize variables
44 string_length = len(num)
45 fibonacci_sequence = []
46
47 # Start the backtracking process
48 backtrack(0)
49
50 return fibonacci_sequence
51
1class Solution {
2 // List to store the Fibonacci-like sequence
3 private List<Integer> sequence = new ArrayList<>();
4 // Input string of digits
5 private String inputString;
6
7 /**
8 * Splits the given string into a Fibonacci-like sequence.
9 * A Fibonacci-like sequence has at least 3 numbers where each number
10 * (starting from the third) equals the sum of the two preceding ones.
11 *
12 * @param num The input string containing only digits
13 * @return List of integers forming a Fibonacci-like sequence, or empty list if not possible
14 */
15 public List<Integer> splitIntoFibonacci(String num) {
16 this.inputString = num;
17 backtrack(0);
18 return sequence;
19 }
20
21 /**
22 * Recursive backtracking method to find a valid Fibonacci-like sequence.
23 *
24 * @param startIndex The current index in the input string to start parsing from
25 * @return true if a valid Fibonacci-like sequence is found, false otherwise
26 */
27 private boolean backtrack(int startIndex) {
28 // Base case: if we've processed the entire string
29 if (startIndex == inputString.length()) {
30 // Valid sequence must have at least 3 numbers
31 return sequence.size() >= 3;
32 }
33
34 long currentNumber = 0;
35
36 // Try all possible numbers starting from startIndex
37 for (int endIndex = startIndex; endIndex < inputString.length(); ++endIndex) {
38 // No leading zeros allowed (except for the number 0 itself)
39 if (endIndex > startIndex && inputString.charAt(startIndex) == '0') {
40 break;
41 }
42
43 // Build the current number digit by digit
44 currentNumber = currentNumber * 10 + inputString.charAt(endIndex) - '0';
45
46 // Early termination: number exceeds Integer range or is too large for Fibonacci property
47 if (currentNumber > Integer.MAX_VALUE ||
48 (sequence.size() >= 2 &&
49 currentNumber > sequence.get(sequence.size() - 1) + sequence.get(sequence.size() - 2))) {
50 break;
51 }
52
53 // Check if current number can be added to the sequence
54 if (sequence.size() < 2 ||
55 currentNumber == sequence.get(sequence.size() - 1) + sequence.get(sequence.size() - 2)) {
56
57 // Add current number to the sequence
58 sequence.add((int) currentNumber);
59
60 // Recursively try to build the rest of the sequence
61 if (backtrack(endIndex + 1)) {
62 return true;
63 }
64
65 // Backtrack: remove the last added number if no valid sequence found
66 sequence.remove(sequence.size() - 1);
67 }
68 }
69
70 return false;
71 }
72}
73
1class Solution {
2public:
3 vector<int> splitIntoFibonacci(string num) {
4 int stringLength = num.size();
5 vector<int> result;
6
7 // Lambda function for depth-first search to find valid Fibonacci sequence
8 function<bool(int)> searchFibonacci = [&](int startIndex) -> bool {
9 // Base case: reached end of string, valid if we have at least 3 numbers
10 if (startIndex == stringLength) {
11 return result.size() > 2;
12 }
13
14 long long currentNumber = 0;
15
16 // Try different lengths for the current number starting from startIndex
17 for (int endIndex = startIndex; endIndex < stringLength; ++endIndex) {
18 // No leading zeros allowed (except for single digit "0")
19 if (endIndex > startIndex && num[startIndex] == '0') {
20 break;
21 }
22
23 // Build the current number digit by digit
24 currentNumber = currentNumber * 10 + num[endIndex] - '0';
25
26 // Early termination: number exceeds INT_MAX or violates Fibonacci property
27 if (currentNumber > INT_MAX ||
28 (result.size() > 1 &&
29 currentNumber > (long long)result[result.size() - 1] + result[result.size() - 2])) {
30 break;
31 }
32
33 // Check if current number can be part of Fibonacci sequence
34 if (result.size() < 2 ||
35 currentNumber == (long long)result[result.size() - 1] + result[result.size() - 2]) {
36
37 // Add current number to result and continue searching
38 result.push_back(currentNumber);
39
40 // Recursively search for the rest of the sequence
41 if (searchFibonacci(endIndex + 1)) {
42 return true;
43 }
44
45 // Backtrack if no valid sequence found
46 result.pop_back();
47 }
48 }
49
50 return false;
51 };
52
53 // Start the search from index 0
54 searchFibonacci(0);
55 return result;
56 }
57};
58
1function splitIntoFibonacci(num: string): number[] {
2 const stringLength = num.length;
3 const result: number[] = [];
4
5 // Depth-first search to find valid Fibonacci sequence
6 const searchFibonacci = (startIndex: number): boolean => {
7 // Base case: reached end of string, valid if we have at least 3 numbers
8 if (startIndex === stringLength) {
9 return result.length > 2;
10 }
11
12 let currentNumber = 0;
13
14 // Try different lengths for the current number starting from startIndex
15 for (let endIndex = startIndex; endIndex < stringLength; endIndex++) {
16 // No leading zeros allowed (except for single digit "0")
17 if (endIndex > startIndex && num[startIndex] === '0') {
18 break;
19 }
20
21 // Build the current number digit by digit
22 currentNumber = currentNumber * 10 + parseInt(num[endIndex]);
23
24 // Early termination: number exceeds INT_MAX (2^31 - 1) or violates Fibonacci property
25 if (currentNumber > 2147483647 ||
26 (result.length > 1 &&
27 currentNumber > result[result.length - 1] + result[result.length - 2])) {
28 break;
29 }
30
31 // Check if current number can be part of Fibonacci sequence
32 if (result.length < 2 ||
33 currentNumber === result[result.length - 1] + result[result.length - 2]) {
34
35 // Add current number to result and continue searching
36 result.push(currentNumber);
37
38 // Recursively search for the rest of the sequence
39 if (searchFibonacci(endIndex + 1)) {
40 return true;
41 }
42
43 // Backtrack if no valid sequence found
44 result.pop();
45 }
46 }
47
48 return false;
49 };
50
51 // Start the search from index 0
52 searchFibonacci(0);
53 return result;
54}
55
Time and Space Complexity
Time Complexity: O(n^3)
The analysis breaks down as follows:
- The outer DFS function explores different starting positions, which in the worst case can be
O(n)
recursive calls deep - At each recursive level, we iterate through the string from position
i
ton
, which takesO(n)
time - For each substring we consider, we perform operations to build the number
x
digit by digit, which takesO(n)
time in the worst case - The backtracking nature means we might explore multiple paths, but the pruning conditions (checking for overflow and Fibonacci property) help limit the search space
- In the worst case, we have
O(n)
recursive levels, each doingO(n)
work to iterate through positions, andO(n)
work to build numbers, resulting inO(n^3)
overall time complexity
Space Complexity: O(n)
The space analysis includes:
- The recursion stack depth can go up to
O(n)
in the worst case when we process each character - The
ans
list stores the Fibonacci sequence numbers, which can have at mostO(n)
elements (one for each character in the extreme case, though practically it would be less due to the Fibonacci constraint) - The variable
x
and other local variables useO(1)
space - Therefore, the total space complexity is
O(n)
for the recursion stack plusO(n)
for the answer list, which simplifies toO(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow During Number Construction
The Pitfall: When building the current number digit by digit using current_number = current_number * 10 + int(num[end_index])
, the multiplication can cause integer overflow even before we check the 32-bit limit. In Python, this isn't a runtime error, but the number might grow unnecessarily large before we detect it exceeds the limit, wasting computation time.
Solution: Check for potential overflow before the multiplication:
# Instead of:
current_number = current_number * 10 + int(num[end_index])
if current_number > 2**31 - 1:
break
# Better approach:
if current_number > (2**31 - 1 - int(num[end_index])) // 10:
break
current_number = current_number * 10 + int(num[end_index])
2. Incorrect Leading Zero Handling
The Pitfall: The code correctly handles leading zeros for multi-digit numbers, but developers often make mistakes by:
- Forgetting that "0" itself is valid (not a leading zero case)
- Breaking too early and missing valid sequences that start with "0" as a separate number
Example of the issue: For input "0112358", the sequence [0, 11, 2, 3, 5, 8] is invalid because 11 ≠ 0 + 11, but [0, 1, 1, 2, 3, 5, 8] is valid. The leading zero check must only apply when forming multi-digit numbers, not when "0" stands alone.
Solution: The current code handles this correctly with if end_index > start_index and num[start_index] == '0': break
, but ensure you understand why both conditions are necessary.
3. Premature Optimization Breaking Valid Sequences
The Pitfall: The optimization if current_number > fibonacci_sequence[-2] + fibonacci_sequence[-1]: break
assumes that once a number exceeds the required sum, all longer numbers will also exceed it. While this is true for positive integers, developers might incorrectly apply similar optimizations in other places.
Example mistake: Some might try to optimize by checking if the remaining string is too short to form a valid sequence, but calculating this incorrectly:
# Wrong optimization:
remaining_digits = string_length - end_index - 1
min_required_digits = len(str(fibonacci_sequence[-1])) if fibonacci_sequence else 1
if remaining_digits < min_required_digits:
continue # This could skip valid sequences!
Solution: Be conservative with optimizations. Test thoroughly with edge cases before adding pruning conditions.
4. Not Handling Edge Cases with Small Sequences
The Pitfall: Forgetting to handle strings that are too short to form a valid sequence (less than 3 characters where each forms a single-digit number).
Example: Input "12" cannot form a valid Fibonacci-like sequence since we need at least 3 numbers.
Solution: The base case already handles this by checking len(fibonacci_sequence) >= 3
, but ensure your implementation doesn't try to access fibonacci_sequence[-2]
when the list has fewer than 2 elements.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
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!