问题描述
小C拿到了一个由数字字符和 ?
组成的字符串,她的目标是将所有的 ?
替换成数字字符,使得替换后的字符串表示的十进制整数成为正整数 pp 的倍数。由于方案数可能非常大,需要对最终的结果取模 109+7109+7。
测试样例
样例1:
输入:
s = "??",p = 1
输出:100
样例2:
输入:
s = "????1",p = 12
输出:0
样例3:
输入:
s = "1??2",p = 3
输出:34
C++解题
问题理解
你有一个由数字字符和 ?
组成的字符串 s
,目标是将其中的所有 ?
替换成数字字符,使得替换后的字符串表示的十进制整数是正整数 p
的倍数。由于方案数可能非常大,需要对最终的结果取模 10^9 + 7
。
数据结构选择
- 字符串处理:由于我们需要处理字符串中的每个字符,并且可能需要替换
?
,因此字符串操作是必不可少的。 - 动态规划:考虑到我们需要计算所有可能的替换方案,并且需要判断这些方案是否满足条件,动态规划可能是一个合适的选择。
算法步骤
- 初始化:定义一个动态规划数组
dp
,其中dp[i][r]
表示前i
个字符组成的字符串,其对p
取模的结果为r
的方案数。 - 状态转移:
- 如果当前字符是数字,直接更新
dp
数组。 - 如果当前字符是
?
,则需要考虑所有可能的数字(0-9),并更新dp
数组。
- 如果当前字符是数字,直接更新
- 取模操作:在每次更新
dp
数组时,都需要对10^9 + 7
取模,以防止溢出。 - 最终结果:最终结果是
dp[n][0]
,其中n
是字符串的长度,0
表示对p
取模的结果为 0。
总结
通过动态规划,我们可以有效地计算出所有可能的替换方案,并判断哪些方案满足条件。这个方法的时间复杂度是 O(n * p * 10)
,其中 n
是字符串的长度,p
是给定的正整数,10 是 ?
可以替换的数字个数。
代码实现
#include <iostream>
#include <vector>
#include <string>
using namespace std;
const int MOD = 1e9 + 7;
int solution(string s, int p) {
int n = s.size();
vector<vector<int>> dp(n + 1, vector<int>(p, 0));
// 初始化
dp[0][0] = 1;
// 状态转移
for (int i = 0; i < n; ++i) {
for (int r = 0; r < p; ++r) {
if (dp[i][r] == 0) continue;
if (s[i] == '?') {
// 如果是 '?',考虑所有可能的数字
for (int digit = 0; digit <= 9; ++digit) {
int new_r = (r * 10 + digit) % p;
dp[i + 1][new_r] = (dp[i + 1][new_r] + dp[i][r]) % MOD;
}
} else {
// 如果是数字,直接更新
int digit = s[i] - '0';
int new_r = (r * 10 + digit) % p;
dp[i + 1][new_r] = (dp[i + 1][new_r] + dp[i][r]) % MOD;
}
}
}
// 最终结果
return dp[n][0];
}
int main() {
cout << (solution("??", 1) == 100) << endl;
cout << (solution("????1", 12) == 0) << endl;
cout << (solution("1??2", 3) == 34) << endl;
return 0;
}
Python解题
思路说明
题目要求将字符串中的 ?
替换成数字字符,使得替换后的字符串表示的十进制整数成为正整数 p 的倍数。由于方案数可能非常大,需要对最终的结果取模 109+7。我们可以通过动态规划来解决这个问题。动态规划的核心思想是将问题分解为子问题,并记录每个子问题的解,从而避免重复计算。
解题过程
-
初始化动态规划数组:
- 定义一个二维数组
f
,其中f[i][j]
表示前i
个字符组成的数字模p
余数为j
的方案数。 - 初始化
f[0][0] = 1
,表示空字符串模p
余数为0
的方案数为 1。
- 定义一个二维数组
-
状态转移:
- 遍历字符串
s
的每个字符s[i]
。 - 对于每个字符
s[i]
,遍历所有可能的余数j
。 - 如果
s[i]
是?
,则枚举所有可能的数字0
到9
,并更新f[i+1][(j * 10 + x) % p]
。 - 如果
s[i]
是数字,则直接计算f[i+1][(j * 10 + (ord(ch) - ord('0'))) % p]
。
- 遍历字符串
-
结果输出:
- 最终结果为
f[n][0]
,即前n
个字符组成的数字模p
余数为0
的方案数。
- 最终结果为
复杂度分析
-
时间复杂度:
- 外层循环遍历字符串
s
的每个字符,时间复杂度为 O(n)。 - 内层循环遍历所有可能的余数
j
,时间复杂度为 O(p)。 - 对于每个字符
s[i]
,如果是?
,则需要枚举所有可能的数字0
到9
,时间复杂度为 O(10)。 - 因此,总的时间复杂度为 O(n×p×10)=O(n×p)。
- 外层循环遍历字符串
-
空间复杂度:
- 使用了一个二维数组
f
,大小为N \times M
,其中N
是字符串长度,M
是p
的最大值。 - 因此,空间复杂度为 O(n×p)。
- 使用了一个二维数组
知识点扩展
-
动态规划:
- 动态规划是一种通过将问题分解为子问题并记录每个子问题的解来避免重复计算的算法。
- 在这道题目中,我们使用动态规划来记录每个子问题的解,即前
i
个字符组成的数字模p
余数为j
的方案数。
-
模运算:
- 模运算在数论中非常重要,特别是在处理大数问题时。
- 在这道题目中,我们需要计算替换后的字符串表示的十进制整数模
p
的余数,并使用模运算来避免溢出。
-
字符串处理:
- 字符串处理是算法题中常见的问题类型,涉及字符串的遍历、替换、拼接等操作。
- 在这道题目中,我们需要遍历字符串
s
,并根据字符是?
还是数字来进行不同的处理。
代码实现
def solution(s:str,p:int) -> int:
N = 104
M = 10004
mod = int(1e9 + 7)
def ADD(x, y):
x += y
if x >= mod:
x -= mod
return x
f = [[0] * M for _ in range(N)]
n = len(s)
f[0][0] = 1
for i in range(n):
ch = s[i]
for j in range(p):
if f[i][j] == 0:
continue
if ch == '?':
for x in range(10):
f[i + 1][(j * 10 + x) % p] = ADD(f[i + 1][(j * 10 + x) % p], f[i][j])
else:
f[i + 1][(j * 10 + (ord(ch) - ord('0'))) % p] = ADD(f[i + 1][(j * 10 + (ord(ch) - ord('0'))) % p], f[i][j])
return f[n][0]
if __name__ == '__main__':
print(solution(s = "??",p = 1) == 100)
print(solution(s = "????1",p = 12) == 0)
print(solution(s = "1??2",p = 3) == 34)