• leetcode 6151. 统计特殊整数


    题目描述:

    如果一个正整数每一个数位都是 互不相同 的,我们称它是 特殊整数 。

    给你一个 正 整数 n ,请你返回区间 [1, n] 之间特殊整数的数目。

    示例 1:

    输入:n = 20
    输出:19
    解释:1 到 20 之间所有整数除了 11 以外都是特殊整数。所以总共有 19 个特殊整数。
    示例 2:

    输入:n = 5
    输出:5
    解释:1 到 5 所有整数都是特殊整数。
    示例 3:

    输入:n = 135
    输出:110
    解释:从 1 到 135 总共有 110 个整数是特殊整数。
    不特殊的部分数字为:22 ,114 和 131 。

    提示:

    1 <= n <= 2 * 109

    分析

    尽管提高课上刷了很多数位DP的问题,如果很久不做这类问题的话也是很容易翻车的。

    一般数位DP的问题都会给一个限制条件,如何求在固定范围内满足该限制条件的数有多少个。比如本题的限制条件就是每个数位上的数各不相同。我们要求的有两部分:以j开头的i位数满足限制条件的有多少个;不超过指定的数满足限制条件的数有多少个。

    第一个问题我们往往用f[i][j]表示以j为末尾的i位数满足条件的数的数量,但是对于霸体而已,以什么数开头并不重要,重要的是几位数。我们只需要指定i位数中满足条件的有多少个就可以了。

    • 1位数:1 - 9共9个
    • 2位数,首位数可以是1 - 9,末尾数可以是0 - 9中除掉首位数的数字,一共有9 * 9 = 81个
    • 3位数,首位数可以是1 - 9,第二位数可以是0 - 9中除了第一位的数,第三位数可以是0 - 9中除了前两位的数,一共9 * 9 * 8个

    所以很明显,可以用一个数组预处理出满足条件的i位数有多少个,用q[i]表示i位数中满足条件的数的个数。

    第二个问题,不超过指定的数满足限制条件的数有多少个?

    比如n = 987,首先所有的两位数和1位数中满足条件的均小于n,也就是q[1] + q[2]个。然后就考察3位数的情况。最高位是1 - 8时,不管后两位是多少,均小于n,所以一共8 * 9 * 8个数。

    然后考察首位是9的情况,第二位是0 - 7时,不管第三位是多少,均小于n,而前两位占了两个数,第三位只能有8种选择了,一共有8 * 8个数。

    前两位是98时,最后一位是0-6均满足条件,同时987也没有重复数字,也是满足条件的,一共有8个满足条件的数。

    所以从这个例子可以看出,我们需要自高位向低位遍历n,每个位置上小于n对应位置上的数,只需要取考虑后面位数的数字,如果对应位置上的数等于n对应位置上的数,就需要继续考察下一位。

    代码

    class Solution {
    public:
        int f[12][12];
        int countSpecialNumbers(int n) {
            if(n < 10)  return n;
            vector<int> num;
            while(n)    num.push_back(n % 10),n /= 10;
            int res = 0;
            vector<bool> m(12,false);
            bool flag = true;
            vector<int> q(12,0);
            q[1] = 9;
            int r = 9;
            for(int i = 2;i <= 10;i++) {
                q[i] = q[i-1] * r;
                r--;
            }
            for(int i = 1;i < num.size();i++)   res += q[i];//位数低于n的情况
            int choose = 0;
            for(int i = num.size() - 1;i >= 0;i--){
                int x = num[i];
                choose++;//已经选择了的位数
                int s = 1;//剩下位置上的符合条件的数
                for(int j = 0,k = 10 - choose;j < i;j++,k--) {
                    s *= k;
                }
                for(int j = (choose == 1);j < x;j++) {
                    if(!m[j])   res += s;
                }
                if(m[x])    break;//出现重复数,枚举终止
                m[x] = true;
                if(!i)  res++;//枚举到最后一位
            }
            return res;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    总结

    虽然本题分类讨论的逻辑以及代码都不复杂,但数位DP问题需要注意的地方很多。比如本题单独讨论了1到n - 1位数中满足条件的数的数量,但是很多问题中会将这个归类于讨论第一位数时,首位为0的情况,但是本题由于后面的数与之前出现的数相关,所以无法合并。举个例子,比如前两位是10后面枚举时就只有2 - 9这么多数可以枚举了,但是如果首位是0,代表没有数字,后面依旧可以枚举0的,比如020,也就是20,是合法的数。

    如果严格按照DP的思想来分析本题,可以用g[i][j]表示前i - 1位数与n相同,第i位数为j,且j小于n的第i位数的符合条件的i位数有多少个。正如开头所说,设n的位数是k,位数小于k的合法的数的数量是很容易求出来的,我们进行状态划分只需要考虑前i位与n相同的k位数情况。

    总的来说,数位DP的问题需要先将数逐位分离存下来,然后求以j位末尾满足条件的i位数的数量;然后遍历n位数,依次讨论前面的数与n相同,当前位置的数比n对应位置小的情况,需要特判的是首位为0的情况以及遍历结束时恰好n也是满足条件的数的情况。

  • 相关阅读:
    开发者谈 | OAuth 2.0 和 OIDC 协议的关系?(内含必看案例)
    docker下的onlyoffice安装(for seafile)
    c++取出文件路径中的文件名
    加盐加密详解
    3.前端调式
    15 【登录鉴权】
    巧用递归解决煎饼排序问题
    MySQL INNER JOIN:内连接查询
    Spring事务
    设备巡检系统为巡检人员带来便利有哪些
  • 原文地址:https://blog.csdn.net/qq_30277239/article/details/126334656