You are given a string s
consisting of digits ('0'-'9'). Your task is to count the number of ways to split s
into one or more non-empty substrings so that:
Return the total number of such ways, modulo 10^9 + 7
.
Key constraints:
At first glance, this problem seems to ask for all ways to partition a string into substrings and check if the sequence is non-decreasing. A brute-force approach would be to try every possible split, convert substrings to integers, and check the sequence order and leading zeros. However, since the string can be long (up to 3500 characters), this would be far too slow.
To optimize, we need to avoid recomputing the same subproblems. This suggests using dynamic programming (DP). We can define a state that represents the number of ways to split the string up to a certain index, given the last substring. We also need to efficiently compare substrings for ordering, which can be done with string comparison or precomputed longest common prefix (LCP) arrays.
The main challenge is efficiently checking the non-decreasing constraint and handling leading zeros. We need to iterate over possible partition points and use DP to avoid redundant work.
We use dynamic programming to count the number of valid ways to split the string. Here’s how:
dp[i]
be the number of ways to split the prefix s[0:i]
(exclusive of i
), such that the last number ends at position i-1
.i
(from 1 to n), consider all possible start indices j
(j < i
) for the last number.s[j]
is '0' and i-j > 1
(leading zero check).s[prev_j:j]
) to the current substring (s[j:i]
) to ensure non-decreasing order.dp[j]
to dp[i]
.dp[0] = 1
(empty string has one way).s[j:i]
and s[prev_j:j]
efficiently, precompute the Longest Common Prefix (LCP) between all pairs of positions.dp[n]
, where n
is the length of s
.Why this works: By building up solutions from smaller prefixes and only considering valid, non-decreasing splits, we avoid redundant checks and ensure efficiency.
Consider s = "327"
:
The DP will build up as follows:
Brute-force approach:
s
.This is efficient enough for the given constraints.
We tackled the problem of counting valid ways to split a digit string into non-decreasing, non-leading-zero numbers. By recognizing overlapping subproblems, we used dynamic programming and optimized substring comparison with LCP arrays. This reduced the exponential brute-force search to an efficient O(n2) solution, enabling us to handle large inputs. The key insight was to structure the problem as a prefix DP and carefully handle substring comparisons and leading zero rules.
MOD = 10**9 + 7
class Solution:
def numberOfCombinations(self, s: str) -> int:
n = len(s)
if s[0] == '0':
return 0
# Precompute LCP (Longest Common Prefix)
lcp = [[0] * (n + 1) for _ in range(n + 1)]
for i in range(n - 1, -1, -1):
for j in range(n - 1, -1, -1):
if s[i] == s[j]:
lcp[i][j] = lcp[i + 1][j + 1] + 1
# dp[i][k]: number of ways to split s[0:i] with last part of length k
dp = [[0] * (n + 1) for _ in range(n + 1)]
for k in range(1, n + 1):
if s[0] != '0':
dp[k][k] = 1
for i in range(1, n + 1):
for k in range(1, i + 1):
j = i - k
if s[j] == '0':
continue
# Add all ways for previous lengths <= k
if j == 0:
dp[i][k] = 1
continue
# Compare s[j-k:j] and s[j:i]
if j - k >= 0:
prev = j - k
cmp = 0
l = lcp[prev][j]
if l >= k or (l < k and s[prev + l] <= s[j + l]):
cmp = 1
if cmp:
dp[i][k] = (dp[i][k] + dp[j][k]) % MOD
# Add all ways for shorter previous length
dp[i][k] = (dp[i][k] + sum(dp[j][1:k])) % MOD
return sum(dp[n][1:]) % MOD
class Solution {
public:
int numberOfCombinations(string s) {
int n = s.size();
const int MOD = 1e9 + 7;
if (s[0] == '0') return 0;
// Precompute LCP
vector<vector<int>> lcp(n + 1, vector<int>(n + 1, 0));
for (int i = n - 1; i >= 0; --i) {
for (int j = n - 1; j >= 0; --j) {
if (s[i] == s[j]) {
lcp[i][j] = lcp[i + 1][j + 1] + 1;
}
}
}
// dp[i][k]: number of ways to split s[0:i] with last part of length k
vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
for (int k = 1; k <= n; ++k) {
if (s[0] != '0') {
dp[k][k] = 1;
}
}
for (int i = 1; i <= n; ++i) {
for (int k = 1; k <= i; ++k) {
int j = i - k;
if (s[j] == '0') continue;
if (j == 0) {
dp[i][k] = 1;
continue;
}
if (j - k >= 0) {
int prev = j - k;
int l = lcp[prev][j];
if (l >= k || (l < k && s[prev + l] <= s[j + l])) {
dp[i][k] = (dp[i][k] + dp[j][k]) % MOD;
}
}
int sum = 0;
for (int len = 1; len < k; ++len) {
sum = (sum + dp[j][len]) % MOD;
}
dp[i][k] = (dp[i][k] + sum) % MOD;
}
}
int res = 0;
for (int k = 1; k <= n; ++k) res = (res + dp[n][k]) % MOD;
return res;
}
};
class Solution {
public int numberOfCombinations(String s) {
int n = s.length();
int MOD = 1000000007;
if (s.charAt(0) == '0') return 0;
// Precompute LCP
int[][] lcp = new int[n + 1][n + 1];
for (int i = n - 1; i >= 0; --i) {
for (int j = n - 1; j >= 0; --j) {
if (s.charAt(i) == s.charAt(j)) {
lcp[i][j] = lcp[i + 1][j + 1] + 1;
}
}
}
// dp[i][k]: number of ways to split s[0:i] with last part of length k
int[][] dp = new int[n + 1][n + 1];
for (int k = 1; k <= n; ++k) {
if (s.charAt(0) != '0') {
dp[k][k] = 1;
}
}
for (int i = 1; i <= n; ++i) {
for (int k = 1; k <= i; ++k) {
int j = i - k;
if (s.charAt(j) == '0') continue;
if (j == 0) {
dp[i][k] = 1;
continue;
}
if (j - k >= 0) {
int prev = j - k;
int l = lcp[prev][j];
if (l >= k || (l < k && s.charAt(prev + l) <= s.charAt(j + l))) {
dp[i][k] = (dp[i][k] + dp[j][k]) % MOD;
}
}
int sum = 0;
for (int len = 1; len < k; ++len) {
sum = (sum + dp[j][len]) % MOD;
}
dp[i][k] = (dp[i][k] + sum) % MOD;
}
}
int res = 0;
for (int k = 1; k <= n; ++k) res = (res + dp[n][k]) % MOD;
return res;
}
}
var numberOfCombinations = function(s) {
const n = s.length;
const MOD = 1e9 + 7;
if (s[0] === '0') return 0;
// Precompute LCP
const lcp = Array.from({length: n+1}, () => Array(n+1).fill(0));
for (let i = n - 1; i >= 0; --i) {
for (let j = n - 1; j >= 0; --j) {
if (s[i] === s[j]) {
lcp[i][j] = lcp[i + 1][j + 1] + 1;
}
}
}
// dp[i][k]: number of ways to split s[0:i] with last part of length k
const dp = Array.from({length: n+1}, () => Array(n+1).fill(0));
for (let k = 1; k <= n; ++k) {
if (s[0] !== '0') {
dp[k][k] = 1;
}
}
for (let i = 1; i <= n; ++i) {
for (let k = 1; k <= i; ++k) {
let j = i - k;
if (s[j] === '0') continue;
if (j === 0) {
dp[i][k] = 1;
continue;
}
if (j - k >= 0) {
let prev = j - k;
let l = lcp[prev][j];
if (l >= k || (l < k && s[prev + l] <= s[j + l])) {
dp[i][k] = (dp[i][k] + dp[j][k]) % MOD;
}
}
let sum = 0;
for (let len = 1; len < k; ++len) {
sum = (sum + dp[j][len]) % MOD;
}
dp[i][k] = (dp[i][k] + sum) % MOD;
}
}
let res = 0;
for (let k = 1; k <= n; ++k) res = (res + dp[n][k]) % MOD;
return res;
};