【题目描述】
把前n(n≤10000)个整数顺次写在一起:123456789101112…数一数0~9各出现多少次
(输出10个整数,分别是0,1,…,9出现的次数)。
输入第一行表示有T组数据,后续是T行数据n。依次输出前n个整数中0~9出现的次数,中间用空格隔开。
【样例输入】
2
3
13
【样例输出】
0 1 1 1 0 0 0 0 0 0
1 6 2 2 1 1 1 1 1 1
【题目来源】
刘汝佳《算法竞赛入门经典 第2版》习题3-3 数数字(Digit Counting , ACM/ICPC Danang 2007, UVa1225)
【解析】
前n个数相当于公差为1的等差数列,所以本题本质上就是统计数列中的各个数字出现的次数。
基本思路就是将每个整数分隔成一个个的数字k,然后用f[k]++记录数字k出现的次数。
一、效率较低的算法:每个n都遍历一次
就是针对输入的每个n,由1-n遍历、分离每个数求解。代码如下:
#include<stdio.h>
#include<string.h>
int main(){
int T;
int f[10];
scanf("%d", &T);
while(T--){
memset(f, 0, sizeof(f)); //数组重置为0
int n;
scanf("%d", &n);
for(int i=1; i<=n; i++){
int t=i;
//将数字t中各个数字出现次数累加到f[k]
while(t){
int k = t%10;
f[k] ++;
t /= 10;
}
}
for(int k=0; k<9; k++){
printf("%d ", f[k]);
}
printf("%d\n", f[9]);
}
return 0;
}
代码说明:
1.数组重置。由于涉及多组数据,为了避免上一组数据统计结果影响到下一组,需要在统计每组数据前将数组f[k]进行重置。
2.分离数字。采用“取尾法”将一个整数分离成各个数字。如想进一步了解详见“整数的分离与合成”
3.空格隔开。这是一个初学者常掉的坑,但凡见到“用空格隔开数据”这样的表述一定要注意最后一个数字后不能输出空格。
二、高效算法:一次完全遍历,多次查找。
由于程序涉及到多组数据,像上面每输入一个n都要从1-n遍历一次显然效率比较低。更高效的做法是一次性从1-10000整个遍历一次,针对每个n,分别求出0-9出现的次数,然后把这些数据都存储到数组中。
这时候就需要用到二维数组f[n][k]统计数次,n表示前n个整数,k表示0-9。显然有f[n+1][k]=f[n][k]+x(x是k在整数n+1中出现的次数)
代码如下:
#include<stdio.h>
int f[10005][10];
int main(){
for(int n=1; n<=10000; n++){
//初始化f[n][k]
for(int k=0; k<=9; k++)
f[n][k] = f[n-1][k];
int t=n;
//将数字t中各个数字出现次数累加到f[n][k]
while(t){
int k = t%10;
f[n][k]++;
t /= 10;
}
}
int T;
scanf("%d", &T);
while(T--){
int n;
scanf("%d", &n);
//直接查找输出
for(int k=0; k<9; k++){
printf("%d ", f[n][k]);
}
printf("%d\n", f[n][9]);
}
return 0;
}
代码说明:
关于f[n+1][k]=f[n][k]+x的实现,老金最初也想用一个表达式实现,但是没有成功,只好退而求其次先用一个循环实现f[n+1][k]=f[n][k],不知道有没有高手有更好的方法,还望指点一二。
三、数字转字符串法
除了分离数字的方法,还有一种方法,就是将每个整数转为字符串,然后再遍历字符串中的每个字符。代码如下:
#include<stdio.h>
#include<string.h>
int f[10005][10];
int main(){
for(int n=1; n<=10000; n++){
//初始化f[n][k]
for(int k=0; k<=9; k++)
f[n][k] = f[n-1][k];
char s[5];
//整数转字符串
sprintf(s, "%d", n);
int len=strlen(s);
//对字符串中的每个字符计数
for(int k=0; k<len; k++){
f[n][s[k]-'0']++;
}
}
int T;
scanf("%d", &T);
while(T--){
int n;
scanf("%d", &n);
//直接查找输出
for(int k=0; k<9; k++){
printf("%d ", f[n][k]);
}
printf("%d\n", f[n][9]);
}
return 0;
}
代码说明:
和方法二相比,其实只更改了统计字符个数的那部分代码。
关键是要用到数字转字符串的函数sprintf(s, "%d", n),它的用法和printf比较相似,只是增加了一个参数用来保存转换后的字符串。