Facebook Pixel

306. Additive Number

Problem Description

An additive number is a string of digits that can be split into a sequence where each number (after the first two) equals the sum of the two numbers before it.

To be valid, an additive sequence must have at least three numbers. Starting from the third number, each number must equal the sum of the two numbers immediately before it.

Given a string containing only digits, determine if it represents an additive number by returning true if it does, or false otherwise.

Important constraint: Numbers in the sequence cannot have leading zeros (except for the number 0 itself). This means sequences like 1, 2, 03 or 1, 02, 3 are invalid because 03 and 02 have leading zeros.

Example breakdown:

  • For string "112358": This can be split as 1, 1, 2, 3, 5, 8 where:

    • 1 + 1 = 2
    • 1 + 2 = 3
    • 2 + 3 = 5
    • 3 + 5 = 8
    • This forms a valid additive sequence, so return true
  • For string "199100199": This can be split as 1, 99, 100, 199 where:

    • 1 + 99 = 100
    • 99 + 100 = 199
    • This forms a valid additive sequence, so return true

The task is to check all possible ways to split the input string into numbers and determine if any splitting forms a valid additive sequence following the rules above.

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 parsing a string of digits into numbers, not nodes and edges in a graph structure.

Need to solve for kth smallest/largest?

  • No: We're not looking for any kth smallest or largest element, but rather validating if a sequence can be formed.

Involves Linked Lists?

  • No: The problem works with strings and number sequences, not linked list data structures.

Does the problem have small constraints?

  • Yes: While not explicitly stated, the nature of the problem suggests manageable string lengths. More importantly, we need to explore all possible ways to split the string into valid number sequences, which requires trying different combinations of splits.

Brute force / Backtracking?

  • Yes: We need to systematically try different ways to split the string:
    1. Try all possible positions for the first number
    2. Try all possible positions for the second number
    3. Once we have two numbers, recursively check if the remaining string can form a valid additive sequence
    4. If a path doesn't work, backtrack and try different splits

The backtracking nature is evident because:

  • We make choices (where to split the string for each number)
  • We explore each choice recursively
  • If a choice leads to failure, we undo it and try alternatives
  • We're essentially exploring a decision tree of all possible valid splits

Conclusion: The flowchart correctly leads us to use a Backtracking approach, where we try different combinations of splitting the string into numbers and recursively validate if they form an additive sequence.

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 an additive sequence, the entire rest of the sequence is determined. This is because each subsequent number must be the sum of the previous two.

Think of it like dominoes - once the first two are placed, everything else follows automatically. If we have numbers a and b as our first two, then the third must be a + b, the fourth must be b + (a + b), and so on.

This observation leads us to a backtracking strategy:

  1. Try all possible ways to choose the first number from the string
  2. For each first number, try all possible ways to choose the second number
  3. Once we have two numbers, we can deterministically check if the remaining string follows the additive pattern

The beauty of this approach is that we don't need to guess all numbers - just the first two. After that, we know exactly what the next number should be (sum = first + second), and we only need to check if the remaining string starts with this sum.

For example, with string "112358":

  • If we choose 1 and 1 as our first two numbers
  • We know the next must be 1 + 1 = 2
  • Check if remaining string "2358" starts with 2
  • Continue: next must be 1 + 2 = 3
  • Check if "358" starts with 3
  • And so on...

The backtracking happens when our current path fails. If at any point the expected sum doesn't match what we see in the string, we backtrack and try different positions for splitting the first two numbers.

The leading zero constraint adds another layer: we must skip any split that would create a multi-digit number starting with '0' (like "03" or "012"), as these aren't valid number representations.

Solution Approach

The implementation uses a two-layer approach: an outer loop to try all possible first two numbers, and an inner recursive function to validate if the remaining string can form a valid additive sequence.

Main Function Structure: The outer function iterates through all possible ways to split the string into the first two numbers:

  • First loop i from 1 to n-1: defines where the first number ends
  • Second loop j from i+1 to n: defines where the second number ends
  • The first number is num[:i] and the second is num[i:j]

Leading Zero Validation: Before attempting each split, we check for invalid leading zeros:

  • If i > 1 and num[0] == '0': the first number would be multi-digit starting with 0, so we break
  • If j - i > 1 and num[i] == '0': the second number would be multi-digit starting with 0, so we skip this j

