Leetcode 第 356 场周赛 Problem D 2801. 统计范围内的步进数字数目(数位 DP,递推写法)

  • Leetcode 第 356 场周赛 Problem D 2801. 统计范围内的步进数字数目(数位 DP,递推写法)
  • 题目
    • 给你两个正整数 low 和 high ,都用字符串表示,请你统计闭区间 [low, high] 内的 步进数字 数目。
    • 如果一个整数相邻数位之间差的绝对值都 恰好 是 1 ,那么这个数字被称为 步进数字 。
    • 请你返回一个整数,表示闭区间 [low, high] 之间步进数字的数目。
    • 由于答案可能很大,请你将它对 10 ^ 9 + 7 取余 后返回。
    • 注意:步进数字不能有前导 0 。
    • 1 <= int(low) <= int(high) < 10 ^ 100
    • 1 <= low.length, high.length <= 100
    • low 和 high 只包含数字。
    • low 和 high 都不含前导 0 。
  • 解法
    • 首先将题目转化为 [1,high] - [1,low-1] 有多少个步进数字,数字为 0-9 直接返回数值,否则拆分成 [0, 10 ^ (len-1)) + [10 ^ (len-1), numStr],不用处理前导零
    • 前者
      • dp[i][j] 代表前 i 位、最后一位数字为 j 时,步进数字的个数,
      • 转移方程:
        • j=0 时、dp[i][j]=dp[i-1][j+1],
        • j=9 时、dp[i][j] = dp[i-1][j-1],
        • 否则 dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1]
      • dp[i][j] 的总和就是答案
    • 后者
      • dp[i][j][k] 代表前 i 位、第一位非 0、最后一位为 j 且前面 k=(0-小于 1-等于 numStr 前 i 位)有多少步进数字,
      • 转移方程:
      • dp[i][j][1]=dp[i-1][j-1][1]+dp[i-1][j+1][1],注意 0<=j<=9、填写的 j 等于 numStr[i-1],
      • dp[i][j][0]=dp[i-1][j-1][0]+dp[i-1][j+1][0]+dp[i-1][j-1][1]+dp[i-1][j+1][1],注意 0<=j<=9、dp[i-1][j-1][1]+dp[i-1][j+1][1] 需要填写的 j 小于 numStr[i-1]
      • dp[len][j][k] 的总和就是答案
    • 最后结果可能为负数,需要使用 (x % mod + mod) % mod
    • len 位 high 的长度,10 为数字 0-9 总和,时间复杂度:O(10 * len),空间复杂度:O(10 * len)
  • 代码
