Hard
题目描述
你在一个名为 num 的字符串中写下了许多正整数。但是,你意识到你忘记添加逗号来分隔不同的数字。你记得整数列表是非递减的,并且没有整数有前导零。
返回你可能写下的整数列表的方案数,以得到字符串 num。由于答案可能很大,请返回对 10^9 + 7 取模的结果。
示例 1:
输入:num = "327"
输出:2
解释:你可能写下了以下数字:
3, 27
327
示例 2:
输入:num = "094"
输出:0
解释:没有数字可以有前导零,并且所有数字必须是正数。
示例 3:
输入:num = "0"
输出:0
解释:没有数字可以有前导零,并且所有数字必须是正数。
约束条件:
- 1 <= num.length <= 3500
- num 由数字 ‘0’ 到 ‘9’ 组成
解题思路
这道题需要用动态规划来解决。核心思路是:对于字符串的每个位置,考虑以该位置结尾的所有可能数字的分割方案。
状态定义:
dp[i][j]表示以位置i结尾,最后一个数字长度为j的分割方案数
状态转移:
- 如果当前数字有前导零(且长度>1),则方案数为0
- 否则,需要枚举上一个数字的所有可能长度
- 上一个数字的长度不能超过当前数字的长度(保证非递减)
- 当长度相等时,需要比较数值大小
优化技巧:
- 使用前缀和优化状态转移,避免重复计算
- 预处理字符串比较,快速判断两个子串的大小关系
- 使用滚动数组优化空间复杂度
关键点:
- 处理前导零的特殊情况
- 高效比较两个数字字符串的大小
- 利用前缀和加速DP转移过程
时间复杂度主要来自于预处理和DP转移,通过合理的优化可以在限定时间内通过。
代码实现
class Solution {
public:
int numberOfCombinations(string num) {
int n = num.length();
const int MOD = 1e9 + 7;
if (num[0] == '0') return 0;
// 预处理:计算LCP (Longest Common Prefix)
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 (num[i] == num[j]) {
lcp[i][j] = lcp[i + 1][j + 1] + 1;
} else {
lcp[i][j] = 0;
}
}
}
// 比较两个子串的大小
auto compare = [&](int i1, int j1, int i2, int j2) -> bool {
int len1 = j1 - i1 + 1, len2 = j2 - i2 + 1;
if (len1 != len2) return len1 < len2;
int common = lcp[i1][i2];
if (common >= len1) return false; // 相等
return num[i1 + common] < num[i2 + common];
};
// dp[i][j] 表示以位置i结尾,最后一个数字长度为j的方案数
vector<vector<long long>> dp(n, vector<long long>(n + 1, 0));
vector<long long> prefix(n + 1, 0);
// 初始化
for (int j = 1; j <= n; j++) {
if (j == 1 || num[0] != '0') {
dp[j - 1][j] = 1;
}
}
for (int i = 0; i < n; i++) {
prefix[0] = 0;
for (int j = 1; j <= n; j++) {
prefix[j] = (prefix[j - 1] + dp[i][j]) % MOD;
}
for (int j = 1; j <= n - i - 1; j++) {
int start = i + 1;
int end = start + j - 1;
if (end >= n || (j > 1 && num[start] == '0')) continue;
// 找到最大的长度k,使得前面的数字不超过当前数字
int maxK = j;
for (int k = 1; k <= min(j, i + 1); k++) {
int prevStart = start - k;
if (prevStart < 0) break;
if (k < j || compare(prevStart, start - 1, start, end)) {
dp[end][j] = (dp[end][j] + dp[start - 1][k]) % MOD;
} else if (k == j) {
// 长度相等且前面的数不小于当前数,只能取相等的情况
if (!compare(start, end, prevStart, start - 1)) {
dp[end][j] = (dp[end][j] + dp[start - 1][k]) % MOD;
}
break;
}
}
}
}
long long result = 0;
for (int j = 1; j <= n; j++) {
result = (result + dp[n - 1][j]) % MOD;
}
return result;
}
};
class Solution:
def numberOfCombinations(self, num: str) -> int:
n = len(num)
MOD = 10**9 + 7
if num[0] == '0':
return 0
# 预处理:计算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 num[i] == num[j]:
lcp[i][j] = lcp[i + 1][j + 1] + 1
def compare(i1, j1, i2, j2):
# 比较 num[i1:j1+1] 和 num[i2:j2+1]
len1, len2 = j1 - i1 + 1, j2 - i2 + 1
if len1 != len2:
return len1 < len2
common = lcp[i1][i2]
if common >= len1:
return False # 相等
return num[i1 + common] < num[i2 + common]
# dp[i][j] 表示以位置i结尾,最后一个数字长度为j的方案数
dp = [[0] * (n + 1) for _ in range(n)]
# 初始化
for j in range(1, n + 1):
if j == 1 or num[0] != '0':
if j <= n:
dp[j - 1][j] = 1
for i in range(n):
for j in range(1, n - i):
start = i + 1
end = start + j - 1
if end >= n or (j > 1 and num[start] == '0'):
continue
for k in range(1, min(j + 1, i + 2)):
prev_start = start - k
if prev_start < 0:
break
if k < j or compare(prev_start, start - 1, start, end):
dp[end][j] = (dp[end][j] + dp[start - 1][k]) % MOD
elif k == j and not compare(start, end, prev_start, start - 1):
dp[end][j] = (dp[end][j] + dp[start - 1][k]) % MOD
return sum(dp[n - 1]) % MOD
public class Solution {
public int NumberOfCombinations(string num) {
int n = num.Length;
const int MOD = 1000000007;
if (num[0] == '0') return 0;
// 预处理:计算LCP (Longest Common Prefix)
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 (num[i] == num[j]) {
lcp[i, j] = lcp[i + 1, j + 1] + 1;
}
}
}
bool Compare(int i1, int j1, int i2, int j2) {
int len1 = j1 - i1 + 1, len2 = j2 - i2 + 1;
if (len1 != len2) return len1 < len2;
int common = lcp[i1, i2];
if (common >= len1) return false;
return num[i1 + common] < num[i2 + common];
}
// dp[i,j] 表示以位置i结尾,最后一个数字长度为j的方案数
long[,] dp = new long[n, n + 1];
// 初始化
for (int j = 1; j <= n; j++) {
if (j == 1 || num[0] != '0') {
if (j <= n) {
dp[j - 1, j] = 1;
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 1; j <= n - i - 1; j++) {
int start = i + 1;
int end = start + j - 1;
if (end >= n || (j > 1 && num[start] == '0')) continue;
for (int k = 1; k <= Math.Min(j, i + 1); k++) {
int prevStart = start - k;
if (prevStart < 0) break;
if (k < j || Compare(prevStart, start - 1, start, end)) {
dp[end, j] = (dp[end, j] + dp[start - 1, k]) % MOD;
} else if (k == j && !Compare(start, end, prevStart, start - 1)) {
dp[end, j] = (dp[end, j] + dp[start - 1, k]) % MOD;
}
}
}
}
long result = 0;
for (int j = 1; j <= n; j++) {
result = (result + dp[n - 1, j]) % MOD;
}
return (int)result;
}
}
var numberOfCombinations = function(num) {
const n = num.length;
const MOD = 1e9 + 7;
if (num[0]
复杂度分析
| 复杂度类型 | 大O表示法 | 说明 |
|---|---|---|
| 时间复杂度 | O(n³) | 预处理LCP需要O(n²),DP转移需要O(n³) |
相关题目
. Decode Ways (Medium)
. Decode Ways II (Hard)
. Restore The Array (Hard)