Recursive DFS Function: The dfs(a, b, num) function validates if a string can continue an additive sequence starting with numbers a and b:

  1. Base Case: If num is empty, we've successfully consumed the entire string, return True

  2. Leading Zero Check: If the expected sum a + b is greater than 0 but num[0] == '0', this would create an invalid number with leading zero, return False

  3. Try All Possible Lengths:

    • Iterate through possible lengths i from 1 to len(num)
    • Extract the substring num[:i] and convert to integer
    • Check if this equals our expected sum a + b
  4. Recursive Call: If we find a match:

    • Make a recursive call with updated parameters: dfs(b, a + b, num[i:])
    • The new first number becomes b
    • The new second number becomes a + b (the sum we just matched)
    • The remaining string is num[i:] (after removing the matched portion)
  5. Backtracking: If no valid length produces the expected sum, return False to trigger backtracking

Algorithm Flow Example: For input "112358":

  1. Try first="1" (1), second="1" (1)
  2. Call dfs(1, 1, "2358")
  3. Expected sum is 1+1=2, check if "2358" starts with "2" ✓
  4. Recursively call dfs(1, 2, "358")
  5. Expected sum is 1+2=3, check if "358" starts with "3" ✓
  6. Continue until string is empty, return True

The combination of systematic enumeration (trying all possible first two numbers) and recursive validation (checking if the rest follows the pattern) ensures we find any valid additive sequence if it exists.

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 trace through the algorithm with the string "199100199".

Step 1: Try different splits for first two numbers

We'll iterate through possible positions:

  • First number ends at position i
  • Second number ends at position j

Let's try i=1, j=3:

  • First number: "1" → 1
  • Second number: "99" → 99
  • Remaining string: "100199"

Step 2: Check for leading zeros

  • First number "1": single digit, no leading zero issue ✓
  • Second number "99": starts with '9', no leading zero issue ✓

Step 3: Call recursive DFS

Call dfs(1, 99, "100199"):

Iteration 1 in DFS:

  • Expected sum: 1 + 99 = 100
  • Try different lengths from remaining string "100199":
    • Length 1: "1" → 1 (not equal to 100)
    • Length 2: "10" → 10 (not equal to 100)
    • Length 3: "100" → 100 (equals 100! ✓)
  • Found match! Recursively call dfs(99, 100, "199")

Iteration 2 in DFS:

  • Expected sum: 99 + 100 = 199
  • Try different lengths from remaining string "199":
    • Length 1: "1" → 1 (not equal to 199)
    • Length 2: "19" → 19 (not equal to 199)
    • Length 3: "199" → 199 (equals 199! ✓)
  • Found match! Recursively call dfs(100, 199, "")

Iteration 3 in DFS:

  • Remaining string is empty → Base case reached
  • Return True

The recursive calls unwind, all returning True, so the main function returns True.

Why other splits fail:

If we had tried i=2, j=3 instead:

  • First number: "19" → 19
  • Second number: "9" → 9
  • Expected sum: 19 + 9 = 28
  • Remaining string "100199" doesn't start with "28" → This path fails

The algorithm systematically tries all possible first two numbers until finding a valid sequence or exhausting all possibilities.

Solution Implementation

