Additive Number
MedPrompt
An additive number is a string whose digits can form an additive sequence.
A valid additive sequence should contain at least three numbers. Except for the first two numbers, each subsequent number in the sequence must be the sum of the preceding two.
Given a string containing only digits, return true if it is an additive number or false otherwise.
Note: Numbers in the additive sequence cannot have leading zeros, so sequence 1, 2, 03 or 1, 02, 3 is invalid.
Example 1:
Input: "112358"
Output: true
Explanation:
The digits can form an additive sequence: 1, 1, 2, 3, 5, 8.
1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, 3 + 5 = 8Example 2:
Input: "199100199"
Output: true
Explanation:
The additive sequence is: 1, 99, 100, 199.
1 + 99 = 100, 99 + 100 = 199
Constraints:
1 <= num.length <= 35numconsists only of digits.
Follow up: How would you handle overflow for very large input integers?
Approaches
2 approaches with complexity analysis and trade-offs.
A much more efficient approach is to realize that an additive sequence is uniquely determined by its first two numbers. We can iterate through all possible first and second numbers, and for each pair, check if they can form a valid additive sequence that consumes the entire input string.
Algorithm
-
- The main function
isAdditiveNumberiterates through all possible lengths,iandj, for the first two numbers.
- The main function
-
- The loop for
i(length of the first number) runs from1ton/2.
- The loop for
-
- The loop for
j(length of the second number) runs from1up to the point where the remaining string lengthn-i-jis at leastmax(i, j).
- The loop for
-
- Inside the loops, extract the first two number strings,
s1ands2.
- Inside the loops, extract the first two number strings,
-
- Check for invalid leading zeros. If
s1has a leading zero (and length > 1), we can break the outer loop. Ifs2has one, continue to the nextj.
- Check for invalid leading zeros. If
-
- Call a recursive helper function,
checkSequence(s1, s2, rest_of_string), to validate the remainder of the string.
- Call a recursive helper function,
-
- The
checkSequencefunction is the core of the backtracking:
- a. Base Case: If the remaining string is empty, it means the whole string was consumed by a valid sequence, so return
true. - b. Calculate the sum of the two previous numbers using a
stringAddhelper. - c. Check if the remaining string starts with the calculated sum string. If not, return
false. - d. If it does, make a recursive call with the next set of numbers:
checkSequence(s2, sum, rest.substring(sum.length())).
- The
-
- If
checkSequencereturnstrueat any point, the main function returnstrue.
- If
-
- If the loops complete without finding a solution, return
false.
- If the loops complete without finding a solution, return
Walkthrough
We use two nested loops to select the first two numbers, num1 and num2. The outer loop determines the length of num1, and the inner loop determines the length of num2. Some optimizations can be applied to the loop bounds. For instance, the length of the first or second number cannot be more than half the total string length, because the third number must be at least as long as the longer of the first two.
After selecting num1 and num2, we start a validation process. This process recursively or iteratively generates the next number in the sequence (sum = num1 + num2) and checks if the remaining input string starts with this sum. If it does, we continue the process with num2 and sum. If at any point the check fails, or if we successfully consume the entire string, the process terminates.
Since the numbers can be very large (up to 35 digits), we cannot use standard integer types like long. Instead, we must represent numbers as strings and implement a string addition helper function to handle the 'Follow up' part of the problem.
class Solution { public boolean isAdditiveNumber(String num) { int n = num.length(); if (n < 3) return false; // i is the length of the first number for (int i = 1; i <= n / 2; ++i) { // j is the length of the second number // The third number's length must be >= max(i, j). // So, n - i - j >= max(i, j). for (int j = 1; Math.max(i, j) <= n - i - j; ++j) { if (isValid(i, j, num)) { return true; } } } return false; } // Checks if a valid sequence can be formed starting with numbers of length i and j private boolean isValid(int i, int j, String num) { String s1 = num.substring(0, i); String s2 = num.substring(i, i + j); // Rule: no leading zeros, unless the number is "0" itself. if (s1.length() > 1 && s1.charAt(0) == '0') return false; if (s2.length() > 1 && s2.charAt(0) == '0') return false; String rest = num.substring(i + j); return checkSequence(s1, s2, rest); } // Recursively checks if the 'rest' of the string follows the additive sequence private boolean checkSequence(String prev1, String prev2, String rest) { if (rest.isEmpty()) { return true; // The entire string has been consumed by the sequence } String sum = stringAdd(prev1, prev2); if (!rest.startsWith(sum)) { return false; // The next number in the string doesn't match the sum } // Recurse with the next pair of numbers return checkSequence(prev2, sum, rest.substring(sum.length())); } // Helper function to add two numbers represented as strings private String stringAdd(String n1, String n2) { StringBuilder res = new StringBuilder(); int i = n1.length() - 1; int j = n2.length() - 1; int carry = 0; while (i >= 0 || j >= 0 || carry != 0) { int digit1 = i >= 0 ? n1.charAt(i--) - '0' : 0; int digit2 = j >= 0 ? n2.charAt(j--) - '0' : 0; int sum = digit1 + digit2 + carry; res.append(sum % 10); carry = sum / 10; } return res.reverse().toString(); }}Complexity
Time
O(n^3). The nested loops to pick the first two numbers have a complexity of `O(n^2)`. For each pair, the `checkSequence` function is called. The work done in one full path of `checkSequence` involves a series of string additions and comparisons. The total work is proportional to the sum of lengths of all numbers, which is `O(n)`. Thus, the validation for one pair is `O(n)`. Total complexity is `O(n^2) * O(n) = O(n^3)`.
Space
O(n). The space is dominated by the recursion stack depth of `checkSequence`, which can be at most `O(n)` (in the case of a sequence like "101123..."). The strings created during the process also contribute `O(n)` space.
Trade-offs
Pros
Significantly more efficient than brute-forcing all partitions.
Passes within the time limits for the given constraints.
The logic is a direct and structured way to search for the solution.
Cons
The complexity is polynomial, which might be slow for very large
n(thoughn=35is fine).Requires careful implementation of string addition and handling of edge cases like leading zeros.
This is a highly inefficient approach that explores every possible way to partition the input string into a sequence of numbers. For each partition, it checks if the sequence is additive. This method is primarily for demonstrating a naive solution and understanding the problem space.
Algorithm
-
- Define a recursive function
find(index, partition)to generate all possible partitions of the input stringnum.
- Define a recursive function
-
- The base case for the recursion is when
indexreaches the end of the string. At this point, a complete partition has been formed.
- The base case for the recursion is when
-
- Once a partition is formed, check if it represents a valid additive sequence. A sequence is additive if it has at least three numbers and each number after the second is the sum of the two preceding ones.
-
- In the recursive step, loop from
k = indexton-1to form the next number in the partition,s = num.substring(index, k+1).
- In the recursive step, loop from
-
- Handle leading zeros: if
shas more than one digit and starts with '0', it's an invalid number, so this path is pruned.
- Handle leading zeros: if
-
- Add the valid number
sto the currentpartitionand make a recursive call:find(k+1, partition). After the call returns, backtrack by removingsto explore other possibilities.
- Add the valid number
-
- The validation function, say
isAdditive(partition), will use a string addition helper to verify the sum property.
- The validation function, say
-
- If any partition is found to be a valid additive sequence, the main function returns
trueimmediately. If all partitions are explored without success, it returnsfalse.
- If any partition is found to be a valid additive sequence, the main function returns
Walkthrough
The core idea is to use recursion or backtracking to generate all 2^(n-1) possible partitions of the string. A function generate(index, current_partition) would try to form a number starting at index and ending at k (where k goes from index to n-1). For each number formed, it's added to current_partition, and the function recurses from k+1. When a full partition is formed (i.e., index == n), it's validated. The validation step checks if it's an additive sequence with at least three numbers, where each number after the second is the sum of the two preceding ones. Due to the large number of possible partitions, this approach is not feasible for the given constraints.
Complexity
Time
O(2^n * n). The number of ways to partition a string of length `n` is `2^(n-1)`. Validating each partition takes `O(n)` time, as it involves string additions and comparisons over the length of the string.
Space
O(n). The recursion depth can go up to `n`, and the list storing the current partition can also take `O(n)` space.
Trade-offs
Pros
Conceptually simple to understand as it directly models the problem of finding a valid partition.
Cons
Extremely slow due to its exponential time complexity.
Impractical for the given constraints (n <= 35) and will time out.
Solutions
Solution
class Solution { public boolean isAdditiveNumber ( String num ) { int n = num . length (); for ( int i = 1 ; i < Math . min ( n - 1 , 19 ); ++ i ) { for ( int j = i + 1 ; j < Math . min ( n , i + 19 ); ++ j ) { if ( i > 1 && num . charAt ( 0 ) == '0' ) { break ; } if ( j - i > 1 && num . charAt ( i ) == '0' ) { continue ; } long a = Long . parseLong ( num . substring ( 0 , i )); long b = Long . parseLong ( num . substring ( i , j )); if ( dfs ( a , b , num . substring ( j ))) { return true ; } } } return false ; } private boolean dfs ( long a , long b , String num ) { if ( "" . equals ( num )) { return true ; } if ( a + b > 0 && num . charAt ( 0 ) == '0' ) { return false ; } for ( int i = 1 ; i < Math . min ( num . length () + 1 , 19 ); ++ i ) { if ( a + b == Long . parseLong ( num . substring ( 0 , i ))) { if ( dfs ( b , a + b , num . substring ( i ))) { return true ; } } } return false ; } }class Solution { public: bool isAdditiveNumber ( string num ) { int n = num . size (); for ( int i = 1 ; i < min ( n - 1 , 19 ); ++ i ) { for ( int j = i + 1 ; j < min ( n , i + 19 ); ++ j ) { if ( i > 1 && num [ 0 ] == '0' ) break ; if ( j - i > 1 && num [ i ] == '0' ) continue ; auto a = stoll ( num . substr ( 0 , i )); auto b = stoll ( num . substr ( i , j - i )); if ( dfs ( a , b , num . substr ( j , n - j ))) return true ; } } return false ; } bool dfs ( long long a , long long b , string num ) { if ( num == "" ) return true ; if ( a + b > 0 && num [ 0 ] == '0' ) return false ; for ( int i = 1 ; i < min (( int ) num . size () + 1 , 19 ); ++ i ) if ( a + b == stoll ( num . substr ( 0 , i ))) if ( dfs ( b , a + b , num . substr ( i , num . size () - i ))) return true ; return false ; } };class Solution : def isAdditiveNumber ( self , num : str ) -> bool : def dfs ( startIndex : int , out : List [ int ]) -> bool : # equal check is here if len ( out ) >= 3 and out [ - 1 ] != out [ - 2 ] + out [ - 3 ]: return False if startIndex == len ( num ): return len ( out ) >= 3 for i in range ( startIndex , len ( num )): current = num [ startIndex : i + 1 ] if ( len ( current ) > 1 and current [ 0 ] == '0' ): break out . append ( int ( current )) if dfs ( i + 1 , out ): return True out . pop () return False if not num : return False return dfs ( 0 , []) ############# # follow up: super large number, solve overflow issue class Solution : def addStrings ( num1 : str , num2 : str ) -> str : carry = 0 result = [] i , j = len ( num1 ) - 1 , len ( num2 ) - 1 while i >= 0 or j >= 0 or carry : x = int ( num1 [ i ]) if i >= 0 else 0 y = int ( num2 [ j ]) if j >= 0 else 0 sum = x + y + carry result . append ( str ( sum % 10 )) carry = sum // 10 i , j = i - 1 , j - 1 return '' . join ( reversed ( result )) def isAdditiveNumber ( num : str ) -> bool : n = len ( num ) for i in range ( 1 , n ): for j in range ( i + 1 , n ): num1 , num2 = num [: i ], num [ i : j ] if ( len ( num1 ) > 1 and num1 [ 0 ] == '0' ) or ( len ( num2 ) > 1 and num2 [ 0 ] == '0' ): continue while j < n : sum = addStrings ( num1 , num2 ) if not num . startswith ( sum , j ): break j += len ( sum ) num1 , num2 = num2 , sum if j == n : return True return False ############# class Solution : def isAdditiveNumber ( self , num : str ) -> bool : def dfs ( a , b , num ): if not num : return True if a + b > 0 and num [ 0 ] == '0' : return False for i in range ( 1 , len ( num ) + 1 ): if a + b == int ( num [: i ]): if dfs ( b , a + b , num [ i :]): return True return False n = len ( num ) for i in range ( 1 , n - 1 ): # 1st cut if i > 1 and num [ 0 ] == '0' : # 0 + 1 = 1 is fine, but 00 + 1 is wrong break for j in range ( i + 1 , n ): # 2nd cut, so making it a 3-segments if j - i > 1 and num [ i ] == '0' : break if dfs ( int ( num [: i ]), int ( num [ i : j ]), num [ j :]): # better than below, early stop return True return FalseVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.