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