1class Solution:
2    def isAdditiveNumber(self, num: str) -> bool:
3        """
4        Check if the string can form an additive sequence.
5        An additive sequence is where each number (except first two) equals the sum of preceding two.
6        """
7      
8        def validate_sequence(first_num: int, second_num: int, remaining_str: str) -> bool:
9            """
10            Recursively validate if remaining string can continue the additive sequence.
11          
12            Args:
13                first_num: The first number in current pair
14                second_num: The second number in current pair  
15                remaining_str: The remaining string to check
16              
17            Returns:
18                True if remaining string forms valid additive sequence, False otherwise
19            """
20            # Base case: if no remaining string, sequence is valid
21            if not remaining_str:
22                return True
23          
24            # Check for invalid leading zeros (except for single digit 0)
25            expected_sum = first_num + second_num
26            if expected_sum > 0 and remaining_str[0] == '0':
27                return False
28          
29            # Try different lengths for the next number
30            for next_num_length in range(1, len(remaining_str) + 1):
31                potential_next_num = int(remaining_str[:next_num_length])
32              
33                # If this number equals the expected sum, continue checking
34                if potential_next_num == expected_sum:
35                    # Recursively check with updated numbers and remaining string
36                    if validate_sequence(second_num, expected_sum, remaining_str[next_num_length:]):
37                        return True
38          
39            return False
40      
41        string_length = len(num)
42      
43        # Try all possible splits for first two numbers
44        for first_num_end in range(1, string_length - 1):
45            # Skip if first number has leading zeros (except single '0')
46            if first_num_end > 1 and num[0] == '0':
47                break
48              
49            for second_num_end in range(first_num_end + 1, string_length):
50                # Skip if second number has leading zeros (except single '0')
51                if second_num_end - first_num_end > 1 and num[first_num_end] == '0':
52                    continue
53              
54                # Extract first two numbers
55                first_number = int(num[:first_num_end])
56                second_number = int(num[first_num_end:second_num_end])
57              
58                # Check if remaining string forms valid sequence
59                if validate_sequence(first_number, second_number, num[second_num_end:]):
60                    return True
61      
62        return False
63
1class Solution {
2    /**
3     * Determines if a string is an additive number.
4     * An additive number is a string whose digits can form an additive sequence.
5     * A valid additive sequence should contain at least three numbers.
6     * Except for the first two numbers, each subsequent number in the sequence must be the sum of the preceding two.
7     * 
8     * @param num the input string containing only digits
9     * @return true if num is an additive number, false otherwise
10     */
11    public boolean isAdditiveNumber(String num) {
12        int n = num.length();
13      
14        // Try all possible positions for the first number (ending at index i-1)
15        // Limit to 19 digits to avoid Long overflow
16        for (int i = 1; i < Math.min(n - 1, 19); ++i) {
17            // Try all possible positions for the second number (ending at index j-1)
18            for (int j = i + 1; j < Math.min(n, i + 19); ++j) {
19                // First number cannot have leading zeros (except when it's just "0")
20                if (i > 1 && num.charAt(0) == '0') {
21                    break;
22                }
23              
24                // Second number cannot have leading zeros (except when it's just "0")
25                if (j - i > 1 && num.charAt(i) == '0') {
26                    continue;
27                }
28              
29                // Parse the first two numbers
30                long firstNum = Long.parseLong(num.substring(0, i));
31                long secondNum = Long.parseLong(num.substring(i, j));
32              
33                // Check if the remaining string forms a valid additive sequence
34                if (dfs(firstNum, secondNum, num.substring(j))) {
35                    return true;
36                }
37            }
38        }
39      
40        return false;
41    }
42
43    /**
44     * Recursively checks if the remaining string can form a valid additive sequence
45     * given the previous two numbers.
46     * 
47     * @param prev1 the first of the two previous numbers
48     * @param prev2 the second of the two previous numbers
49     * @param remaining the remaining string to be checked
50     * @return true if the remaining string forms a valid sequence, false otherwise
51     */
52    private boolean dfs(long prev1, long prev2, String remaining) {
53        // Base case: if no more digits left, the sequence is valid
54        if ("".equals(remaining)) {
55            return true;
56        }
57      
58        // The next number cannot have leading zeros (unless the sum itself is 0)
59        if (prev1 + prev2 > 0 && remaining.charAt(0) == '0') {
60            return false;
61        }
62      
63        // Try different lengths for the next number in the sequence
64        // Limit to 19 digits to avoid Long overflow
65        for (int i = 1; i < Math.min(remaining.length() + 1, 19); ++i) {
66            // Check if the current substring equals the sum of the previous two numbers
67            if (prev1 + prev2 == Long.parseLong(remaining.substring(0, i))) {
68                // Recursively check the rest of the string with updated previous numbers
69                if (dfs(prev2, prev1 + prev2, remaining.substring(i))) {
70                    return true;
71                }
72            }
73        }
74      
75        return false;
76    }
77}
78
1class Solution {
2public:
3    bool isAdditiveNumber(string num) {
4        int n = num.size();
5      
6        // Try all possible combinations of first two numbers
7        // i represents the end position of the first number (exclusive)
8        for (int i = 1; i < min(n - 1, 19); ++i) {
9            // j represents the end position of the second number (exclusive)
10            for (int j = i + 1; j < min(n, i + 19); ++j) {
11                // First number cannot have leading zeros (except single digit 0)
12                if (i > 1 && num[0] == '0') break;
13              
14                // Second number cannot have leading zeros (except single digit 0)
15                if (j - i > 1 && num[i] == '0') continue;
16              
17                // Extract the first two numbers
18                long long firstNum = stoll(num.substr(0, i));
19                long long secondNum = stoll(num.substr(i, j - i));
20              
21                // Check if the remaining string forms a valid additive sequence
22                string remaining = num.substr(j, n - j);
23                if (checkAdditiveSequence(firstNum, secondNum, remaining)) {
24                    return true;
25                }
26            }
27        }
28      
29        return false;
30    }
31
32private:
33    bool checkAdditiveSequence(long long prev1, long long prev2, string remaining) {
34        // Base case: if no more digits left, the sequence is valid
35        if (remaining.empty()) {
36            return true;
37        }
38      
39        // The next number cannot have leading zeros (except 0 itself)
40        long long expectedSum = prev1 + prev2;
41        if (expectedSum > 0 && remaining[0] == '0') {
42            return false;
43        }
44      
45        // Try to match the sum with prefixes of the remaining string
46        // Limit to 19 digits to avoid overflow (max digits in long long)
47        int maxLength = min(static_cast<int>(remaining.size() + 1), 19);
48      
49        for (int i = 1; i < maxLength; ++i) {
50            long long currentNum = stoll(remaining.substr(0, i));
51          
52            // If current number equals the expected sum
53            if (currentNum == expectedSum) {
54                // Recursively check the rest of the string
55                string nextRemaining = remaining.substr(i, remaining.size() - i);
56                if (checkAdditiveSequence(prev2, expectedSum, nextRemaining)) {
57                    return true;
58                }
59            }
60        }
61      
62        return false;
63    }
64};
65
1function isAdditiveNumber(num: string): boolean {
2    const n = num.length;
3  
4    // Try all possible combinations of first two numbers
5    // i represents the end position of the first number (exclusive)
6    for (let i = 1; i < Math.min(n - 1, 19); i++) {
7        // j represents the end position of the second number (exclusive)
8        for (let j = i + 1; j < Math.min(n, i + 19); j++) {
9            // First number cannot have leading zeros (except single digit 0)
10            if (i > 1 && num[0] === '0') break;
11          
12            // Second number cannot have leading zeros (except single digit 0)
13            if (j - i > 1 && num[i] === '0') continue;
14          
15            // Extract the first two numbers
16            const firstNum = BigInt(num.substring(0, i));
17            const secondNum = BigInt(num.substring(i, j));
18          
19            // Check if the remaining string forms a valid additive sequence
20            const remaining = num.substring(j);
21            if (checkAdditiveSequence(firstNum, secondNum, remaining)) {
22                return true;
23            }
24        }
25    }
26  
27    return false;
28}
29
30function checkAdditiveSequence(prev1: bigint, prev2: bigint, remaining: string): boolean {
31    // Base case: if no more digits left, the sequence is valid
32    if (remaining.length === 0) {
33        return true;
34    }
35  
36    // Calculate the expected sum of the two previous numbers
37    const expectedSum = prev1 + prev2;
38  
39    // The next number cannot have leading zeros (except 0 itself)
40    if (expectedSum > 0n && remaining[0] === '0') {
41        return false;
42    }
43  
44    // Convert expectedSum to string for comparison
45    const expectedSumStr = expectedSum.toString();
46  
47    // Check if the remaining string starts with the expected sum
48    if (remaining.startsWith(expectedSumStr)) {
49        // Extract the next remaining part after the matched sum
50        const nextRemaining = remaining.substring(expectedSumStr.length);
51      
52        // Recursively check the rest of the string with updated previous numbers
53        return checkAdditiveSequence(prev2, expectedSum, nextRemaining);
54    }
55  
56    return false;
57}
58

