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 as1, 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 as1, 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:
- Try all possible positions for the first number
- Try all possible positions for the second number
- Once we have two numbers, recursively check if the remaining string can form a valid additive sequence
- 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.
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:
- Try all possible ways to choose the first number from the string
- For each first number, try all possible ways to choose the second number
- 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
and1
as our first two numbers - We know the next must be
1 + 1 = 2
- Check if remaining string
"2358"
starts with2
✓ - Continue: next must be
1 + 2 = 3
- Check if
"358"
starts with3
✓ - 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 ton-1
: defines where the first number ends - Second loop
j
fromi+1
ton
: defines where the second number ends - The first number is
num[:i]
and the second isnum[i:j]
Leading Zero Validation: Before attempting each split, we check for invalid leading zeros:
- If
i > 1
andnum[0] == '0'
: the first number would be multi-digit starting with 0, so we break - If
j - i > 1
andnum[i] == '0'
: the second number would be multi-digit starting with 0, so we skip thisj
Recursive DFS Function:
The dfs(a, b, num)
function validates if a string can continue an additive sequence starting with numbers a
and b
:
-
Base Case: If
num
is empty, we've successfully consumed the entire string, returnTrue
-
Leading Zero Check: If the expected sum
a + b
is greater than 0 butnum[0] == '0'
, this would create an invalid number with leading zero, returnFalse
-
Try All Possible Lengths:
- Iterate through possible lengths
i
from 1 tolen(num)
- Extract the substring
num[:i]
and convert to integer - Check if this equals our expected sum
a + b
- Iterate through possible lengths
-
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)
- Make a recursive call with updated parameters:
-
Backtracking: If no valid length produces the expected sum, return
False
to trigger backtracking
Algorithm Flow Example:
For input "112358"
:
- Try first=
"1"
(1), second="1"
(1) - Call
dfs(1, 1, "2358")
- Expected sum is 1+1=2, check if
"2358"
starts with "2" ✓ - Recursively call
dfs(1, 2, "358")
- Expected sum is 1+2=3, check if
"358"
starts with "3" ✓ - 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 EvaluatorExample 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! ✓)
- Length 1:
- 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! ✓)
- Length 1:
- 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
ton-1
, and the second loop runs fromi+1
ton
, giving usO(n^2)
possible pairs. - For each pair
(i, j)
, we call thedfs
function with the substringnum[j:]
. - Inside the
dfs
function, we iterate through the remaining string to find a matching sum. In the worst case, this loop runsO(n)
times. - The string slicing operations
num[:i]
andnum[i:]
each takeO(n)
time in the worst case. - The integer conversion
int(num[:i])
also takesO(n)
time for a string of lengthn
. - 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 ofO(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
What data structure does Breadth-first search typically uses to store intermediate states?
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!