统计数字问题

题目出自《计算机算法设计与分析》王晓东编著。

问题描述:

一本书的页码从自然数1开始顺序编码直到自然数n。求的页码按照通常的习惯编排,每个页码都不含多余的前导数字0。例如第6页为6而非06或006等。试统计给定书的总页码n,计算出书的全部页码中分别用到多少次数字0,1,2,…,9。

给定表示书的总页码的十进制整数n(1≤n≤10^9),计算书的全部页码中分别用到多少次数字0,1,2,…,9。


解法一:

最容易想到的就是穷举1到n的所有数,逐个统计。

代码实现:

/* 穷举所有数进行统计的算法 */
void countNumber(int num, int results[10]) {
	int i;
	int temp;

	// 计数清零 
	for (i = 0; i < 10; i++) {
		results[i] = 0;
	}

	for (i = 1; i <= num; i++) {
		temp = i;
		while (temp > 0) {
			results[temp%10]++;
			temp /= 10;
		}
	}
}
这个算法的时间复杂度为 n*log10(n)


解法二:

发现当n分别为9,99,999,……,999999999时,数字1~9出现的次数分别均为1,20, 300,……,900000000。现在,我们任意给定一个数n,如2672。假设我们已经算出n=672时,数字1出现的次数为m。则n=2672时,会多出673~2672这些数。数字1出现的次数多出了300*2+1000=1600个,即当n=2672时,数字1出现的次数为m+1600。数字2的情况则不一样了,因为这时最高位的千位数为2,这时并没有多出1000个2,而是673个,所以当n=2672时,数字2出现的次数是多出了300*2+673=1273个。数字3~数字9的情况就又不一样了,这时最高位的千位数并没有出现数字3~9,所以这种情况下,数字出现的次数只多出了300*2=600个。数字0的情况较为特殊,按照数字1~9的情况进行计算,会多算出前导0,所以要把前导0减掉,而每一位多出的前导0是10^i个,i表示第i位数,所以len位数数字0就会多算出(10^len-1)/9个数,例如五位数就会多算出11111个数字零。所以,最后把多算出的前导零减掉就是正确的结了。同理,计算n=672时,可依据n=72时的结果,以此类推。

代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void countNumber(int num, int results[10]) {
	int i, j;
	int v;
	int len = (int)log10(num) + 1;	// num的长度
	int weight[] = {0, 1, 20, 300, 4000, 50000, 600000, 7000000, 80000000, 900000000};
	int digit[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};

	/* 计数清零 */
	for (i = 0; i < 10; i++) {
		results[i] = 0;
	}

	for (i = 0; i < len; i++ ) {
		v = num / digit[i] % 10;	// v表示第i位上的数
		for (j = 0; j < 10; j ++){
			if (j < v) {
				// j小于第i位上的数的情况
				results[j] = results[j] + v * weight[i] + digit[i];
			}
			if (j == v) {
				// j等于第i位上的数的情况
				results[j] = results[j] + v * weight[i] + num % digit[i] + 1;
			}
			if (j > v) {
				// j大于第i位上的数的情况
				results[j] = results[j] + v * weight[i];
			}
		}
	}
	results[0] = results[0] - ((int)pow(10, len) - 1) / 9;	// 减去前导零
}

/* 打印结果到控制台 */
void printResult(int results[10]) {
	int i;
	for (i = 0; i < 10; i++) {
		printf("%d\n", results[i]);
	}
}

int main() {

	int num;
	int results[10];

	printf("请输入一个正整数(0~1^10): ");
	scanf("%d", &num);
	if (num < 0 || num > 10000000000) {
		printf("输入有误!\n");
		exit(0);
	}

	countNumber(num, results);
	
	printf("结果:\n");
	printResult(results);

	return 0;
}
这个算法的时间复杂度为 log10(n),效率明显高于第一个算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值