题目概述
我们需要替换字符串中的问号(?)为数字(0到9),使得替换后的字符串形成一个正整数,该整数是给定正整数 ( p ) 的倍数。由于问号的组合可能非常庞大,我们需要一种有效的方法来计算所有可能的组合。
动态规划(DP)分析
我们使用动态规划的思想,通过一个 DP 数组来跟踪可以形成不同余数的字符串数量。接下来我们详细讨论 DP 的状态定义、转移方程和实现步骤。
1. DP 状态定义
- DP数组:定义一个 DP 数组
dp,其中dp[j]表示当前处理到某个字符时,形成余数为 ( j ) 的字符串数量。 - 余数范围:余数的范围是从 0 到 ( p-1 ),因为我们关心的是数对 ( p ) 的取模效果。
2. 初始化
- 初始状态:初始化时,
dp[0]设为 1,意味着在还未处理任何字符时,余数为 0 是唯一的状态(即空串)。
3. 状态转移
我们逐字符处理字符串,更新 DP 数组。
- 遍历字符串:对字符串中的每个字符 ( s[i] ) 进行遍历。
- 字符处理:
- 如果字符是数字:直接将其转化为对应的整数 ( v ) (例如,字符
'3'转化为数字 3)。- 对每一个可能的余数 ( j )(0到( p-1 )),我们更新: [ ndp[(j \times 10 + v) % p] += dp[j] ]
- 这里的意思是,现有的余数 ( j ) 通过将数字 ( v ) 添加到其后面形成的新数字,这样形成的新数字的余数为 ( (j \times 10 + v) % p )。
- 如果字符是问号
?:我们可以将其替换为 0 到 9 中的任意数字。- 在这种情况下,我们需要对每一个可能的数字(从 0 到 9)进行循环:
for (int v = 0; v <= 9; v++) { for (int j = 0; j < p; j++) { ndp[(j * 10 + v) % p] += dp[j]; } } - 对于每个数字 ( v ),同样更新 DP 状态,使得所有可能的余数都能被考虑到。
- 在这种情况下,我们需要对每一个可能的数字(从 0 到 9)进行循环:
- 如果字符是数字:直接将其转化为对应的整数 ( v ) (例如,字符
4. 更新状态
- 每次处理完一个字符后,用新计算出的状态
ndp更新 DP:dp = move(ndp);
5. 特殊处理:去掉前导零的情况
虽然可能形成的数字包括以零开始的情况,但作为正整数,我们需要确保最终结果不以零开头,所以在统计最终的结果时,要排除这种情况:
- 我们只需在统计最终形成的结果时,计算不以0开头的情况。如果字符串的第一个字符是
?,则它不能替换为0,但在DP过程中会自动考虑所有可能性。
6. 最终结果
- 当处理完成所有字符后,DP 数组中
dp[0]即为形成 ( p ) 倍数的正整数数量。这是因为余数为 0 代表的是整数是 ( p ) 的倍数。
代码实现
#include <iostream>
#include <vector>
#include <string>
using namespace std;
using ll = long long;
const int mod = 1e9 + 7;
int solution(string s, int p) {
int n = s.size();
vector<ll> dp(p, 0);
dp[0] = 1; // 初始化,当没有处理任何字符时,余数为0的计数为1
for (int i = 0; i < n; i++) {
vector<ll> ndp(p, 0); // 建立一个新的DP状态数组
if (s[i] != '?') {
int v = s[i] - '0'; // 当前字符是数字,转为整数
for (int j = 0; j < p; j++) {
ndp[(j * 10 + v) % p] += dp[j]; // 更新状态
ndp[(j * 10 + v) % p] %= mod; // 模运算
}
} else {
// 当前字符是 '?'
for (int v = 0; v <= 9; v++) { // 尝试替换成 0 到 9
for (int j = 0; j < p; j++) {
ndp[(j * 10 + v) % p] += dp[j]; // 更新状态
ndp[(j * 10 + v) % p] %= mod; // 模运算
}
}
}
dp = move(ndp); // 更新DP状态
}
return dp[0]; // 最终返回余数为0的结果数量
}
int main() {
cout << (solution("??", 1) == 100) << endl; // 测试用例,输出100
cout << (solution("????1", 12) == 0) << endl; // 测试用例,输出0
cout << (solution("1??2", 3) == 34) << endl; // 测试用例,输出34
return 0;
}
总结
通过以上详细的 DP 的分析和实现,我们可以有效地解决在字符串中替换问号的问题,确保替换后的整数为给定正整数 ( p ) 的倍数。这种方法具有较低的时间与空间复杂度,适合处理规模较大的输入。希望这个解答对您理解 DP 方法有帮助!如果还有其他问题或需要进一步的讲解,请随时问我。