问题描述
小C拿到了一个由数字字符和 ? 组成的字符串,她的目标是将所有的 ? 替换成数字字符,使得替换后的字符串表示的十进制整数成为正整数 pp 的倍数。由于方案数可能非常大,需要对最终的结果取模 109+7109+7。
测试样例
样例1:
输入:
s = "??",p = 1
输出:100
样例2:
输入:
s = "????1",p = 12
输出:0
样例3:
输入:
s = "1??2",p = 3
输出:34问题理解
你有一个由数字字符和 ? 组成的字符串 s,目标是将其中的所有 ? 替换成数字字符,使得替换后的字符串表示的十进制整数是给定正整数 p 的倍数。由于可能的替换方案数可能非常大,最终结果需要对 10^9 + 7 取模。
数据结构选择
- 字符串处理:由于我们需要替换字符串中的
?,字符串处理是不可避免的。 - 动态规划(DP) :考虑到可能的替换方案数非常大,使用动态规划来记录不同状态下的有效方案数是一个不错的选择。
算法步骤
-
初始化:定义一个动态规划数组
dp,其中dp[i][r]表示前i个字符组成的字符串,其对p取模的余数为r的方案数。 -
状态转移:
- 如果当前字符是数字,直接更新
dp数组。 - 如果当前字符是
?,则需要考虑所有可能的数字(0-9),并更新dp数组。
- 如果当前字符是数字,直接更新
-
取模操作:在每次更新
dp数组时,都需要对10^9 + 7取模,以防止数值溢出。 -
最终结果:最终结果是
dp[len(s)][0],即整个字符串对p取模为 0 的方案数。
总结
通过动态规划,我们可以有效地计算出所有可能的替换方案数,并确保结果在合理范围内。
MOD = 10**9 + 7
n = len(s)
# 初始化 dp 数组
dp = [[0] * p for _ in range(n + 1)]
dp[0][0] = 1 # 空字符串对任何数取模都是 0
# 遍历字符串 s
for i in range(1, n + 1):
for r in range(p):
if s[i - 1] == '?':
# 如果是 '?',考虑所有可能的数字
for digit in range(10):
new_r = (r * 10 + digit) % p
dp[i][new_r] = (dp[i][new_r] + dp[i - 1][r]) % MOD
else:
# 如果是数字,直接更新
digit = int(s[i - 1])
new_r = (r * 10 + digit) % p
dp[i][new_r] = (dp[i][new_r] + dp[i - 1][r]) % MOD
# 最终结果是 dp[n][0]
return dp[n][0]
if __name__ == '__main__':
print(solution("??", 1) == 100)
print(solution("????1", 12) == 0)
print(solution("1??2", 3) == 34)
-
初始化:
- 我们初始化了一个二维数组
dp,其中dp[i][r]代表在考虑前i个字符时,模p的结果为r的方式数目。 - 初始状态
dp[0][0] = 1,表示空字符串的模数是 0。
- 我们初始化了一个二维数组
-
状态转移:
-
对于每个字符
s[i-1],我们需要根据其值(数字或'?')来更新dp数组:- 如果是数字,直接用该数字更新当前模数。
- 如果是
'?',则尝试用 0 到 9 中的每个数字更新模数。
-
-
最终输出:
- 最终的答案是
dp[n][0],即考虑所有字符后,模p为 0 的方式数。
- 最终的答案是
实验结果
-
测试用例 1:
- 输入:
s = "??", p = 1 - 解释:所有的两位数
??都能被替换成从00到99的数字,而每个数字对 1 取模的结果总是 0。因此,输出应为 100。 - 输出:
100,与预期一致。
- 输入:
-
测试用例 2:
- 输入:
s = "????1", p = 12 - 解释:任何替换字符后,最后的数字模 12 都不可能等于 0,因此输出应为 0。
- 输出:
0,与预期一致。
- 输入:
-
测试用例 3:
- 输入:
s = "1??2", p = 3 - 解释:通过替换问号,考虑所有可能的数字,最终得到对 3 取模为 0 的数字的个数。结果为 34。
- 输出:
34,与预期一致。
- 输入:
实验总结
- 动态规划 是解决此类问题的关键,能够有效地管理和更新状态,避免了暴力穷举的高时间复杂度。
- 边界条件 处理得当,特别是空字符串的模数。
- 时间复杂度:
O(n * p * 10),其中n是字符串的长度,p是模数,10是因为每个'?'可能替换成 0 到 9 之间的任意一个数字。