数位dp
如果计算区间[l,r]中出现次数,cnt(r) - cnt(l - 1)即可
class Solution {
public:
int res[10][1024];
int countDigitOne(int n) {
return dp(0, 0, true, false, to_string(n));
}
// 模板 有4个参数
// 从i开始填数字,i前面填过的数字集合是mask
// isLimit表示前面填的数字是否都是n对应位上的:isLimit==true,那么当前位至多为s[i](eg:n=123,前两位填的数字都是n对应位上的填了12,那么当前位只能填0~s[i]即0~3了);isLimit==false,当前位可以从0~9选填,至多为'9'
// isNum表示前面是否填了数字(是否跳过,是否是一个合法数字 比如010就不合法):isNum==true,那么当前位可以从0开始;isNum==false,那么我们可以跳过这位也不填,或者这位从1~9选填
int dp(int i, int mask, bool islimit, bool isnum, string n)
{
//返回条件需要视题目情况而定,这里返回mask是因为用的mask存的1的个数
if(i == n.size()) return mask;
// 前面既没有填n对应位上的数,又填了数,那么后面填的都不受限,这种情况会重复计算,所以用记忆化搜索来存
if(!islimit && isnum && res[i][mask] != 0)
return res[i][mask];
int sum = 0;
//如果之前位没有填数字,那么当前位可以选择跳过
if(!isnum)
sum = dp(i + 1, mask, false, false, n);
//确定每一位的上界up
int up = 0;
if(islimit)
up = n[i] - '0';
else up = 9;
//如果前一位没有填数字(isnum == false),当前位不能填0,从1开始;否则可从0开始填
for(int j = 1 - int(isnum); j <= up; j ++)
{
sum += dp(i + 1, mask + (j == 1), islimit && j == up, true, n);
}
//记忆化搜索,存储当前状态(i, mask)的值
res[i][mask] = sum;
return sum;
}
};