小U的问号替换问题 | 豆包MarsCode AI刷题

73 阅读4分钟

问题分析与解题思路

这个问题的核心是通过动态规划(DP)解决字符串中?替换为数字后的余数计算问题。具体来说,我们需要逐个处理字符串中的字符,并计算替换后的数字是否能被一个给定的数字 p 整除。通过动态规划,我们能够有效地枚举所有可能的替换方式,并找到符合条件的方案。

动态规划状态设计

  1. 状态定义

    • 我们定义 dp[i][r] 表示考虑前 i 个字符,并且计算得到的余数为 r 的方案数。i 表示当前处理的字符位置,r 表示余数的值,r 范围是 [0, p-1]
  2. 状态转移

    • 对于字符串中的每一个字符,我们根据它的值(如果是 ?,则可以是 0 到 9 的任何一个数字),进行状态转移:
      • 如果当前字符是数字字符 d,则我们直接用上一状态的结果进行更新,转移公式为:
        dp[i][(r×10+d)%p]+=dp[i1][r]dp[i][(r \times 10 + d) \% p] += dp[i-1][r]
      • 如果当前字符是 ?,我们枚举 ? 可以替换成的每一个数字 d,然后进行状态转移。
  3. 初始化

    • 初始时,dp[0][0] = 1,表示没有字符时余数为 0 的方案数为 1(即没有数字时,默认余数为 0)。
  4. 滚动数组优化

    • 由于我们每次只需要上一行的状态,因此我们可以使用滚动数组来优化空间复杂度,将 dp 数组从二维降为一维。
  5. 输出结果

    • 最终,我们关心的是所有替换后余数为 0 的方案数,即 dp[n][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

代码解释

  1. 变量初始化

    • MOD 是结果的取模常数,避免结果过大。
    • dp 数组是动态规划的核心,它存储余数为 [0, p-1] 的方案数。dp[r] 表示当前状态下,余数为 r 的方案数。
  2. 字符遍历

    • 遍历字符串中的每个字符 char
      • 如果 char?,我们枚举它可以替换成的数字 09,并更新 next_dp
      • 如果 char 是一个数字,我们直接用这个数字进行余数计算,并更新 next_dp
  3. 滚动数组

    • 在每次更新状态时,dp 会被更新为 next_dp,这是通过滚动数组的方式来实现的,使得空间复杂度从 O(n*p) 降到 O(p)
  4. 结果输出

    • 最后返回 dp[0],即余数为 0 的方案数。

复杂度分析

  1. 时间复杂度

    • 每次处理一个字符时,我们需要枚举每个余数 r(共 p 种)和数字 d(共 10 种),因此每次处理字符的复杂度为 O(p * 10)
    • 总共要处理 n 个字符,因此总时间复杂度为 O(n * p * 10),即 O(n * p),其中 n 是字符串的长度,p 是给定的整数。
  2. 空间复杂度

    • 我们使用了滚动数组来保存状态,因此空间复杂度为 O(p),即只需要保存余数为 [0, p-1] 的状态。

测试样例

  1. 测试样例 1

    • 输入:s = "??", p = 1
    • 解释:每个 ? 可以替换为 0 到 9 的任意数字,总共有 100 种方案,每种方案都能被 1 整除,因此输出 100
  2. 测试样例 2

    • 输入:s = "????1", p = 12
    • 解释:由于最后一个字符是 1,任何替换后的数字都无法被 12 整除,因此输出 0
  3. 测试样例 3

    • 输入:s = "1??2", p = 3
    • 解释:通过替换 ?,可以得到 34 种符合条件的方案,因此输出 34

总结

通过动态规划和滚动数组优化,我们可以高效地解决这个问题。空间复杂度优化到 O(p),时间复杂度为 O(n * p),适用于较大规模的输入。在实际应用中,这种方法能够处理较复杂的字符串匹配问题,并在时间和空间上保持较好的性能。