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

43 阅读4分钟

问题描述

小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

解题思路

  1. 问题分解

    • 我们需要将字符串中的每个 ? 替换成一个数字字符(0-9)。
    • 替换后的字符串表示的十进制整数必须是 p 的倍数。
    • 由于方案数可能非常大,需要对最终的结果取模 10^9 + 7
  2. 数据结构选择

    • 使用动态规划(DP)来解决这个问题。
    • 定义一个状态 dp[i][r],表示前 i 个字符组成的字符串,其对 p 取模后的余数为 r 的方案数。
  3. 算法步骤

    • 初始化 dp[0][0] = 1,表示空字符串对 p 取模为 0 的方案数为 1

    • 遍历字符串 s,对于每个字符 s[i]

      • 如果是数字字符,直接更新 dp 状态。
      • 如果是 ?,则尝试将其替换为 0-9,并更新 dp 状态。
    • 最终答案为 dp[len(s)][0],表示整个字符串对 p 取模为 0 的方案数。

难点分析

  1. 动态规划状态转移

    • 如何正确地更新 dp 状态是一个难点。特别是当字符是 ? 时,需要考虑所有可能的替换值(0-9),并计算新的余数。
    • 状态转移方程需要仔细设计,确保每一步的更新都是正确的。
  2. 取模运算

    • 由于方案数可能非常大,需要在每一步更新 dp 状态时进行取模运算,以避免整数溢出。
    • 取模运算的正确性需要保证,特别是在状态转移过程中。
  3. 边界条件处理

    • 初始状态 dp[0][0] = 1 的设置是关键,表示空字符串对 p 取模为 0 的方案数为 1
    • 对于字符串中的第一个字符,需要特别处理,确保状态转移的正确性。
  4. 复杂度分析

    • 动态规划的时间复杂度为 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 数组。
    • 这个复杂度在合理范围内,能够处理中等规模的问题