/**
     * 首先将题目转化为 [1,high] - [1,low-1] 有多少个步进数字,数字为 0-9 直接返回数值,否则拆分成 [0, 10^(len-1)) + [10^(len-1), numStr],不用处理前导零
     * 前者
     *     dp[i][j] 代表前 i 位、最后一位数字为 j 时,步进数字的个数,
     *     转移方程:j=0 时、dp[i][j]=dp[i-1][j+1],j=9 时、dp[i][j] = dp[i-1][j-1],
     *     否则 dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1]
     *     dp[i][j] 的总和就是答案
     * 后者
     *     dp[i][j][k] 代表前 i 位、第一位非 0、最后一位为 j 且前面 k=(0-小于 1-等于 numStr 前 i 位)有多少步进数字,
     *     转移方程:dp[i][j][1]=dp[i-1][j-1][1]+dp[i-1][j+1][1],注意 0<=j<=9、填写的 j 等于 numStr[i-1]
     *     dp[i][j][0]=dp[i-1][j-1][0]+dp[i-1][j+1][0]+dp[i-1][j-1][1]+dp[i-1][j+1][1],注意 0<=j<=9、dp[i-1][j-1][1]+dp[i-1][j+1][1] 需要填写的 j 小于 numStr[i-1]
     *     dp[len][j][k] 的总和就是答案
     * 最后结果可能为负数,需要使用 (x % mod + mod) % mod
     * len 位 high 的长度,10 为数字 0-9 总和,时间复杂度:O(10*len),空间复杂度:O(10*len)
     */
    public int solution(String low, String high) {
        int mod = 1_000_000_007;

        // [1,high] 有多少个步进数字
        int countHigh = doCountSteppingNumbers(high, mod);

        // [1,low-1] 有多少个步进数字
        String lowReduce = (new BigInteger(low)).subtract(BigInteger.ONE).toString();
        int countLowReduce = doCountSteppingNumbers(lowReduce, mod);

//        System.out.println(countHigh + " : " + countLowReduce);

        // 结果可能为负数,需要使用 (x % mod + mod) % mod
        return ((countHigh - countLowReduce) % mod + mod) % mod;
    }

    /**
     * 返回 [1,numStr] 有多少个步进数字
     */
    private int doCountSteppingNumbers(String numStr, int mod) {
        // 数字为 0-9 直接返回数值
        int len = numStr.length();
        if (len == 1) {
            return numStr.charAt(0) - '0';
        }

        // 返回 [0, 10^(len-1)) 有多少步进数字
        int count = doCountCompleteNumbers(len - 1, mod);
//        System.out.println("count:" + count);

        // 返回 [10^(len-1), numStr] 有多少步进数字
        count += doCountLastCompleteNumbers(len, numStr, mod);
//        System.out.println("count:" + count);

        return count % mod;
    }

    /**
     * 返回 [0, 10^(len-1)) 有多少步进数字
     */
    private int doCountCompleteNumbers(int len, int mod) {
        // dp[i][j] 代表前 i 位、最后一位数字为 j 时,步进数字的个数
        int[][] dp = new int[len + 1][10];

        Arrays.fill(dp[1], 1);
        dp[1][0] = 0;

        // 转移方程:j=0 时、dp[i][j]=dp[i-1][j+1],j=9 时、dp[i][j] = dp[i-1][j-1],否则 dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1]
        int res = 9;
        for (int i = 2; i < len + 1; i++) {
            for (int j = 0; j < 10; j++) {
                dp[i][j] = (j == 0 ? 0 : dp[i - 1][j - 1]) + (j == 9 ? 0 : dp[i - 1][j + 1]);
                dp[i][j] %= mod;

                res += dp[i][j];
                res %= mod;
            }
        }

        return res;
    }

    private int doCountLastCompleteNumbers(int len, String numStr, int mod) {
        // dp[i][j][k] 代表前 i 位、第一位非 0、最后一位为 j 且前面 k=(0-小于 1-等于 numStr 前 i 位)有多少步进数字
        int[][][] dp = new int[len + 1][10][2];

        // 初始化第一位非 0
        for (int j = 1; j < 10; j++) {
            // 到当前比 numStr 小就初始化
            dp[1][j][0] = (j >= numStr.charAt(0) - '0') ? 0 : 1;
            // 到当前与 numStr 相等就初始化
            dp[1][j][1] = (j != numStr.charAt(0) - '0') ? 0 : 1;
        }

        /**
         * 转移方程:
         * dp[i][j][1]=dp[i-1][j-1][1]+dp[i-1][j+1][1],注意 0<=j<=9、填写的 j 等于 numStr[i-1]
         * dp[i][j][0]=dp[i-1][j-1][0]+dp[i-1][j+1][0]+dp[i-1][j-1][1]+dp[i-1][j+1][1],注意 0<=j<=9、dp[i-1][j-1][1]+dp[i-1][j+1][1] 需要填写的 j 小于 numStr[i-1]
         */
        int res = 0;
        for (int i = 2; i < len + 1; i++) {
            for (int j = 0; j < 10; j++) {
                dp[i][j][1] = ((j == 0 || j != numStr.charAt(i - 1) - '0') ? 0 : dp[i - 1][j - 1][1])
                        + ((j == 9 || j != numStr.charAt(i - 1) - '0') ? 0 : dp[i - 1][j + 1][1]);
                dp[i][j][1] %= mod;


                dp[i][j][0] = ((j == 0) ? 0 : dp[i - 1][j - 1][0]) + ((j == 9) ? 0 : dp[i - 1][j + 1][0])
                        + ((j == 0 || j >= numStr.charAt(i - 1) - '0') ? 0 : dp[i - 1][j - 1][1])
                        + ((j == 9 || j >= numStr.charAt(i - 1) - '0') ? 0 : dp[i - 1][j + 1][1]);

                dp[i][j][0] %= mod;
            }
//            System.out.println(Arrays.toString(dp[i]));
        }

        // 结果为填写 len 位后总和
        for (int j = 0; j < 10; j++) {
            res += dp[len][j][0];
            res %= mod;

            res += dp[len][j][1];
            res %= mod;
        }

        return res;
    }

你可能感兴趣的:(LeetCode,leetcode,算法,动态规划)