问题的链接
问题描述
小C拿到了一个由数字字符和 ?
组成的字符串,她的目标是将所有的 ?
替换成数字字符,使得替换后的字符串表示的十进制整数成为正整数 pp 的倍数。由于方案数可能非常大,需要对最终的结果取模 109+7109+7。
测试样例
样例1:
输入:
s = "??",p = 1
输出:100
样例2:
输入:
s = "????1",p = 12
输出:0
样例3:
输入:
s = "1??2",p = 3
输出:34
算法分析
这个问题可以当作是是一个典型的回溯(Backtracking)问题。其核心在于通过递归尝试所有可能的数字填充问号的位置,并检查生成的数字是否满足给定条件(即生成的数字能否被给定的正整数 pp 整除)。
由于可能的组合数量巨大,因此在计算过程中使用了模运算以防止结果溢出。
超大数可以下划线,这样更加清晰,表示的值是一样的。1000000007可以表示成1_000_000_007。
对于每个问号位置,我们有10种选择(从0到9),因此可以采用递归的方式遍历所有可能的选择。每做出一次选择后,检查当前形成的数字是否满足条件,如果不满足,则撤销这次选择,继续尝试其他选择(这就是回溯的过程)。
由于结果需要对 1_000_000_007取模,所以在每次累加结果时都要进行取模操作,确保不会发生数值溢出。
countValidNumbers 方法:
如果已经处理完字符串中的所有字符(即 index == chars.length
),则检查当前形成的数字是否能被 pp 整除,如果能,则返回1,否则返回0。
如果当前字符不是问号,则直接将其转换为数字并加入当前正在构建的数字中,然后递归处理下一个字符。
如果当前字符是问号,则尝试用0到9中的每一个数字替换它,并递归地处理下一个字符。每次递归调用返回的结果都需要累加起来,同时注意对结果进行取模操作。
代码实现
public class Main {
private static final int MOD = 1_000_000_007;
public static int solution(String s, int p) {
return countValidNumbers(s.toCharArray(), p, 0, 0);
}
private static int countValidNumbers(char[] chars, int p, int index, long currentNumber) {
if (index == chars.length) {
return currentNumber % p == 0 ? 1 : 0;
}
if (chars[index] != '?') {
currentNumber = (currentNumber * 10 + (chars[index] - '0')) % p;
return countValidNumbers(chars, p, index + 1, currentNumber);
} else {
int count = 0;
for (char digit = '0'; digit <= '9'; digit++) {
long newNumber = (currentNumber * 10 + (digit - '0')) % p;
count = (count + countValidNumbers(chars, p, index + 1, newNumber)) % MOD;
}
return count;
}
}
public static void main(String[] args) {
System.out.println(solution("??", 1) == 100); // true
System.out.println(solution("????1", 12) == 0); // true
System.out.println(solution("1??2", 3) == 34); // true
}
}
时间复杂度和空间复杂度分析
时间复杂度:
空间复杂度: 深度,
AI优化
在这里我们可以用AI看看能不能优化,让豆包MarsCode AI 给一下有没有优化的可能性
优化代码
public class Main {
private static final int MOD = 1_000_000_007;
public static int solution(String s, int p) {
int n = s.length();
// dp[i][j] 表示处理到第i个字符,余数为j的方案数
int[][] dp = new int[n + 1][p];
// 初始化边界情况,处理到第0个字符(空串),余数为0的方案数为1
dp[0][0] = 1;
for (int i = 0; i < n; i++) {
for (int prevRemainder = 0; prevRemainder < p; prevRemainder++) {
if (dp[i][prevRemainder] == 0) {
continue;
}
if (s.charAt(i)!= '?') {
int digit = s.charAt(i) - '0';
int newRemainder = (prevRemainder * 10 + digit) % p;
dp[i + 1][newRemainder] = (dp[i + 1][newRemainder] + dp[i][prevRemainder]) % MOD;
} else {
for (int digit = 0; digit < 10; digit++) {
int newRemainder = (prevRemainder * 10 + digit) % p;
dp[i + 1][newRemainder] = (dp[i + 1][newRemainder] + dp[i][prevRemainder]) % MOD;
}
}
}
}
return dp[n][0];
}
public static void main(String[] args) {
System.out.println(solution("??", 1) == 100); // true
System.out.println(solution("????1", 12) == 0); // true
System.out.println(solution("1??2", 3) == 34); // true
}
}
优化代码分析
-
外层循环:
for (int i = 0; i < n; i++)
遍历字符串s
的每个字符,时间复杂度为 ( O(n) )。
-
内层循环:
for (int prevRemainder = 0; prevRemainder < p; prevRemainder++)
遍历所有可能的余数,时间复杂度为 ( O(p) )。
-
处理
?
的情况:- 如果当前字符是
?
,则需要遍历0
到9
的所有可能数字,时间复杂度为 ( O(10) )。
- 如果当前字符是
优化代码时间复杂度和空间复杂度分析
- 时间复杂度:O(n⋅p⋅10)
- 空间复杂度:O(n⋅p)