数位dp初理解+练习

数位dp,先尝试做题,背一下代码,自己模拟归纳

  • 1.2376. 统计特殊整数
  • 2.2233. 数字 1 的个数
  • 3.1012. 至少有 1 位重复的数字

1.2376. 统计特殊整数

数位dp初理解+练习_第1张图片

class Solution 
{
public:
    int countSpecialNumbers(int n) 
    {
        //记忆化搜索
        auto s=to_string(n);
        int m=s.length(),memo[m][1<<10];
        memset(memo,-1,sizeof memo);  //memo[i][mask]表示前i位数在,且填了数的集合是mask的情况下,有多少种方案
        //2  3  4 5 ..
        //3  2  ......
        //      i      memo[i][mask]    //上面的mask都是[2,3]的集合,所以后面都是一样的情况

        function<int(int,int,bool,bool)>dp=[&](int i,int mask,bool is_limit,bool is_num)->int
        {
            if(i==m)  //说明构造完了一个1-m位的合法数
                return is_num;  //当前位要有值才行

            if(!is_limit&&is_num&&memo[i][mask]!=-1)  //如果前一位有数且最高位不是n对应的数,那么可以返回记忆值
                return memo[i][mask];
            
            int res=0;  //记录最高位是i的情况下,有多少数满足条件

            //可以选择跳过
            if(!is_num)  //构造一个低一位的数
                res=dp(i+1,0,false,false);
            
            int up=is_limit?s[i]-'0':9;

            for(int d=1-is_num;d<=up;d++)  //1-is_num是因为如果前面没有数,那么当前又要选一个数,那么必须是1开始
            {
                if((mask>>d&1)==0)  //当前数没有选过
                {
                    //如果下一个有限制,那么当前数正好是n对应的数,且当前数也是被限制的
                    res+=dp(i+1,mask|(1<<d),is_limit&&d==up,true);
                }
            }

            if(!is_limit&&is_num)
                memo[i][mask]=res;
            return res;

        };
        return dp(0,0,true,false);
    }
};

2.2233. 数字 1 的个数

数位dp初理解+练习_第2张图片

class Solution 
{
public:
    int countDigitOne(int n) 
    {
        //找出0-n中的所有数字1出现的个数
        auto s=to_string(n);
        int m=s.length(),dp[m][m];
        memset(dp,-1,sizeof dp);

        function<int(int,int,bool)>f=[&](int i,int cnt,bool is_limit)->int
        {
            if(i==m)
                return cnt;
            
            if(!is_limit&&dp[i][cnt]!=-1)return dp[i][cnt];

            int res=0;

            for(int d=0,up=is_limit?s[i]-'0':9;d<=up;d++)
            {
                res+=f(i+1,cnt+(d==1),is_limit&&(d==up));
            }

            if(!is_limit)
                dp[i][cnt]=res;
            return res;
            
        };

        return f(0,0,true);
    }
};

3.1012. 至少有 1 位重复的数字

数位dp初理解+练习_第3张图片
和2376一样的解放,算出没有重复的数,n-没有重复的数,剩下的就是有重复的

class Solution 
{
public:
    int numDupDigitsAtMostN(int n) 
    {
        //找出[1,n]中至少有1位重复数字的正整数的个数.
        
        auto s=to_string(n);
        int m=s.length();
        int memo[m][1<<10];
        memset(memo,-1,sizeof memo);

        function<int(int,int,bool,bool)>dp=[&](int i,int mask,bool is_limit,bool is_num)->int  
        {  //is_num表当前位前面是否有数
            if(i==m)return is_num;
            if(!is_limit&&is_num&&memo[i][mask]!=-1)return memo[i][mask];

            int res=0;
            if(!is_num)res=dp(i+1,0,false,false);   //跳过该数
            //实际上跳过该数的目的就是算出(i+1位为最高位的合法数),然后记忆化回溯
            //不跳过该数,

            int up=is_limit?s[i]-'0':9;
            for(int d=1-is_num;d<=up;d++)  //d==1-is_num,因为如果当前位要选数
            //那么当前位前面如果没有数,那么当前必须从1开始,否则可以从0开始
            {
                if((mask>>d&1)==0) //第i位没有取数
                    res+=dp(i+1,mask|(1<<d),is_limit&&d==up,true);  //当前数有限制且没有去到对应n的那一位的上界,则下一位没有限制
            }
            if(!is_limit&&is_num)  //如果当前数没有限制,那么可以
                memo[i][mask]=res;
            return res;
            
        };
        return n-dp(0,0,true,false);  
    }
};

你可能感兴趣的:(算法,leetcode)