Time and Space Complexity

Time Complexity: O(n^3)

The time complexity analysis breaks down as follows:

  • The outer two loops iterate through all possible combinations of the first two numbers in the sequence. The first loop runs from 1 to n-1, and the second loop runs from i+1 to n, giving us O(n^2) possible pairs.
  • For each pair (i, j), we call the dfs function with the substring num[j:].
  • Inside the dfs function, we iterate through the remaining string to find a matching sum. In the worst case, this loop runs O(n) times.
  • The string slicing operations num[:i] and num[i:] each take O(n) time in the worst case.
  • The integer conversion int(num[:i]) also takes O(n) time for a string of length n.
  • However, the dominant factor is the nested structure: O(n^2) pairs × O(n) for DFS traversal = O(n^3).

Space Complexity: O(n)

The space complexity analysis:

  • The recursive dfs function can have a maximum call stack depth of O(n) in the worst case, where each recursive call processes one number from the sequence.
  • Each recursive call stores local variables (a, b, num) and the substring operations create new string objects.
  • The string slicing creates new strings that could be up to O(n) in size.
  • The overall space complexity is O(n) due to the recursion stack and the string operations.

Common Pitfalls

1. Incorrect Leading Zero Handling

The Pitfall: A common mistake is not properly handling the leading zero constraint. Many implementations either:

  • Forget to check for leading zeros entirely
  • Only check the first character without considering multi-digit numbers
  • Incorrectly allow numbers like "01", "02", "003" in the sequence

