动态规划之数位DP

51 阅读1分钟

数位DP是指将一个数字按照个,十,百,千等数位拆分,关注每一位的数字,进而进行DP 一般解决的问题有如下特征:

  1. 最终目的为计数
  2. 这些条件可以使用数位来理解
  3. 输入可以为区间
  4. 上界很大,暴力搜索会超时

基本原理

我们发现,在一般的枚举过程中,如[7000, 7999],[8000, 8999],[9000, 9999]三个区间中他们的后三位的情况是高度相似的,因此我们可以将其归并,从而简化计算。 数位DP通常会利用常规计数技巧,例如将一个区间答案拆分成两部分相减(f(l,r)=f(0,r)f(0,l1)f(l, r) = f(0,r) - f(0,l - 1)

本质上就是从高位向低位逐步填充,但是过程中要注意1. 是否构成数字 2. 是否超过上界 3. 如何传递关系

应用

例1 统计特殊整数

如果一个正整数每一个数位都是 互不相同 的,我们称它是 特殊整数 。 给你一个  整数 n ,请你返回区间 [1, n] 之间特殊整数的数目。

class Solution {
    char s[];
    int memo[][];
    public int countSpecialNumbers(int n) {
        s = String.valueOf(n).toCharArray();
        int m = s.length;
        memo = new int [m][1 << 10];
        for (int i = 0; i < m; i++) Arrays.fill(memo[i], -1);
        return dfs(0, 0, true, false);
    }
  
    // 表示从第i位填数字,i前面填写的数字的集合为mask
    // isLimit表示前面填的数字是否是和n前缀相同
    // isNum表示是否前面均为0
    public int dfs(int i, int mask, boolean isLimit, boolean isNum) {
        if (i == s.length) return isNum ? 1 : 0;
        if (!isLimit && isNum && memo[i][mask] != -1) return memo[i][mask];
        int res = 0;
        if (!isNum) res = dfs(i + 1, mask, false, false);
        int up = isLimit ? s[i] - '0' : 9; // 如果前缀和n相同,那么这一位最多填写s[i]种情况
        for (int d = isNum ? 0 : 1; d <= up; d++) {
            if ((mask >> d & 1) == 0) // d不在mask中
                res += dfs(i+1, mask | (1 << d), isLimit && d == up, true);
        }
        return res;
    }
}