小U的问号替换问题

问题描述

小C拿到了一个由数字字符和 ? 组成的字符串,她的目标是将所有的 ? 替换成数字字符,使得替换后的字符串表示的十进制整数成为正整数 pp 的倍数。由于方案数可能非常大,需要对最终的结果取模 109+7109+7。


测试样例

样例1:

输入:s = "??",p = 1
输出:100

样例2:

输入:s = "????1",p = 12
输出:0

样例3:

输入:s = "1??2",p = 3
输出:34

C++解题

问题理解

你有一个由数字字符和 ? 组成的字符串 s,目标是将其中的所有 ? 替换成数字字符,使得替换后的字符串表示的十进制整数是正整数 p 的倍数。由于方案数可能非常大,需要对最终的结果取模 10^9 + 7

数据结构选择

  1. 字符串处理:由于我们需要处理字符串中的每个字符,并且可能需要替换 ?,因此字符串操作是必不可少的。
  2. 动态规划:考虑到我们需要计算所有可能的替换方案,并且需要判断这些方案是否满足条件,动态规划可能是一个合适的选择。

算法步骤

  1. 初始化:定义一个动态规划数组 dp,其中 dp[i][r] 表示前 i 个字符组成的字符串,其对 p 取模的结果为 r 的方案数。
  2. 状态转移
    • 如果当前字符是数字,直接更新 dp 数组。
    • 如果当前字符是 ?,则需要考虑所有可能的数字(0-9),并更新 dp 数组。
  3. 取模操作:在每次更新 dp 数组时,都需要对 10^9 + 7 取模,以防止溢出。
  4. 最终结果:最终结果是 dp[n][0],其中 n 是字符串的长度,0 表示对 p 取模的结果为 0。

总结

通过动态规划,我们可以有效地计算出所有可能的替换方案,并判断哪些方案满足条件。这个方法的时间复杂度是 O(n * p * 10),其中 n 是字符串的长度,p 是给定的正整数,10 是 ? 可以替换的数字个数。

代码实现

#include <iostream>
#include <vector>
#include <string>
using namespace std;

const int MOD = 1e9 + 7;

int solution(string s, int p) {
    int n = s.size();
    vector<vector<int>> dp(n + 1, vector<int>(p, 0));
    
    // 初始化
    dp[0][0] = 1;
    
    // 状态转移
    for (int i = 0; i < n; ++i) {
        for (int r = 0; r < p; ++r) {
            if (dp[i][r] == 0) continue;
            
            if (s[i] == '?') {
                // 如果是 '?',考虑所有可能的数字
                for (int digit = 0; digit <= 9; ++digit) {
                    int new_r = (r * 10 + digit) % p;
                    dp[i + 1][new_r] = (dp[i + 1][new_r] + dp[i][r]) % MOD;
                }
            } else {
                // 如果是数字,直接更新
                int digit = s[i] - '0';
                int new_r = (r * 10 + digit) % p;
                dp[i + 1][new_r] = (dp[i + 1][new_r] + dp[i][r]) % MOD;
            }
        }
    }
    
    // 最终结果
    return dp[n][0];
}

int main() {
    cout << (solution("??", 1) == 100) << endl;
    cout << (solution("????1", 12) == 0) << endl;
    cout << (solution("1??2", 3) == 34) << endl;
    return 0;
}

Python解题

思路说明

题目要求将字符串中的 ? 替换成数字字符,使得替换后的字符串表示的十进制整数成为正整数 p 的倍数。由于方案数可能非常大,需要对最终的结果取模 109+7。我们可以通过动态规划来解决这个问题。动态规划的核心思想是将问题分解为子问题,并记录每个子问题的解,从而避免重复计算。

解题过程

  1. 初始化动态规划数组

    • 定义一个二维数组 f,其中 f[i][j] 表示前 i 个字符组成的数字模 p 余数为 j 的方案数。
    • 初始化 f[0][0] = 1,表示空字符串模 p 余数为 0 的方案数为 1。
  2. 状态转移

    • 遍历字符串 s 的每个字符 s[i]
    • 对于每个字符 s[i],遍历所有可能的余数 j
    • 如果 s[i] 是 ?,则枚举所有可能的数字 0 到 9,并更新 f[i+1][(j * 10 + x) % p]
    • 如果 s[i] 是数字,则直接计算 f[i+1][(j * 10 + (ord(ch) - ord('0'))) % p]
  3. 结果输出

    • 最终结果为 f[n][0],即前 n 个字符组成的数字模 p 余数为 0 的方案数。

复杂度分析

  • 时间复杂度

    • 外层循环遍历字符串 s 的每个字符,时间复杂度为 O(n)。
    • 内层循环遍历所有可能的余数 j,时间复杂度为 O(p)。
    • 对于每个字符 s[i],如果是 ?,则需要枚举所有可能的数字 0 到 9,时间复杂度为 O(10)。
    • 因此,总的时间复杂度为 O(n×p×10)=O(n×p)。
  • 空间复杂度

    • 使用了一个二维数组 f,大小为 N \times M,其中 N 是字符串长度,M 是 p 的最大值。
    • 因此,空间复杂度为 O(n×p)。

知识点扩展

  • 动态规划

    • 动态规划是一种通过将问题分解为子问题并记录每个子问题的解来避免重复计算的算法。
    • 在这道题目中,我们使用动态规划来记录每个子问题的解,即前 i 个字符组成的数字模 p 余数为 j 的方案数。
  • 模运算

    • 模运算在数论中非常重要,特别是在处理大数问题时。
    • 在这道题目中,我们需要计算替换后的字符串表示的十进制整数模 p 的余数,并使用模运算来避免溢出。
  • 字符串处理

    • 字符串处理是算法题中常见的问题类型,涉及字符串的遍历、替换、拼接等操作。
    • 在这道题目中,我们需要遍历字符串 s,并根据字符是 ? 还是数字来进行不同的处理。

代码实现

def solution(s:str,p:int) -> int:
    N = 104
    M = 10004
    mod = int(1e9 + 7)

    def ADD(x, y):
        x += y
        if x >= mod:
            x -= mod
        return x

    f = [[0] * M for _ in range(N)]

    n = len(s)

    f[0][0] = 1

    for i in range(n):
        ch = s[i]
        for j in range(p):
            if f[i][j] == 0:
                continue
            if ch == '?':
                for x in range(10):
                    f[i + 1][(j * 10 + x) % p] = ADD(f[i + 1][(j * 10 + x) % p], f[i][j])
            else:
                f[i + 1][(j * 10 + (ord(ch) - ord('0'))) % p] = ADD(f[i + 1][(j * 10 + (ord(ch) - ord('0'))) % p], f[i][j])

    return f[n][0]


if __name__ == '__main__':
    print(solution(s = "??",p = 1) == 100)
    print(solution(s = "????1",p = 12) == 0)
    print(solution(s = "1??2",p = 3) == 34)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值