Example of Incorrect Code:

# WRONG: This doesn't properly validate leading zeros
if remaining_str[0] == '0' and len(remaining_str) > 1:
    return False  # This rejects "0" itself, which is valid!

The Problem: This would incorrectly reject valid sequences where "0" appears as a standalone number, like in "101123" which can be split as [1, 0, 1, 1, 2, 3].

Correct Solution:

# Check if the expected sum would create a number with leading zeros
expected_sum = first_num + second_num
if expected_sum > 0 and remaining_str[0] == '0':
    return False  # Only reject if we expect non-zero but string starts with '0'

2. Integer Overflow with Large Numbers

The Pitfall: The string can contain very large numbers that exceed standard integer limits in some languages. While Python handles arbitrary precision integers automatically, this could be an issue in languages like Java or C++.

Example Problematic Input:

num = "12345678901234567890123456789012345678901234567890"
# This could cause overflow in languages with fixed integer sizes

Solution for Python: No change needed as Python handles big integers automatically.

Solution for Other Languages: Use string addition or BigInteger classes:

// Java solution using BigInteger
BigInteger first = new BigInteger(num.substring(0, i));
BigInteger second = new BigInteger(num.substring(i, j));
BigInteger sum = first.add(second);

3. Off-by-One Errors in Loop Boundaries

The Pitfall: Incorrectly setting loop boundaries, especially forgetting that we need at least three numbers in the sequence.

Example of Incorrect Code:

# WRONG: This allows the second number to consume the entire remaining string
for second_num_end in range(first_num_end + 1, string_length + 1):  # Wrong upper bound!
    # This leaves no room for a third number

The Problem: If second_num_end equals string_length, there's no remaining string to form the third number, violating the "at least three numbers" requirement.

Correct Solution:

# Ensure there's at least one character left for the third number
for second_num_end in range(first_num_end + 1, string_length):  # Correct: stops at string_length-1

4. Early Termination with Break vs Continue

The Pitfall: Using break when you should use continue (or vice versa) when handling leading zeros.

Example of Incorrect Code:

# WRONG: This stops checking all possibilities for the second number
if second_num_end - first_num_end > 1 and num[first_num_end] == '0':
    break  # Wrong! This exits the entire inner loop

The Problem: Using break here would stop checking other valid positions for the second number. For example, with "1023", after trying second number as "02" (invalid), it would stop instead of trying "023".

Correct Solution:

# Skip only this invalid second number, continue checking others
if second_num_end - first_num_end > 1 and num[first_num_end] == '0':
    continue  # Correct: skip this iteration but continue the loop

5. Not Handling Edge Cases Properly

The Pitfall: Failing to handle special cases like:

  • Strings with all zeros: "000"
  • Single zero in sequence: "101123"
  • Minimum length strings: "123" (barely enough for 3 numbers)

Solution: Ensure your validation logic:

  • Allows "0" as a valid standalone number
  • Properly validates sequences with zeros
  • Handles the minimum case of exactly 3 numbers
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What data structure does Breadth-first search typically uses to store intermediate states?


Recommended Readings

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

Load More