面试题43. 1~n整数中1出现的次数
这道题我很喜欢,有用的知识增加了。
首先明确这道题可以用数位DP来做:
先上一个数位DP模板:
#include <bits/stdc++.h>
using namespace std;
//问:在区间[a,b]中不含49的数字有多少个
int a, b, shu[20], dp[20][2];
//shu数组存储各位上的数 dp[i][1] 表示第i+1位为4,剩余数字长度为i的数字的个数
//dp[i][0] 意思第i+1位为除4外任意一个数字时,剩余数字长度为i的数字的个数。不是所有不为4的数剩余数字长度为i的数字的个数之和 如dp[1][0]=9,dp[1][1]=8
int dfs(int len,bool if4,bool limit) //len 遍历的每一位(...千,百,十,个),len=1表示个位.len=2表示十位... if4 此位的前一位是否为4, shangxian, 此位数是否有上限, 例如前两位是1, 2, 第三位个位有上限7
{
//然后开始在len位上遍历数字
if (len==0)
return 1;
if (!limit&&dp[len][if4]!=0) //记忆化搜索这里可以直接返回结果,
return dp[len][if4]; //如对数字127,当前两位为1,0和 1,1 的时候由于最后一位没有limit,故结果相同, 但与1,2情况不同,因为为1,2时第三位有limit了
int cnt=0,maxx;
if(limit)
maxx=shu[len]; //有上限的话最大取shu[len]的值
else
maxx=9; //没上限的话最大取9
for(int i=0;i<=maxx;i++) //上限记录到了maxx中,从0开始依次取
{
if(if4&&i==9)
continue; //如果上一位值为4且当前位值为9,直接进入下一循环
cnt+=dfs(len-1,i==4,limit&&i==maxx); //只有 当前有限制 且 现在已经达到了上限(maxx)才能构成下一位有限制
} //由于在这个循环中len没有变,只是前一位数字变了,所以在进入下一个dfs函数,如果没有限制则返回值与之前得到的就相同,dp[len][if4]。
if(!limit)
dp[len][if4]=cnt; //如果有限制,那么就不能记忆化,否则记忆的是个错误的数.例如前两位12,由于第三位有上限7不能保存这个结果,而10和11结果相同,无上限, 记忆化保留结果
return cnt;
}
int solve(int x)
{
memset(shu,0,sizeof(shu));
int k=0;
while(x){
shu[++k]=x%10; //保存a,b的数
x/=10;
}
return dfs(k,false,true);//从最高位开始
}
int main(){
scanf("%d%d",&a,&b);
printf("%d\n",solve(b)-solve(a-1));
return 0;
}
好了,给出这道题的正解:
1、len的作用
len 遍历的每一位(…千百十个),遍历从高位到低位,len=1表示个位,len=2表示十位。
2、state的作用
state 表示之前所谓位的状态:前面总共出现了多少个1。
例如要求
20
20
20 以内出现了多少个
1
1
1,那么当
l
e
n
=
2
len = 2
len=2 的时候,
c
n
t
cnt
cnt 分别加上两位数,
0
x
、
1
x
、
2
x
0x、1x、2x
0x、1x、2x有多少个1,(当十位是
0
0
0 或者
1
1
1 的时候,
x
x
x 不受限制,limit=false,当十位是
2
2
2 的时候,
x
x
x 只能取
0
0
0),
l
e
n
=
2
len=2
len=2(十位)的时候,
s
t
a
t
e
=
0
state = 0
state=0,因为十位以前没有
1
1
1。
但是当
l
e
n
=
1
len = 1
len=1(个位)的时候,
0
x
0x
0x 的
s
t
a
t
e
=
0
state = 0
state=0,如果
x
x
x 是
1
1
1,就返回
1
1
1,至此
c
n
t
cnt
cnt 累计加
1
1
1。
当
l
e
n
=
1
len = 1
len=1(个位)的时候,
1
x
1x
1x 的
s
t
a
t
e
=
1
state = 1
state=1,如果
x
x
x 是
1
1
1,就返回
s
t
a
t
e
+
1
state+1
state+1,至此
c
n
t
cnt
cnt 累计加
s
t
a
t
e
∗
10
+
1
state*10+1
state∗10+1(除了
11
11
11 返回
2
2
2 之外,其他都是返回
1
1
1)。
3、limit的作用
我们知道在搜索的时候,数位搜索范围可能发生变化;
举个例子:我们在搜索 [0,555] 的数时,显然最高位搜索范围是 0 ~ 5 ,而后面的位数的取值范围会根据上一位发生变化:
1、当百位不是5的时候,十位可以取0~9
2、当百位是5的时候,十位只能取0~5
4、关于DP数组
dp[i][j] 表示第 i 位,之前的高位状态是 j 的时候,有多少种解法。
比如说 dp[1][1]=11,因为 i=1 表示个位,j=1 表示前面只有一个1,那十位肯定就是1,然后10~19共有11个1。
dp 数组起到一个记忆化的作用。
如果前一个高位有限制,那么就不能记忆化,否则记忆的是个错误的数,例如要求12里面有多少个1,前一位是1,已经到达了十位限制的最大数,那么后面只能取0,1,2,最后算出dp[1][1]=4,但是这个4没有考虑13~19,是错误的不可以被重新使用的。
#include <bits/stdc++.h>
using namespace std;
//问:求1~n这n个整数的十进制表示中1出现的次数
int n, shu[32], dp[32][32];
int dfs(int len, int state, bool limit) {
if (len==0)
return state;
if (!limit && dp[len][state]!=0)
return dp[len][state];
int cnt=0, maxx;
if(limit)
maxx=shu[len]; //有上限的话,上限就是shu[len]的值
else
maxx=9; //没上限的话最大取9
//上限记录到了maxx中,从0开始依次取
for(int i=0;i<=maxx;i++) {
int nstate = state;
if(i==1) nstate++;
cnt+=dfs(len-1, nstate, limit&&i==maxx);
//只有 “当前有限制” 且 现在已经达到了上限(maxx)才能构成下一位有限制
} //由于在这个循环中len没有变,只是前一位数字变了,所以在进入下一个dfs函数,如果没有限制则返回值与之前得到的就相同,dp[len][if4]。
if(!limit)
dp[len][state]=cnt;
/**
如果有限制,那么就不能记忆化,否则记忆的是个错误的数.例如前两位12,
由于第三位有上限7不能保存这个结果,而10和11结果相同,无上限,记忆化保留结果
*/
return cnt;
}
int solve(int x) {
memset(shu, 0, sizeof(shu));
int k=0;
while(x) {
shu[++k]=x%10;
x/=10;
}
return dfs(k, 0, true);//从最高位开始
}
int main() {
scanf("%d",&n);
printf("%d\n",solve(n));
return 0;
}