• `算法题解` `LeetCode` 2376. 统计特殊整数


    题目链接

    题解

    问在[1, x]之间的(所有数字) 的某类性质, 这是数位DP的问题;

    按照数位DP的规定, Prefix( r)表示: 在[0 - r]之间的所有数字
    所以, 将其转换为: Prefix( x) - Prefix( 1 - 1),

    数位DP会将[0 - r]的所有数字, 划分为3大类: 以r = 2345
    (1, 第一类: 非对齐数字, 为所有< 1000的数, 即[0 - 999]这些)
    (2, 第二类: 对齐数字, 为所有[1000, 2344]这些数, 可以发现, 这些数字的长度是相同的)
    (3, 第三类: r = 2345)

    一般, 优先考虑, (第二类数字) 是否有解; … 因为, 有时第一类数字的问题, 可以用与 (第二类数字) 相同的方法来解决

    具体的, 第二类数字, 还会继续划分成:
    ([1000, 1999])
    ([2000, 2099] [2100, 2199] [2200, 2299])
    ([2300, 2309] [2310, 2319] [2320, 2329] [2330, 2339])
    ([2340, 2340] [2341, 2341] [2342, 2342] [2343, 2343] [2344, 2345])
    … 具体规则可以回顾(数位DP)知识.

    我们称, 上面这13个区间, 我们对(第二类数字)的处理过程, 就是 对这13个区间的处理; 即, 一个区间, 是我们最小的处理单元;
    这每个区间, 他们都是可以表示为: abcd, 且某个前缀是固定的 后缀是0-9的任意
    比如, 对于区间[2100, 2199], 他为 (固定前缀为21, 后缀任意), 所有区间都是这样的形式

    因此, Aligned( vector< int> pre, int k)函数, 就是针对每个区间, 求解该区间的答案;
    pre为固定前缀(没有前导零), k为后缀长度. 该区间有10^k个数; [2100, 2199]对应为Aligned( {2, 1}, 2)
    那么, 比如对于[abc] [000 - 999]区间, 他的答案是多少呢?
    (1, 如果abc是非法的 即, 存在两位相同, 则, 整个区间全为非法的; ans为0)
    (2, 否则, abc互不相同, 则后缀有: [10-3种选择, 10-4种选择, 10-5种选择], 即ans = 7 * 6 * 5, 因为所有位都不同, 就类似于(排列问题).

    至此, (第二类数字) 我们已解决;

    看第一类数字: [0, 999], 他一般有两种处理方法:
    (1, 尝试用Aligned (即第二类数字的处理方法)来解决, 通过将(第一类数字) 添加前导零, 使得 (第一类数字 与 第二类数字 ) 具有相同长度
    … 即, 将[0, 999] 变成 [0000, 0999], 即Aligned( {0}, 3)的答案 是否等于 [0, 999]的答案呢?
    … 答案是否定的! 0001Aligned里 会按照非法的来处理, 而0001他在[0, 999]里对应为1 他是合法的!!!
    … 因此, 不可以添加前导零;
    (2, 拆分集合; 将[0, 999]继续划分为: ([0, 0] [1, 9] [10, 99], [100, 999])
    … 这样划分的依据在于: (每个区间, 都是等长的)
    … 每个区间的长度为: 1, 9, 90, 900, ...
    … 除了第一个区间[0, 0], 其他区间 都是: [1 后面全是0, 9 后面全是9]的形式;
    … 对于每个区间, 继续进行划分: 比如[100, 999], 划分为: [100, 199] [200, 299] [300, 399] …, [900, 999] (共9个)
    … 这样做的好处是: 对于每个小区间[200, 299], 他符合Aligned的形式, 直接调用Aligned( {2}, 2)即可
    … … 而这里, 其实对于[1000, 9999], 也可以自己直接算: [9种, 9种, 8, 7, ..], ans = 9 * 9 * 8 * 7 ... (注意, 第一位是[1-9], 其他位都是[0-9])

    更新

    题目要求的 (特殊数 所有位不同), 那么, 数位DP 是选择计算(特殊数) 还是计算(非特殊数)呢?
    … 这要看 [a b c] [000 - 999], 谁能在线性时间 处理这个集合的答案;
    … 这里的(非特殊数), 并不是连续的, 比如: 12341是(非特殊数), 它是跳跃的; 这不是DP能处理的
    … 而(特殊数), 每个位 都不同, 联系到(排列数)

    注意点

    • 这是一个非DP的数位DP, 不需要Build_dp记忆化; 直接暴力即可

    代码

    bool Is_valid( int _a){
        vector< bool> vis( 10, false);
        for( auto i = _a; i > 0; i /= 10){
            if( vis[ i % 10]){
                return false;
            }
            vis[ i % 10] = true;
        }
        return true;
    }
    int Aligned( const vector< int> & _pre, int _k){ //< _pre无前导零, _pre不会为空, _k可能为0
        { //* check whether `_pre` is valid
            vector< bool> vis( 10, false);
            for( auto i : _pre){
                if( vis[ i]){
                    return false;
                }
                vis[ i] = true;
            }
        }
        int ans = 1;
        int f = 10 - _pre.size();
        FOR_( i, 1, _k, 1){
            ans *= f;
            -- f;
        }
        return ans;
    }
    int Prefix( int _r){
        if( _r < 0){ return 0;}
        //--
        vector< int> bits;
        for( auto i = _r; i > 0; i /= 10){
            bits.push_back( i % 10);
        }
        reverse( bits.begin(), bits.end());
        if( bits.empty()){ bits.push_back( 0);}
        //--
        int n = bits.size();
        int ans = 0;
        FOR_( i, 0, n - 1, 1){
            int k = n - 1 - i;
            FOR_( h, 0, bits[ i] - 1, 1){
                if( ( i == 0) && ( h == 0)){
                    FOR_( j, 0, k, 1){
                        if( j == 0){
                            ans += Is_valid( 0);
    //< 此时代表[0, 0]这个区间, 即`0`这个数;   这里不能瞎写! 
    //< 虽然题目是[1, r]区间 不涉及0, 但我们的数位DP就是从`0`开始的
    //< 而且`Prefix( 0)`会调用(他会调用`Is_valid( 0)`, 不会进入这里, 不会执行(数位二叉树)
                        }
                        else{
                            FOR_( hh, 1, 9, 1){
                                ans += Aligned( { hh}, j - 1);
                            }
                        }
                    }
                }
                else{
                    vector< int> pre( bits.begin(), bits.begin() + i);
                    pre.push_back( h);
                    ans += Aligned( pre, k);
                }
            }
        }
        if( Is_valid( _r)){
            ++ ans;
        }
        return ans;
    }
    int countSpecialNumbers(int n) {
        return Prefix( n) - Prefix( 0);
    }
    
    • 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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
  • 相关阅读:
    基于物理的运动控制-DeepMimic
    理解依赖注入DI和控制反转IOC和容器
    SQL之exists、not exists
    生鲜行业B2B电子商务交易系统:缩短企业供采交易链路,掘金万亿级生鲜B2B
    Java从零学起(十二)----HashSet集合
    Himall商城Web帮助类 获得http请求数据
    SQL完整性约束
    黑马程序员微服务 第五天课程 分布式搜索引擎2
    ISO 5659-2塑料 烟生成 第2 部分:单室法测定烟密度试验方法
    好物周刊#9:AI 学习必备资料
  • 原文地址:https://blog.csdn.net/weixin_42712593/article/details/126518818