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;
    }
};

总结

虽然本题分类讨论的逻辑以及代码都不复杂,但数位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也是满足条件的数的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值