问题分析与解题思路
这个问题的核心是通过动态规划(DP)解决字符串中?替换为数字后的余数计算问题。具体来说,我们需要逐个处理字符串中的字符,并计算替换后的数字是否能被一个给定的数字 p 整除。通过动态规划,我们能够有效地枚举所有可能的替换方式,并找到符合条件的方案。
动态规划状态设计
-
状态定义:
- 我们定义
dp[i][r]表示考虑前i个字符,并且计算得到的余数为r的方案数。i表示当前处理的字符位置,r表示余数的值,r范围是[0, p-1]。
- 我们定义
-
状态转移:
- 对于字符串中的每一个字符,我们根据它的值(如果是
?,则可以是 0 到 9 的任何一个数字),进行状态转移:- 如果当前字符是数字字符
d,则我们直接用上一状态的结果进行更新,转移公式为: - 如果当前字符是
?,我们枚举?可以替换成的每一个数字d,然后进行状态转移。
- 如果当前字符是数字字符
- 对于字符串中的每一个字符,我们根据它的值(如果是
-
初始化:
- 初始时,
dp[0][0] = 1,表示没有字符时余数为 0 的方案数为 1(即没有数字时,默认余数为 0)。
- 初始时,
-
滚动数组优化:
- 由于我们每次只需要上一行的状态,因此我们可以使用滚动数组来优化空间复杂度,将
dp数组从二维降为一维。
- 由于我们每次只需要上一行的状态,因此我们可以使用滚动数组来优化空间复杂度,将
-
输出结果:
- 最终,我们关心的是所有替换后余数为 0 的方案数,即
dp[n][0]。
- 最终,我们关心的是所有替换后余数为 0 的方案数,即
代码实现
def solution(s: str, p: int) -> int:
MOD = 10**9 + 7 # 结果取模
n = len(s) # 字符串的长度
# dp[r]表示当前位数对应余数为 r 的方案数
dp = [0] * p
dp[0] = 1 # 初始状态,余数为0的方案数为1
for char in s:
next_dp = [0] * p # 用来存储更新后的状态
if char == '?':
# 遍历每个可能的数字 0-9
for r in range(p):
for d in range(10):
next_r = (r * 10 + d) % p
next_dp[next_r] = (next_dp[next_r] + dp[r]) % MOD
else:
# 当前字符是数字,直接进行更新
d = int(char)
for r in range(p):
next_r = (r * 10 + d) % p
next_dp[next_r] = (next_dp[next_r] + dp[r]) % MOD
dp = next_dp # 更新 dp 为 next_dp
# 返回余数为 0 的方案数
return dp[0]
# 测试样例
if __name__ == '__main__':
print(solution("??", 1)) # 输出 100
print(solution("????1", 12)) # 输出 0
print(solution("1??2", 3)) # 输出 34
代码解释
-
变量初始化:
MOD是结果的取模常数,避免结果过大。dp数组是动态规划的核心,它存储余数为[0, p-1]的方案数。dp[r]表示当前状态下,余数为r的方案数。
-
字符遍历:
- 遍历字符串中的每个字符
char:- 如果
char是?,我们枚举它可以替换成的数字0到9,并更新next_dp。 - 如果
char是一个数字,我们直接用这个数字进行余数计算,并更新next_dp。
- 如果
- 遍历字符串中的每个字符
-
滚动数组:
- 在每次更新状态时,
dp会被更新为next_dp,这是通过滚动数组的方式来实现的,使得空间复杂度从O(n*p)降到O(p)。
- 在每次更新状态时,
-
结果输出:
- 最后返回
dp[0],即余数为 0 的方案数。
- 最后返回
复杂度分析
-
时间复杂度:
- 每次处理一个字符时,我们需要枚举每个余数
r(共p种)和数字d(共 10 种),因此每次处理字符的复杂度为O(p * 10)。 - 总共要处理
n个字符,因此总时间复杂度为O(n * p * 10),即O(n * p),其中n是字符串的长度,p是给定的整数。
- 每次处理一个字符时,我们需要枚举每个余数
-
空间复杂度:
- 我们使用了滚动数组来保存状态,因此空间复杂度为
O(p),即只需要保存余数为[0, p-1]的状态。
- 我们使用了滚动数组来保存状态,因此空间复杂度为
测试样例
-
测试样例 1:
- 输入:
s = "??", p = 1 - 解释:每个
?可以替换为 0 到 9 的任意数字,总共有 100 种方案,每种方案都能被 1 整除,因此输出100。
- 输入:
-
测试样例 2:
- 输入:
s = "????1", p = 12 - 解释:由于最后一个字符是
1,任何替换后的数字都无法被 12 整除,因此输出0。
- 输入:
-
测试样例 3:
- 输入:
s = "1??2", p = 3 - 解释:通过替换
?,可以得到 34 种符合条件的方案,因此输出34。
- 输入:
总结
通过动态规划和滚动数组优化,我们可以高效地解决这个问题。空间复杂度优化到 O(p),时间复杂度为 O(n * p),适用于较大规模的输入。在实际应用中,这种方法能够处理较复杂的字符串匹配问题,并在时间和空间上保持较好的性能。