Facebook Pixel

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 as 01, 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:

  1. Starting from the beginning, trying different lengths for the first number
  2. For each potential number, checking if it violates the constraints (too large, has leading zeros)
  3. If we have less than 2 numbers, adding it to our sequence
  4. If we have 2 or more numbers, only adding it if it equals the sum of the previous two
  5. Recursively continuing this process
  6. Backtracking when a path doesn't lead to a valid solution
  7. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Try taking 1 digit for the first number, then try different lengths for the second number
  2. Try taking 2 digits for the first number, then try different lengths for the second number
  3. 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:

  1. 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.

  2. 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 from i to n-1
  3. 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])
  4. 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 equals ans[-2] + ans[-1]
  5. Backtracking:

    • 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

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 Evaluator

Example 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 to ans: 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 to ans: 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 to ans: 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 to ans: 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 to ans: 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 find 13 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 to n, which takes O(n) time
  • For each substring we consider, we perform operations to build the number x digit by digit, which takes O(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 doing O(n) work to iterate through positions, and O(n) work to build numbers, resulting in O(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 most O(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 use O(1) space
  • Therefore, the total space complexity is O(n) for the recursion stack plus O(n) for the answer list, which simplifies to O(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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

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

Load More