问题描述
小C拿到了一个由数字字符和 ? 组成的字符串,她的目标是将所有的 ? 替换成数字字符,使得替换后的字符串表示的十进制整数成为正整数 pp 的倍数。由于方案数可能非常大,需要对最终的结果取模 109+7109+7。
测试样例
样例1:
输入:
s = "??",p = 1
输出:100
样例2:
输入:
s = "????1",p = 12
输出:0
样例3:
输入:
s = "1??2",p = 3
输出:34
分析
题目理解
题目要求我们将一个由数字字符和 ? 组成的字符串中的所有 ? 替换成数字字符(0-9),使得替换后的字符串表示的十进制整数成为正整数 p 的倍数。由于方案数可能非常大,需要对最终的结果取模 10^9 + 7。
解题思路
-
问题分解:
- 我们需要将字符串中的每个
?替换成一个数字字符(0-9)。 - 替换后的字符串表示的十进制整数必须是
p的倍数。 - 由于方案数可能非常大,需要对最终的结果取模
10^9 + 7。
- 我们需要将字符串中的每个
-
数据结构选择:
- 使用动态规划(DP)来解决这个问题。
- 定义一个状态
dp[i][r],表示前i个字符组成的字符串,其对p取模后的余数为r的方案数。
-
算法步骤:
-
初始化
dp[0][0] = 1,表示空字符串对p取模为0的方案数为1。 -
遍历字符串
s,对于每个字符s[i]:- 如果是数字字符,直接更新
dp状态。 - 如果是
?,则尝试将其替换为0-9,并更新dp状态。
- 如果是数字字符,直接更新
-
最终答案为
dp[len(s)][0],表示整个字符串对p取模为0的方案数。
-
难点分析
-
动态规划状态转移:
- 如何正确地更新
dp状态是一个难点。特别是当字符是?时,需要考虑所有可能的替换值(0-9),并计算新的余数。 - 状态转移方程需要仔细设计,确保每一步的更新都是正确的。
- 如何正确地更新
-
取模运算:
- 由于方案数可能非常大,需要在每一步更新
dp状态时进行取模运算,以避免整数溢出。 - 取模运算的正确性需要保证,特别是在状态转移过程中。
- 由于方案数可能非常大,需要在每一步更新
-
边界条件处理:
- 初始状态
dp[0][0] = 1的设置是关键,表示空字符串对p取模为0的方案数为1。 - 对于字符串中的第一个字符,需要特别处理,确保状态转移的正确性。
- 初始状态
-
复杂度分析:
- 动态规划的时间复杂度为
O(n * p * 10),其中n是字符串的长度,p是给定的正整数,10是?可以替换的数字范围。 - 空间复杂度为
O(n * p),需要存储dp数组。
- 动态规划的时间复杂度为
代码题解
def solution(s: str, p: int) -> int:
MOD = 10**9 + 7
n = len(s)
# 初始化 dp 数组
dp = [[0] * p for _ in range(n + 1)]
dp[0][0] = 1
for i in range(n):
for r in range(p):
if dp[i][r] > 0:
if s[i] == '?':
# 尝试将 '?' 替换为 0-9
for digit in range(10):
# 更新 dp 状态
new_r = (r * 10 + digit) % p
dp[i + 1][new_r] = (dp[i + 1][new_r] + dp[i][r]) % MOD
else:
# 直接更新 dp 状态
digit = int(s[i])
new_r = (r * 10 + digit) % p
dp[i + 1][new_r] = (dp[i + 1][new_r] + dp[i][r]) % MOD
# 最终答案
return dp[n][0]
if __name__ == '__main__':
print(solution("??", 1) == 100)
print(solution("????1", 12) == 0)
print(solution("1??2", 3) == 34)
-
动态规划状态定义:
dp[i][r]表示前i个字符组成的字符串,其对p取模后的余数为r的方案数。- 初始状态
dp[0][0] = 1表示空字符串对p取模为0的方案数为1。 - 这个状态定义是合理的,因为它涵盖了所有可能的状态。
-
状态转移方程:
- 对于每个字符
s[i],如果是?,则尝试将其替换为0-9,并更新dp状态。 - 如果是数字字符,直接更新
dp状态。 - 状态转移方程
new_r = (r * 10 + digit) % p是正确的,因为它正确地计算了新的余数。 - 更新
dp状态时,使用dp[i + 1][new_r] = (dp[i + 1][new_r] + dp[i][r]) % MOD确保了每一步的更新都是正确的,并且避免了整数溢出。
- 对于每个字符
-
边界条件处理:
- 初始状态
dp[0][0] = 1是正确的,表示空字符串对p取模为0的方案数为1。 - 对于字符串中的第一个字符,状态转移方程也能正确处理。
- 初始状态
-
取模运算:
- 在每一步更新
dp状态时,都进行了取模运算% MOD,确保了结果不会溢出。 - 取模运算的正确性得到了保证。
- 在每一步更新
-
时间复杂度和空间复杂度:
- 时间复杂度为
O(n * p * 10),其中n是字符串的长度,p是给定的正整数,10是?可以替换的数字范围。 - 空间复杂度为
O(n * p),需要存储dp数组。 - 这个复杂度在合理范围内,能够处理中等规模的问题
- 时间复杂度为