Before attempting this problem, you should be comfortable with:
Backtracking / Recursion - Used to explore all possible ways to split the string and validate sequences
String to Integer Conversion - Building numbers digit by digit from string characters
Stack-based Iteration - Alternative to recursion for simulating DFS without the call stack
Pruning Techniques - Early termination when the current number exceeds the previous value
1. Backtracking
Intuition
The problem asks us to split a string into at least two parts where each consecutive part represents a number that is exactly one less than the previous. Since we need to explore all possible ways to split the string, backtracking is a natural choice. We try every possible split point, build numbers digit by digit, and check if they form a valid descending consecutive sequence.
Algorithm
Create a helper function isValid that checks if a list of numbers forms a descending consecutive sequence (each element is exactly one less than the previous) and contains at least two elements.
Use depth-first search starting at index 0 with an empty list of splits.
At each position, try extending the current number by including more digits (from current index to end of string).
For each potential number, add it to the splits list and recursively process the remaining string.
If we reach the end of the string, validate the sequence. If valid, return true.
Backtrack by removing the last added number and try the next split point.
classSolution:defsplitString(self, s:str)->bool:
n =len(s)defisValid(splits):for i inrange(1,len(splits)):if splits[i]!= splits[i -1]-1:returnFalsereturnlen(splits)>1defdfs(i, splits):if i == n:return isValid(splits)
num =0for j inrange(i, n):
num = num *10+int(s[j])
splits.append(num)if dfs(j +1, splits):returnTrue
splits.pop()returnFalsereturn dfs(0,[])
publicclassSolution{publicbooleansplitString(String s){returndfs(s,0,newArrayList<>());}privatebooleanisValid(List<Long> splits){for(int i =1; i < splits.size(); i++){if(splits.get(i)!= splits.get(i -1)-1){returnfalse;}}return splits.size()>1;}privatebooleandfs(String s,int i,List<Long> splits){if(i == s.length()){returnisValid(splits);}long num =0;for(int j = i; j < s.length(); j++){
num = num *10+(s.charAt(j)-'0');
splits.add(num);if(dfs(s, j +1, splits)){returntrue;}
splits.remove(splits.size()-1);}returnfalse;}}
classSolution{public:boolsplitString(string s){
vector<longlong> splits;returndfs(s,0, splits);}private:boolisValid(vector<longlong>& splits){for(int i =1; i < splits.size(); i++){if(splits[i]!= splits[i -1]-1){returnfalse;}}return splits.size()>1;}booldfs(string& s,int i, vector<longlong>& splits){if(i == s.size()){returnisValid(splits);}unsignedlonglong num =0;for(int j = i; j < s.size(); j++){
num = num *10+(s[j]-'0');
splits.push_back(num);if(dfs(s, j +1, splits)){returntrue;}
splits.pop_back();}returnfalse;}};
classSolution{/**
* @param {string} s
* @return {boolean}
*/splitString(s){const n = s.length;constisValid=(splits)=>{for(let i =1; i < splits.length; i++){if(splits[i]!== splits[i -1]-1){returnfalse;}}return splits.length >1;};constdfs=(i, splits)=>{if(i === n){returnisValid(splits);}let num =0;for(let j = i; j < n; j++){
num = num *10+Number(s[j]);
splits.push(num);if(dfs(j +1, splits)){returntrue;}
splits.pop();}returnfalse;};returndfs(0,[]);}}
publicclassSolution{publicboolSplitString(string s){returnDfs(s,0,newList<long>());}privateboolIsValid(List<long> splits){for(int i =1; i < splits.Count; i++){if(splits[i]!= splits[i -1]-1){returnfalse;}}return splits.Count >1;}privateboolDfs(string s,int i,List<long> splits){if(i == s.Length){returnIsValid(splits);}long num =0;for(int j = i; j < s.Length; j++){
num = num *10+(s[j]-'0');
splits.Add(num);if(Dfs(s, j +1, splits)){returntrue;}
splits.RemoveAt(splits.Count -1);}returnfalse;}}
funcsplitString(s string)bool{
n :=len(s)var isValid func(splits []int64)bool
isValid =func(splits []int64)bool{for i :=1; i <len(splits); i++{if splits[i]!= splits[i-1]-1{returnfalse}}returnlen(splits)>1}var dfs func(i int, splits []int64)bool
dfs =func(i int, splits []int64)bool{if i == n {returnisValid(splits)}var num int64=0for j := i; j < n; j++{
num = num*10+int64(s[j]-'0')
splits =append(splits, num)ifdfs(j+1, splits){returntrue}
splits = splits[:len(splits)-1]}returnfalse}returndfs(0,[]int64{})}
class Solution {funsplitString(s: String): Boolean {returndfs(s,0,mutableListOf())}privatefunisValid(splits: List<Long>): Boolean {for(i in1 until splits.size){if(splits[i]!= splits[i -1]-1){returnfalse}}return splits.size >1}privatefundfs(s: String, i: Int, splits: MutableList<Long>): Boolean {if(i == s.length){returnisValid(splits)}var num =0Lfor(j in i until s.length){
num = num *10+(s[j]-'0')
splits.add(num)if(dfs(s, j +1, splits)){returntrue}
splits.removeAt(splits.size -1)}returnfalse}}
classSolution{funcsplitString(_ s:String)->Bool{let chars =Array(s)let n = chars.count
funcisValid(_ splits:[Int64])->Bool{for i in1..<splits.count {if splits[i]!= splits[i -1]-1{returnfalse}}return splits.count >1}funcdfs(_ i:Int,_ splits:inout[Int64])->Bool{if i == n {returnisValid(splits)}var num:Int64=0for j in i..<n {
num = num *10+Int64(chars[j].asciiValue!-Character("0").asciiValue!)
splits.append(num)ifdfs(j +1,&splits){returntrue}
splits.removeLast()}returnfalse}var splits =[Int64]()returndfs(0,&splits)}}
Time & Space Complexity
Time complexity: O(nn)
Space complexity: O(n)
2. Recursion - I
Intuition
Instead of collecting all splits and validating at the end, we can optimize by passing the previous number directly. Once we fix the first number, we only need to find subsequent numbers that are exactly one less. This eliminates the need to store all splits and allows early termination when we find a valid sequence.
Algorithm
Iterate through all possible first numbers by trying each prefix of the string (excluding the last character to ensure at least two parts).
For each first number, call dfs with the remaining string and the first number as the previous value.
In dfs, try to form numbers from the current position. If a number equals prev - 1, recursively check the rest.
If we reach the end of the string during recursion, we found a valid split.
Return true if any starting number leads to a valid sequence.
classSolution:defsplitString(self, s:str)->bool:defdfs(index, prev):if index ==len(s):returnTrue
num =0for j inrange(index,len(s)):
num = num *10+int(s[j])if num +1== prev and dfs(j +1, num):returnTruereturnFalse
val =0for i inrange(len(s)-1):
val = val *10+int(s[i])if dfs(i +1, val):returnTruereturnFalse
publicclassSolution{publicbooleansplitString(String s){int n = s.length();long val =0;for(int i =0; i < n -1; i++){
val = val *10+(s.charAt(i)-'0');if(dfs(s, i +1, val)){returntrue;}}returnfalse;}privatebooleandfs(String s,int index,long prev){if(index == s.length()){returntrue;}long num =0;for(int j = index; j < s.length(); j++){
num = num *10+(s.charAt(j)-'0');if(num +1== prev &&dfs(s, j +1, num)){returntrue;}}returnfalse;}}
classSolution{public:boolsplitString(string s){int n = s.size();unsignedlonglong val =0;for(int i =0; i < n -1; i++){
val = val *10+(s[i]-'0');if(dfs(s, i +1, val)){returntrue;}}returnfalse;}private:booldfs(string& s,int index,longlong prev){if(index == s.size()){returntrue;}unsignedlonglong num =0;for(int j = index; j < s.size(); j++){
num = num *10+(s[j]-'0');if(num +1== prev &&dfs(s, j +1, num)){returntrue;}}returnfalse;}};
classSolution{/**
* @param {string} s
* @return {boolean}
*/splitString(s){const n = s.length;constdfs=(index, prev)=>{if(index === n){returntrue;}let num =0;for(let j = index; j < n; j++){
num = num *10+Number(s[j]);if(num +1=== prev &&dfs(j +1, num)){returntrue;}}returnfalse;};let val =0;for(let i =0; i < n -1; i++){
val = val *10+Number(s[i]);if(dfs(i +1, val)){returntrue;}}returnfalse;}}
publicclassSolution{publicboolSplitString(string s){int n = s.Length;long val =0;for(int i =0; i < n -1; i++){
val = val *10+(s[i]-'0');if(Dfs(s, i +1, val)){returntrue;}}returnfalse;}privateboolDfs(string s,int index,long prev){if(index == s.Length){returntrue;}long num =0;for(int j = index; j < s.Length; j++){
num = num *10+(s[j]-'0');if(num +1== prev &&Dfs(s, j +1, num)){returntrue;}}returnfalse;}}
funcsplitString(s string)bool{
n :=len(s)var dfs func(index int, prev int64)bool
dfs =func(index int, prev int64)bool{if index == n {returntrue}var num int64=0for j := index; j < n; j++{
num = num*10+int64(s[j]-'0')if num+1== prev &&dfs(j+1, num){returntrue}}returnfalse}var val int64=0for i :=0; i < n-1; i++{
val = val*10+int64(s[i]-'0')ifdfs(i+1, val){returntrue}}returnfalse}
class Solution {funsplitString(s: String): Boolean {val n = s.length
var value =0Lfor(i in0 until n -1){
value = value *10+(s[i]-'0')if(dfs(s, i +1, value)){returntrue}}returnfalse}privatefundfs(s: String, index: Int, prev: Long): Boolean {if(index == s.length){returntrue}var num =0Lfor(j in index until s.length){
num = num *10+(s[j]-'0')if(num +1== prev &&dfs(s, j +1, num)){returntrue}}returnfalse}}
classSolution{funcsplitString(_ s:String)->Bool{let chars =Array(s)let n = chars.count
funcdfs(_ index:Int,_ prev:Int64)->Bool{if index == n {returntrue}var num:Int64=0for j in index..<n {
num = num *10+Int64(chars[j].asciiValue!-Character("0").asciiValue!)if num +1== prev &&dfs(j +1, num){returntrue}}returnfalse}var val:Int64=0for i in0..<(n -1){
val = val *10+Int64(chars[i].asciiValue!-Character("0").asciiValue!)ifdfs(i +1, val){returntrue}}returnfalse}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(n) for recursion stack.
3. Recursion - II
Intuition
Building on the previous approach, we add an important pruning optimization. Since we need descending consecutive values, once the current number we are building becomes greater than or equal to the previous number, there is no point continuing to add more digits. This early termination significantly reduces the search space.
Algorithm
Same setup as Recursion I: iterate through possible first numbers.
In dfs, build numbers digit by digit from the current position.
If the current number equals prev - 1, recursively check the remaining string.
Key optimization: if the current number becomes greater than or equal to prev, break out of the loop early since adding more digits will only make it larger.
Return true when the end of string is reached with a valid sequence.
classSolution:defsplitString(self, s:str)->bool:defdfs(index, prev):if index ==len(s):returnTrue
num =0for j inrange(index,len(s)):
num = num *10+int(s[j])if num +1== prev and dfs(j +1, num):returnTrueif num >= prev:breakreturnFalse
val =0for i inrange(len(s)-1):
val = val *10+int(s[i])if dfs(i +1, val):returnTruereturnFalse
publicclassSolution{publicbooleansplitString(String s){int n = s.length();long val =0;for(int i =0; i < n -1; i++){
val = val *10+(s.charAt(i)-'0');if(dfs(s, i +1, val)){returntrue;}}returnfalse;}privatebooleandfs(String s,int index,long prev){if(index == s.length()){returntrue;}long num =0;for(int j = index; j < s.length(); j++){
num = num *10+(s.charAt(j)-'0');if(num +1== prev &&dfs(s, j +1, num)){returntrue;}if(num >= prev){break;}}returnfalse;}}
classSolution{public:boolsplitString(string s){int n = s.size();unsignedlonglong val =0;for(int i =0; i < n -1; i++){
val = val *10+(s[i]-'0');if(dfs(s, i +1, val)){returntrue;}}returnfalse;}private:booldfs(string& s,int index,longlong prev){if(index == s.size()){returntrue;}unsignedlonglong num =0;for(int j = index; j < s.size(); j++){
num = num *10+(s[j]-'0');if(num +1== prev &&dfs(s, j +1, num)){returntrue;}if(num >= prev){break;}}returnfalse;}};
classSolution{/**
* @param {string} s
* @return {boolean}
*/splitString(s){const n = s.length;constdfs=(index, prev)=>{if(index === n){returntrue;}let num =0;for(let j = index; j < n; j++){
num = num *10+Number(s[j]);if(num +1=== prev &&dfs(j +1, num)){returntrue;}if(num >= prev){break;}}returnfalse;};let val =0;for(let i =0; i < n -1; i++){
val = val *10+Number(s[i]);if(dfs(i +1, val)){returntrue;}}returnfalse;}}
publicclassSolution{publicboolSplitString(string s){int n = s.Length;long val =0;for(int i =0; i < n -1; i++){
val = val *10+(s[i]-'0');if(Dfs(s, i +1, val)){returntrue;}}returnfalse;}privateboolDfs(string s,int index,long prev){if(index == s.Length){returntrue;}long num =0;for(int j = index; j < s.Length; j++){
num = num *10+(s[j]-'0');if(num +1== prev &&Dfs(s, j +1, num)){returntrue;}if(num >= prev){break;}}returnfalse;}}
funcsplitString(s string)bool{
n :=len(s)var dfs func(index int, prev int64)bool
dfs =func(index int, prev int64)bool{if index == n {returntrue}var num int64=0for j := index; j < n; j++{
num = num*10+int64(s[j]-'0')if num+1== prev &&dfs(j+1, num){returntrue}if num >= prev {break}}returnfalse}var val int64=0for i :=0; i < n-1; i++{
val = val*10+int64(s[i]-'0')ifdfs(i+1, val){returntrue}}returnfalse}
class Solution {funsplitString(s: String): Boolean {val n = s.length
var value =0Lfor(i in0 until n -1){
value = value *10+(s[i]-'0')if(dfs(s, i +1, value)){returntrue}}returnfalse}privatefundfs(s: String, index: Int, prev: Long): Boolean {if(index == s.length){returntrue}var num =0Lfor(j in index until s.length){
num = num *10+(s[j]-'0')if(num +1== prev &&dfs(s, j +1, num)){returntrue}if(num >= prev){break}}returnfalse}}
classSolution{funcsplitString(_ s:String)->Bool{let chars =Array(s)let n = chars.count
funcdfs(_ index:Int,_ prev:Int64)->Bool{if index == n {returntrue}var num:Int64=0for j in index..<n {
num = num *10+Int64(chars[j].asciiValue!-Character("0").asciiValue!)if num +1== prev &&dfs(j +1, num){returntrue}if num >= prev {break}}returnfalse}var val:Int64=0for i in0..<(n -1){
val = val *10+Int64(chars[i].asciiValue!-Character("0").asciiValue!)ifdfs(i +1, val){returntrue}}returnfalse}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(n) for recursion stack.
4. Stack
Intuition
Instead of using recursion with the call stack, we can simulate the same process with an explicit stack. This converts the recursive solution into an iterative one, which can be useful for avoiding stack overflow on very deep recursions and makes the state transitions more explicit.
Algorithm
For each possible first number, push the state (next index, first number value) onto the stack.
While the stack is not empty, pop a state containing the current index and previous value.
Build numbers starting from the current index. When a number equals prev - 1:
If this number uses all remaining characters, return true.
Otherwise, push the new state (next index, current number) onto the stack.
Apply the same pruning: if the number grows to be greater than or equal to prev, stop building.
If all possibilities are exhausted without finding a valid sequence, return false.
classSolution:defsplitString(self, s:str)->bool:
n =len(s)
stack =[]
val =0for i inrange(n -1):
val = val *10+int(s[i])
stack.append((i +1, val))while stack:
index, prev = stack.pop()
num =0for j inrange(index, n):
num = num *10+int(s[j])if num +1== prev:if j +1== n:returnTrue
stack.append((j +1, num))elif num >= prev:breakreturnFalse
publicclassSolution{publicbooleansplitString(String s){int n = s.length();Stack<long[]> stack =newStack<>();long val =0;for(int i =0; i < n -1; i++){
val = val *10+(s.charAt(i)-'0');
stack.push(newlong[]{i +1, val});while(!stack.isEmpty()){long[] top = stack.pop();int index =(int) top[0];long prev = top[1];long num =0;for(int j = index; j < n; j++){
num = num *10+(s.charAt(j)-'0');if(num +1== prev){if(j +1== n){returntrue;}
stack.push(newlong[]{j +1, num});}elseif(num >= prev){break;}}}}returnfalse;}}
classSolution{public:boolsplitString(string s){int n = s.size();
stack<pair<int,longlong>> stack;unsignedlonglong val =0;for(int i =0; i < n -1; i++){
val = val *10+(s[i]-'0');
stack.push({i +1, val});while(!stack.empty()){auto[index, prev]= stack.top();
stack.pop();unsignedlonglong num =0;for(int j = index; j < n; j++){
num = num *10+(s[j]-'0');if(num +1== prev){if(j +1== n){returntrue;}
stack.push({j +1, num});}elseif(num >= prev){break;}}}}returnfalse;}};
classSolution{/**
* @param {string} s
* @return {boolean}
*/splitString(s){const n = s.length;let stack =[];let val =0;for(let i =0; i < n -1; i++){
val = val *10+Number(s[i]);
stack.push([i +1, val]);while(stack.length){let[index, prev]= stack.pop();let num =0;for(let j = index; j < n; j++){
num = num *10+Number(s[j]);if(num +1=== prev){if(j +1=== n){returntrue;}
stack.push([j +1, num]);}elseif(num >= prev){break;}}}}returnfalse;}}
publicclassSolution{publicboolSplitString(string s){int n = s.Length;var stack =newStack<long[]>();long val =0;for(int i =0; i < n -1; i++){
val = val *10+(s[i]-'0');
stack.Push(newlong[]{ i +1, val });while(stack.Count >0){long[] top = stack.Pop();int index =(int)top[0];long prev = top[1];long num =0;for(int j = index; j < n; j++){
num = num *10+(s[j]-'0');if(num +1== prev){if(j +1== n){returntrue;}
stack.Push(newlong[]{ j +1, num });}elseif(num >= prev){break;}}}}returnfalse;}}
funcsplitString(s string)bool{
n :=len(s)type pair struct{
index int
prev int64}
stack :=[]pair{}var val int64=0for i :=0; i < n-1; i++{
val = val*10+int64(s[i]-'0')
stack =append(stack, pair{i +1, val})forlen(stack)>0{
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
index, prev := top.index, top.prev
var num int64=0for j := index; j < n; j++{
num = num*10+int64(s[j]-'0')if num+1== prev {if j+1== n {returntrue}
stack =append(stack, pair{j +1, num})}elseif num >= prev {break}}}}returnfalse}
class Solution {funsplitString(s: String): Boolean {val n = s.length
val stack = ArrayDeque<LongArray>()var value =0Lfor(i in0 until n -1){
value = value *10+(s[i]-'0')
stack.addLast(longArrayOf((i +1).toLong(), value))while(stack.isNotEmpty()){val top = stack.removeLast()val index = top[0].toInt()val prev = top[1]var num =0Lfor(j in index until n){
num = num *10+(s[j]-'0')if(num +1== prev){if(j +1== n){returntrue}
stack.addLast(longArrayOf((j +1).toLong(), num))}elseif(num >= prev){break}}}}returnfalse}}
classSolution{funcsplitString(_ s:String)->Bool{let chars =Array(s)let n = chars.count
var stack =[(Int,Int64)]()var val:Int64=0for i in0..<(n -1){
val = val *10+Int64(chars[i].asciiValue!-Character("0").asciiValue!)
stack.append((i +1, val))while!stack.isEmpty {let(index, prev)= stack.removeLast()var num:Int64=0for j in index..<n {
num = num *10+Int64(chars[j].asciiValue!-Character("0").asciiValue!)if num +1== prev {if j +1== n {returntrue}
stack.append((j +1, num))}elseif num >= prev {break}}}}returnfalse}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(n)
Common Pitfalls
Integer Overflow with Large Numbers
The string can be up to 20 characters long, meaning numbers can exceed 64-bit integer limits. Using int or even standard long without considering overflow can produce incorrect comparisons. Use unsigned long long or language-specific big integer handling, and be cautious when building numbers digit by digit.
Not Requiring At Least Two Parts
The problem requires splitting into at least two parts. A common mistake is returning true when the entire string forms a single number. Always ensure the loop for the first number excludes the last character, forcing at least one more part to exist.
Missing the Pruning Optimization
Without early termination when the current number exceeds or equals the previous number, the solution explores many unnecessary branches. Since we need strictly descending consecutive values, once num >= prev, no valid sequence can be formed by adding more digits, so breaking out of the loop is essential for efficiency.