跳至主要內容

数位 DP

大约 5 分钟

数位 DP

题目难度
POJ2282open in new window计算 a 和 b 之间每个数字出现的次数
233. 数字 1 的个数open in new window困难
面试题 17.06. 2 出现的次数open in new window困难类似 233
$1067. 范围内的数字计数open in new window 困难类似 233
357. 统计各位数字都不同的数字个数open in new window中等可打表
600. 不含连续 1 的非负整数open in new window困难
902. 最大为 N 的数字组合open in new window困难
1012. 至少有 1 位重复的数字open in new window困难
1397. 找到所有好字符串open in new window困难结合 KMP
2376. 统计特殊整数open in new window困难同 1012

定义

233. 数字 1 的个数

import java.util.Arrays;

public class Solution233 {
    private char[] s;
    private int[][] dp;

    // 数位 DP
    public int countDigitOne(int n) {
        s = String.valueOf(n).toCharArray();
        int len = s.length;
        dp = new int[len][10];
        for (int i = 0; i < len; i++) {
            Arrays.fill(dp[i], -1);
        }
        return f(0, 0, true, false);
    }

    private int f(int i, int sum, boolean isLimit, boolean isNum) {
        if (i == s.length) {
            return isNum ? sum : 0;
        }
        // 记忆化搜索
        if (!isLimit && isNum && dp[i][sum] >= 0) {
            return dp[i][sum];
        }
        int res = 0;
        if (!isNum) {
            // 可以跳过当前数位
            res = f(i + 1, sum, false, false);
        }
        int up = isLimit ? s[i] - '0' : 9;
        for (int d = isNum ? 0 : 1; d <= up; d++) {
            res += f(i + 1, sum + (d == 1 ? 1 : 0), isLimit && d == up, true);
        }
        if (!isLimit && isNum) {
            dp[i][sum] = res;
        }
        return res;
    }
}
































 







面试题 17.06. 2 出现的次数

import java.util.Arrays;

public class SolutionI1706 {
    private char[] s;
    private int[][] dp;

    public int numberOf2sInRange(int n) {
        s = String.valueOf(n).toCharArray();
        int len = s.length;
        dp = new int[len][10];
        for (int i = 0; i < len; i++) {
            Arrays.fill(dp[i], -1);
        }
        return f(0, 0, true, false);
    }

    private int f(int i, int sum, boolean isLimit, boolean isNum) {
        if (i == s.length) {
            return isNum ? sum : 0;
        }
        // 记忆化搜索
        if (!isLimit && isNum && dp[i][sum] >= 0) {
            return dp[i][sum];
        }
        int res = 0;
        if (!isNum) {
            // 可以跳过当前数位
            res = f(i + 1, sum, false, false);
        }
        int up = isLimit ? s[i] - '0' : 9;
        for (int d = isNum ? 0 : 1; d <= up; d++) {
            res += f(i + 1, sum + (d == 2 ? 1 : 0), isLimit && d == up, true);
        }
        if (!isLimit && isNum) {
            dp[i][sum] = res;
        }
        return res;
    }
}































 







$1067. 范围内的数字计数

import java.util.Arrays;

public class Solution1067 {
    private int x;

    public int digitsCount(int d, int low, int high) {
        x = d;
        return countDigitOne(high) - countDigitOne(low - 1);
    }

    private char[] s;
    private int[][] dp;

    private int countDigitOne(int n) {
        s = String.valueOf(n).toCharArray();
        int len = s.length;
        dp = new int[len][10];
        for (int i = 0; i < len; i++) {
            Arrays.fill(dp[i], -1);
        }
        return f(0, 0, true, false);
    }

    private int f(int i, int sum, boolean isLimit, boolean isNum) {
        if (i == s.length) {
            return isNum ? sum : 0;
        }
        // 记忆化搜索
        if (!isLimit && isNum && dp[i][sum] >= 0) {
            return dp[i][sum];
        }
        int res = 0;
        if (!isNum) {
            // 可以跳过当前数位
            res = f(i + 1, sum, false, false);
        }
        int up = isLimit ? s[i] - '0' : 9;
        for (int d = isNum ? 0 : 1; d <= up; d++) {
            res += f(i + 1, sum + (d == x ? 1 : 0), isLimit && d == up, true);
        }
        if (!isLimit && isNum) {
            dp[i][sum] = res;
        }
        return res;
    }
}



 


 































 







600. 不含连续 1 的非负整数

import java.util.Arrays;

public class Solution600 {
    private char[] s;
    private int[][] dp;

    public int findIntegers(int n) {
        // 二进制字符串
        s = Integer.toBinaryString(n).toCharArray();
        int len = s.length;
        dp = new int[len][2];
        for (int i = 0; i < len; i++) {
            Arrays.fill(dp[i], -1);
        }
        return 1 + f(0, 0, true, false);
    }

