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

70 阅读4分钟

问题的链接

小U的问号替换问题

问题描述

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


测试样例

样例1:

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

样例2:

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

样例3:

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

算法分析

这个问题可以当作是是一个典型的回溯(Backtracking)问题。其核心在于通过递归尝试所有可能的数字填充问号的位置,并检查生成的数字是否满足给定条件(即生成的数字能否被给定的正整数 pp 整除)。

由于可能的组合数量巨大,因此在计算过程中使用了模运算以防止结果溢出。

超大数可以下划线,这样更加清晰,表示的值是一样的。1000000007可以表示成1_000_000_007。

对于每个问号位置,我们有10种选择(从0到9),因此可以采用递归的方式遍历所有可能的选择。每做出一次选择后,检查当前形成的数字是否满足条件,如果不满足,则撤销这次选择,继续尝试其他选择(这就是回溯的过程)。

由于结果需要对 1_000_000_007取模,所以在每次累加结果时都要进行取模操作,确保不会发生数值溢出。

countValidNumbers 方法

如果已经处理完字符串中的所有字符(即 index == chars.length),则检查当前形成的数字是否能被 pp 整除,如果能,则返回1,否则返回0。

如果当前字符不是问号,则直接将其转换为数字并加入当前正在构建的数字中,然后递归处理下一个字符。

如果当前字符是问号,则尝试用0到9中的每一个数字替换它,并递归地处理下一个字符。每次递归调用返回的结果都需要累加起来,同时注意对结果进行取模操作。

代码实现


public class Main {
    private static final int MOD = 1_000_000_007;

    public static int solution(String s, int p) {
        return countValidNumbers(s.toCharArray(), p, 0, 0);
    }

    private static int countValidNumbers(char[] chars, int p, int index, long currentNumber) {
        if (index == chars.length) {
            return currentNumber % p == 0 ? 1 : 0;
        }

        if (chars[index] != '?') {
            currentNumber = (currentNumber * 10 + (chars[index] - '0')) % p;
            return countValidNumbers(chars, p, index + 1, currentNumber);
        } else {
            int count = 0;
            for (char digit = '0'; digit <= '9'; digit++) {
                long newNumber = (currentNumber * 10 + (digit - '0')) % p;
                count = (count + countValidNumbers(chars, p, index + 1, newNumber)) % MOD;
            }
            return count;
        }
    }

    public static void main(String[] args) {
        System.out.println(solution("??", 1) == 100); // true
        System.out.println(solution("????1", 12) == 0); // true
        System.out.println(solution("1??2", 3) == 34); // true
    }
}







时间复杂度和空间复杂度分析

时间复杂度: O(10kn)O(10^k⋅n)

空间复杂度: 深度, O(n)O(n)

AI优化

在这里我们可以用AI看看能不能优化,让豆包MarsCode AI 给一下有没有优化的可能性

图片.png

优化代码

public class Main {
    private static final int MOD = 1_000_000_007;

    public static int solution(String s, int p) {
        int n = s.length();
        // dp[i][j] 表示处理到第i个字符,余数为j的方案数
        int[][] dp = new int[n + 1][p];
        // 初始化边界情况,处理到第0个字符(空串),余数为0的方案数为1
        dp[0][0] = 1;

        for (int i = 0; i < n; i++) {
            for (int prevRemainder = 0; prevRemainder < p; prevRemainder++) {
                if (dp[i][prevRemainder] == 0) {
                    continue;
                }
                if (s.charAt(i)!= '?') {
                    int digit = s.charAt(i) - '0';
                    int newRemainder = (prevRemainder * 10 + digit) % p;
                    dp[i + 1][newRemainder] = (dp[i + 1][newRemainder] + dp[i][prevRemainder]) % MOD;
                } else {
                    for (int digit = 0; digit < 10; digit++) {
                        int newRemainder = (prevRemainder * 10 + digit) % p;
                        dp[i + 1][newRemainder] = (dp[i + 1][newRemainder] + dp[i][prevRemainder]) % MOD;
                    }
                }
            }
        }

        return dp[n][0];
    }

    public static void main(String[] args) {
        System.out.println(solution("??", 1) == 100); // true
        System.out.println(solution("????1", 12) == 0); // true
        System.out.println(solution("1??2", 3) == 34); // true
    }
}

优化代码分析

  • 外层循环

    • for (int i = 0; i < n; i++) 遍历字符串 s 的每个字符,时间复杂度为 ( O(n) )。
  • 内层循环

    • for (int prevRemainder = 0; prevRemainder < p; prevRemainder++) 遍历所有可能的余数,时间复杂度为 ( O(p) )。
  • 处理 ? 的情况

    • 如果当前字符是 ?,则需要遍历 09 的所有可能数字,时间复杂度为 ( O(10) )。

优化代码时间复杂度和空间复杂度分析

  • 时间复杂度:O(n⋅p⋅10)
  • 空间复杂度:O(n⋅p)