1、基础知识
时间复杂度和空间复杂度: 点击跳转文章(转载)
2、统计数字问题
(1). 问题描述
一本书的页码从自然数1 开始顺序编码直到自然数n。书的页码按照通常的习惯编排,每个页码都不含多余的前导数字0。例如,第6 页用数字6 表示,而不是06 或006 等。数字计数问题要求对给定书的总页码n,计算出书的全部页码中分别用到多少次数字0,1, 2,…,9。
(2). 算法设计
给定表示书的总页码的10 进制整数n (1≤n≤10 ) 。编程计算书的全部页码中分别用到多少次数字0,1,2,…,9。
解析:
具体的算法思想是:
说明:n为一个自然数,len表示为十进制数的位数(如1234,则n为1234,len为4)。
对于一个len位整数,我们可以把0到n之间的n+1个整数从小到大这样来排列(补0):
000......0
.............
099......9
100......0
100......1
.............
199......9
200......0
.............
299......9
300......0
..........
n
如上所示一直排到自然数n。对于从0到099…9这个区间来说,抛去最高位的数字不看,不难验证,0到9每个数字出现的次数都等于(len-1)*10的(len-2)次方,
而目标数字n的最高位数p就是 0到099…9这样的区间 的个数,用(len-1)*10的(len-2)次方 *p 在加上这些区间最高位数出现的次数,我们就已经把 0到 n除最高位数其他位数变为0的数x(如n为3929,则x为3000) -1 之间各个数字出现的次数统计出来了。
接下来我们只需将x到n之间数据统计出来即可。我们需把n-x这个数再次递归操作。
不过在进入递归之前,我们要先把最高位数出现的n-x+1次补上,还要判断n-x的位数是否等于len-1,不是的话说明n是例如10010这样的数,要把len-(n-x这个数的位数)-1个0补上。
例子说明:
对于一个数字34567,我们可以这样来计算从1到34567之间所有数字中每个数字出现的次数:
从0到9999,这个区间的每个数字的出现次数可以使用原著中给出的递推公式,即每个数字出现4000次((5-1)* 10的3次方 )。
从10000到19999,中间除去万位的1不算,又是一个从0000到9999的排列,
这样的话,从0到34567之间的这样的区间共有3个。
所以从00000到29999之间除万位外每个数字出现次数为3*4000次。
然后再统计万位数字,每个区间长度为10000,
所以0,1,2在万位上各出现10000次。
而3则出现4567+1=4568次。
之后,抛掉万位数字,对于4567,再使用上面的方法计算,一直计算到个位即可。
在结果统计好之后。还有最后一步,就是减去补上的0.当n=21时,要减去的0有:100(红色部分)+101(黄色部分)
当n=123时,要减去的0有:100(红色部分)+101(黄色部分)+10^2(灰色部分)
可知减少的0的数量为 100 + ······ + 10 n个
源码:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int c[10]; // c数组存储结果
int weishu(int n) {
int weishu = 1;
while (n / 10 >= 1) { //int除法向下取整
weishu++;
n = n / 10;
}
return weishu;
}
int zuigao(int n, int len) {
int num = round(pow(10.0, len - 1)); //round 函数四舍五入 ,确保结果无误。10的位数减一次方
return n / num;
}
int yushu(int n,int len) {
int num = round(pow(10.0, len - 1));
return n % num;
}
void solve(int n) {
//数n的位数
int len = weishu(n);
//最高位的值
int p = zuigao(n, len);
//计算忽略首为数字后(除了首字母最高位和p一样的),1到10出现的次数
for (int i = 0; i < 10; i++) {
c[i] += p * (len - 1) * (int)round(pow(10.0, len - 2));
// 区间: 长度为len-1的 0···00 到 长度为 len-1的 9···99 出现次数为 len-1 * 10 的 len - 2 次方“(len - 1) * (int)round(pow(10.0, len - 2))”
// 共有p个区间。
}
// 计算0到p,首位数字出现的次数,并加入结果
for (int i = 0; i < p; i++) {
c[i] += (int)round(pow(10.0, len - 1));
}
//求n去掉最高位数后的数,(如1229去掉最高位为229)
int t = yushu(n , len);
if (t == 0) {//如果t为0
c[p]++;//最高位加1
c[0] += len - 1;//0位加len-1
return;
}
int lenT = weishu(t);
if (lenT != len - 1) {//若像10010这种情况,中间2个0也要相应的处理
c[0] += (len - lenT - 1) * (t + 1);//若像10010这种情况, 因为10000到10010,中间的数的2个0 都要处理,所以*t+1
}
c[p] += 1 + t; //最高位的数目补一下 如n=10010,10000到10010的最高位1补上 11
return solve(t); //把n去掉最高位数后的数进入下一次递归
}
int main() {
int n;
while (cin >> n) {
fill(c, c + 10, 0); //把数组填充满为0
int len = weishu(n);
solve(n);
for (int i = 0; i < len; i++) {
c[0] -= (int)round(pow(10.0, i)); //减去补上的0
}
for (int i = 0; i < 10; i++) {
cout << i << " : " << c[i] << endl;
}
}
}