    private int f(int i, int pre, boolean isLimit, boolean isNum) {
        if (i == s.length) {
            return isNum ? 1 : 0;
        }
        // 记忆化搜索
        if (!isLimit && isNum && dp[i][pre] >= 0) {
            return dp[i][pre];
        }
        int res = 0;
        if (!isNum) {
            // 可以跳过当前数位
            res = f(i + 1, pre, false, false);
        }
        int up = isLimit ? s[i] - '0' : 1;
        for (int d = isNum ? 0 : 1; d <= up; d++) {
            if (pre == 1 && d == 1) {
                break;
            }
            res += f(i + 1, d, isLimit && d == up, true);
        }
        if (!isLimit && isNum) {
            dp[i][pre] = res;
        }
        return res;
    }
}








 





 















 












902. 最大为 N 的数字组合

import java.util.Arrays;

public class Solution902 {
    private String[] digits;
    private char[] s;
    private int[] dp;

    public int atMostNGivenDigitSet(String[] digits, int n) {
        this.digits = digits;
        s = String.valueOf(n).toCharArray();
        int len = s.length;
        dp = new int[len];
        Arrays.fill(dp, -1);
        return f(0, true, false);
    }

    private int f(int i, boolean isLimit, boolean isNum) {
        if (i == s.length) {
            return isNum ? 1 : 0;
        }
        // 记忆化搜索
        if (!isLimit && isNum && dp[i] >= 0) {
            return dp[i];
        }
        int res = 0;
        if (!isNum) {
            // 可以跳过当前数位
            res = f(i + 1, false, false);
        }
        char up = isLimit ? s[i] : '9';
        for (String digit : digits) {
            char d = digit.charAt(0);
            // 枚举要填入的数字 d
            if (d > up) {
                break;
            }
            res += f(i + 1, isLimit && d == up, true);
        }
        if (!isLimit && isNum) {
            dp[i] = res;
        }
        return res;
    }
}

1012. 至少有 1 位重复的数字

import java.util.Arrays;

public class Solution1012 {
    private char[] s;
    private int[][] dp;

    public int numDupDigitsAtMostN(int n) {
        s = String.valueOf(n).toCharArray();
        int len = s.length;
        dp = new int[len][1 << 10];
        for (int i = 0; i < len; i++) {
            Arrays.fill(dp[i], -1);
        }
        return n - f(0, 0, true, false);
    }

    private int f(int i, int state, boolean isLimit, boolean isNum) {
        if (i == s.length) {
            return isNum ? 1 : 0;
        }
        // 记忆化搜索
        if (!isLimit && isNum && dp[i][state] >= 0) {
            return dp[i][state];
        }
        int res = 0;
        if (!isNum) {
            // 可以跳过当前数位
            res = f(i + 1, state, false, false);
        }
        int up = isLimit ? s[i] - '0' : 9;
        for (int d = isNum ? 0 : 1; d <= up; d++) {
            // 枚举要填入的数字 d
            if ((state >> d & 1) == 0) {
                // d 不在 mask 中
                res += f(i + 1, state | (1 << d), isLimit && d == up, true);
            }
        }
        if (!isLimit && isNum) {
            dp[i][state] = res;
        }
        return res;
    }
}









 






















 

 








1397. 找到所有好字符串

import java.util.Arrays;

public class Solution1397 {
    private static final long MOD = 1000000007;
    private char[] s1c;
    private char[] s2c;
    private char[] evilc;
    private long[][] dp;
    private int[] next;

    public int findGoodStrings(int n, String s1, String s2, String evil) {
        this.s1c = s1.toCharArray();
        this.s2c = s2.toCharArray();
        this.evilc = evil.toCharArray();

        int m = evil.length();
        dp = new long[n][m];
        for (int i = 0; i < n; i++) {
            Arrays.fill(dp[i], -1);
        }
        this.next = getNext(evil);

        return (int) f(0, 0, true, true);
    }

    private long f(int i, int stats, boolean downLimit, boolean upLimit) {
        if (i == s1c.length) {
            return 1;
        }
        // 记忆化搜索
        if (!downLimit && !upLimit && dp[i][stats] != -1) {
            return dp[i][stats];
        }
        long res = 0;
        char down = downLimit ? s1c[i] : 'a';
        char up = upLimit ? s2c[i] : 'z';
        for (char ch = down; ch <= up; ch++) {
            int nextStats = getNextStats(stats, ch);
            if (nextStats == evilc.length) {
                continue;
            }
            res += f(i + 1, nextStats, downLimit && ch == s1c[i], upLimit && ch == s2c[i]);
            res %= MOD;
        }

        if (!downLimit && !upLimit) {
            dp[i][stats] = res;
        }
        return res;
    }

    // KMP
    private int getNextStats(int stats, char ch) {
        while (stats != 0 && evilc[stats] != ch) {
            stats = next[stats - 1];
        }
        return (evilc[stats] == ch) ? stats + 1 : 0;
    }

    // KMP 前缀函数
    // https://oi-wiki.org/string/kmp/
    private int[] getNext(String s) {
        int[] next = new int[s.length()];
        for (int i = 1, j = 0; i < s.length(); i++) {
            while (j > 0 && s.charAt(i) != s.charAt(j)) {
                j = next[j - 1];
            }
            if (s.charAt(i) == s.charAt(j)) {
                j++;
            }
            next[i] = j;
        }
        return next;
    }
}




















 
















 





































(全文完)