Given a string s
consisting only of digits, your task is to determine if it is possible to split s
into two or more non-empty substrings such that:
s
must be used, and substrings must not overlap or be reused.
Return true
if such a split is possible, and false
otherwise.
Example:
Input: s = "54321"
Output: true
Explanation: One possible split is ["5", "4", "3", "2", "1"].
Constraints:
1 <= s.length <= 20
s
consists only of digits ('0'-'9')When approaching this problem, the first idea is to try every possible way to split the string into numbers and check if they form a descending consecutive sequence. Since the split points are not fixed and the numbers can have varying lengths, brute-forcing all possible splits seems feasible for small input sizes.
However, we want to avoid unnecessary work and make the process efficient. We can use recursion (backtracking) to systematically try all possible first numbers, then recursively check if the rest of the string can be split into the next descending value, and so on. If at any point we can't split further or the constraints are violated (e.g., leading zeros, non-consecutive numbers), we backtrack and try another split.
The main challenge is to efficiently try all valid splits, avoid invalid ones early, and return as soon as a valid sequence is found.
We use a recursive backtracking algorithm to explore all possible splits of the input string s
:
s
(from length 1 up to len(s) - 1
) as the starting number. We stop at len(s) - 1
to ensure at least two substrings.
true
.
This approach is efficient for the given constraints because the maximum string length is 20, making the recursion tree manageable.
Let's walk through the input s = "05040302"
:
true
.This example shows how the recursive process finds a valid split by trying all possibilities and backtracking on invalid ones.
Brute-force approach: The number of ways to split a string of length n
is exponential, as at each position we can choose to split or not. In the worst case, there are up to 2^{n-1}
ways to split.
Optimized backtracking approach:
For each split, we only proceed if the next number is exactly one less and has no leading zeros, which prunes many branches. The number of recursive calls is still exponential in the worst case, but with n ≤ 20
, this is acceptable.
To solve the problem of splitting a string into descending consecutive values, we use recursive backtracking to explore all possible splits, checking for valid descending sequences without leading zeros. The manageable input size allows this approach to work efficiently. The key insight is to prune invalid paths early and backtrack as soon as constraints are violated, making the solution both effective and elegant.
class Solution:
def splitString(self, s: str) -> bool:
def dfs(start, prev, count):
if start == len(s):
return count >= 2
for end in range(start + 1, len(s) + 1):
curr_str = s[start:end]
if len(curr_str) > 1 and curr_str[0] == '0':
break
curr = int(curr_str)
if prev is None or curr == prev - 1:
if dfs(end, curr, count + 1):
return True
return False
return dfs(0, None, 0)
class Solution {
public:
bool dfs(const string& s, int start, long long prev, int count) {
if (start == s.size())
return count >= 2;
for (int end = start + 1; end <= s.size(); ++end) {
string curr_str = s.substr(start, end - start);
if (curr_str.size() > 1 && curr_str[0] == '0')
break;
long long curr = stoll(curr_str);
if (prev == -1 || curr == prev - 1) {
if (dfs(s, end, curr, count + 1))
return true;
}
}
return false;
}
bool splitString(string s) {
return dfs(s, 0, -1, 0);
}
};
class Solution {
public boolean splitString(String s) {
return dfs(s, 0, -1, 0);
}
private boolean dfs(String s, int start, long prev, int count) {
if (start == s.length())
return count >= 2;
for (int end = start + 1; end <= s.length(); end++) {
String currStr = s.substring(start, end);
if (currStr.length() > 1 && currStr.charAt(0) == '0')
break;
long curr = Long.parseLong(currStr);
if (prev == -1 || curr == prev - 1) {
if (dfs(s, end, curr, count + 1))
return true;
}
}
return false;
}
}
var splitString = function(s) {
function dfs(start, prev, count) {
if (start === s.length) return count >= 2;
for (let end = start + 1; end <= s.length; ++end) {
let currStr = s.substring(start, end);
if (currStr.length > 1 && currStr[0] === '0') break;
let curr = Number(currStr);
if (prev === null || curr === prev - 1) {
if (dfs(end, curr, count + 1)) return true;
}
}
return false;
}
return dfs(0, null, 0);
};