数位DP--力扣2376.统计特殊数字

https://leetcode.cn/problems/count-special-integers/description/?envType=daily-question&envId=2024-09-20

跟着灵神学的模板题,有一套完整的模板可以完美解决这种数位DP的问题

其核心思想就是使用深度优先搜索,每次找到一个符合要求的特殊数字就加1

在此基础上进行分类讨论

使用四个参数来作为dfs的参数,分别是:

1.i,表示当前遍历到了从左往右的第i位,i==0的时候是左起第一位,i==n的长度的时候是右起第一位的右边

2.mask,位运算,因为集合是可以表示为一个唯一的数的。例如,集合{2,0,3}可以表示为1101,其中,右起第1位为1表示0在集合中,右起第2位为0表示1不在集合中。使用这个mask来表示已经出现的数字

3.is_limit,表示是否受到n的约束。举个例子,如果是123456,在遍历到坐起第4位,即n的4这个位置的时候,前面三位的值已经是123,了, 这个时候is_limit为true。表示当前的这个第4位只能取最多到n[i],即4的值,如果不受约束,前面是122,那么当前第4位可以取最多到9

4.is_num,表示前面的数字是否为空。

如果前面的数字为空,那么有两种情况,要么跳过当前位数,要么当前位数填数字。

1)如果跳过当前位数,那么保留is_num==false

2)如果想要在当前位数上填数字,这个数字必须至少从1开始

如果前面的数字不为空,那么当前这个位数是不能跳过的,必须填一个最少从0开始的数字

接下来开始分类讨论

  1)i==n的长度且is_num==true

  此时我们的指针i其实是在n的右边一位,即从0到i-1已经是n的全部了。出现这种情况表示得到了一个有效数字,返回1

  2)在遍历到当前这个位数的时候,已经计算过了(记忆化搜索)

  例如,在遍历到第i=3的时候,mask==123,而memo[3][123(二进制)]这个值已经计算过了,那么返回memo[3][123(二进制)]

  返回记忆化搜索的结果

  这里要特别提一嘴二进制位运算,如果是前面出现的数字为123,那么表示出现了123这三个数字,使用二进制表示为$$1110$$,十进制为14,即使用[3][14]来表示到第3位为止,前面的数字的组合为123

  此外,还需要添加一个判断条件,is_limit==false,因为如果n=567000,在遍历到第i=3的时候,mask=567$$_2$$,如果没有这个限制条件,那么会把前3位为765的情况也算进去

  3)一般情况,只能计算,分类讨论

  1.跳过当前位数

  只有当is_num为false的时候,才可以跳过当前位数

  2.不跳过当前位数

  分几种情况:

  a.如果is_num是false且is_limit为false

  此时,当前这个位最少应该填1

  b.如果is_num为true且is_limit为false

  此时,当前这个位最少应该填0

  c.如果is_num为false且is_limit为true

  这是不可能的,is_num为false表示前面的值是空,但是is_limit表示前面的值受到了n的约束

  d.如果is_num为true且is_limit为true

  当前这个位数最多只能填到n[i]

  4)存储记忆化搜索的结果

  和读取记忆化搜索结果一样的,也是需要判断是否会受到is_limit的约束

  AC代码

  抄灵神的代码加注释

class Solution {
public:
    int countSpecialNumbers(int n) {
        string s = to_string(n);
        int m = s.length();
        //按二进制左移10位
        vector<vector<int>> memo(m, vector<int>(1<<10, -1)); // -1 表示没有计算过
        //isnum 表示i前面的数位是否填了数字。为false,当前位可以跳过,或者当前位数至少填1,即填1-9;
        //为true,当前位数可以从0开始。即0-9
        auto dfs = [&](auto&& dfs, int i, int mask, bool is_limit, bool is_num) -> int {
            if (i == m) {
                //如果已经遍历到了最后一位,那么说明得到了一个合法数字,返回is_num
                //如果is_num为true,那么是返回1
                //如果is_num为false,那么是返回0,表示全部不填的情况,这种情况下我们没有得到数字
                return is_num; // is_num 为 true 表示得到了一个合法数字
            }
            if (!is_limit && is_num && memo[i][mask] != -1) {
                //如果is_limit为false,即不受到约束
                //并且is_num为true,即当前这个数字是有效的
                //并且在记忆化搜索中,到第i位为止,mask这个数字是计算过的
                return memo[i][mask]; // 之前计算过
            }
            int res = 0;
            if (!is_num) { // 可以跳过当前数位
            //如果is_num为false,即前面的0到i-1位都是空的
            //跳过当前位数
                res = dfs(dfs, i + 1, mask, false, false);
            }
            // 如果前面填的数字都和 n 的一样,那么这一位至多填数字 s[i](否则就超过 n 啦)
            int up = is_limit ? s[i] - '0' : 9;
            // 枚举要填入的数字 d
            // 如果前面没有填数字,则必须从 1 开始(因为不能有前导零)
            for (int d = is_num ? 0 : 1; d <= up; d++) {
                if ((mask >> d & 1) == 0) { // d 不在 mask 中,说明之前没有填过 d
                    res += dfs(dfs, i + 1, mask | (1 << d), is_limit && d == up, true);
                }
            }
            if (!is_limit) {
                //is_limit==false,即不受限制
                //is_num为true,即当前这个数字有效
                memo[i][mask] = res; // 记忆化
            }
            return res;
        };
        return dfs(dfs, 0, 0, true, false);
    }
};

自己写的AC代码:

class Solution {
public:
    int countSpecialNumbers(int n) {
        string s=to_string(n);
        int len=s.length();
        vector<vector<int>> memo(len,vector<int>(1<<10,-1));
        auto dfs=[&](auto&& dfs,int i,int mask,bool is_limit,bool is_num) -> int{
            if(i==len){
                if(is_num==true) return 1;
                else return 0;
            }
            if(!is_limit && is_num && memo[i][mask]!=-1){
                return memo[i][mask];
            }
            int ans=0;
            //先跳过
            if(!is_num){
                ans+=dfs(dfs,i+1,mask,false,false);
            }
            int upper_limit=is_limit?s[i]-'0':9;
            for(int num=is_num?0:1;num<=upper_limit;num++){
                if((mask>>num & 1)==0){
                    ans+=dfs(dfs,i+1,mask|(1<<num),is_limit && num==upper_limit,true);
                }
            }
            if(!is_limit){
                memo[i][mask]=ans;
            }
            return ans;
        };
        return dfs(dfs,0,0,true,false);
    }
};

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值