页码数字统计问题(数字统计问题)

本文介绍了两种计算书页码中0-9每个数字出现次数的方法,包括暴力遍历法和拆数计算法,详细解析了算法思路并提供了C语言实现代码。同时,对两种方法的时间复杂度进行了分析。测试用例和复杂度分析有助于理解算法效率。
摘要由CSDN通过智能技术生成

问题描述

一本书的页码从自然数1开始顺序编码直到自然数n。书的页码按照通常的习惯编码,每个页码都不含有多余的前导数字0。现在给出表示书的总页码的自然数n,计算书的全部页码中分别用到多少次数字0,1,2,3,…, 9。(或者计算说0-9数字出现了多少次)。

例如,给出一个数n=12,则出现的数有1,2,3,4,5,6,7,8,9,10,11,12。这些数中0-9分别出现的次数为:(由上到下分别表示0-9出现的次数,后文程序结果都采用这样的格式)
在这里插入图片描述

题目源于:王晓东.《计算机算法设计与分析》.第5版习题1-1

算法思路与实现代码

方法一:暴力遍历法

思路:设置一个长度为10的数组作为0-9每个数出现次数的计数器,将页码从1~n遍历一遍,提取每一个数的每一位,根据每一位数的值给对应数字计数器加1,最后在循环结束后通过计数器内的结果确定每个数字的出现次数。

代码1

#include<stdio.h>
int main(){
	int arr[10]={0};//存储0-9各个数的出现次数,初始化为零
	int n; scanf("%d",&n);//输入n
	for(int i=1;i<=n;i++){
		//拆分每一个数的每一位
		int num[10]={0};//存每次数的每一位的值 
		int len=0; //这个数的位数 
		int m = i;
		while(m/10){//采用除10取余的方式,从个位开始获取每一位的值
			num[len++] = m%10;
			m = m/10;
		}
		num[len++] = m;
		
		for(int j=0;j<len;j++){//对应计数器+1
			arr[ num[j] ]++;
		}
	}
	for(int i=0;i<10;i++){//打印结果
	printf("%d\n",arr[i]);
	}
return 0;
}

方法二:拆数计算法

思路:统计0-9每个数,分别在每一位上的出现次数。即对于一个总位数为len的数n,依次计算其第一位(个位),第二位(十位),第三位…,第len位上,0-9每个数出现的次数,再统计每次的计算结果。

每一位上各个数字出现的次数与四个条件有关:这一位上的数的值V(Value),这一位数前面一串数的值F(Front),这一位数后面一串数的值R(Rear),这一位数的位置P。以n=12345的第三位(百位)为例,这一位上0-9各个数出现的个数分别与:V=3,F=12,R=45,P=3.有关。
对于输入数n,其任意位都有VFRP(对于最高位,取F=0;对于第一位,取R=0),考虑其任意一位,有如下四种规则:
① 0 − 9 各 数 在 该 为 的 出 现 次 数 至 少 为 : F × 1 0 P − 1 ② 对 于 0 − 9 中 大 于 V 的 数 , 出 现 次 数 不 必 额 外 增 加 。 ③ 对 于 0 − 9 中 等 于 V 的 数 , 出 现 次 数 与 还 R 有 关 , 需 加 上 : R + 1 ( 0 到 x , 共 有 x + 1 个 数 ) ④ 对 于 1 − 9 中 小 于 V 的 数 , 需 加 上 : 1 0 P − 1 \begin{aligned} ①&0-9各数在该为的出现次数至少为:F×10^{P-1}\\ ②&对于0-9中大于V的数,出现次数不必额外增加。\\ ③&对于0-9中等于V的数,出现次数与还R有关,需加上:R + 1(0到x,共有x+1个数)\\ ④&对于1-9中小于V的数,需加上:10^{P-1} \end{aligned} 09F×10P109V09VRR+10xx+119V10P1
第四点中不包括0,是因为0不可出现在首位,即没有0,024,0324等数,例如当n=123,考虑P=2(十位),在已考虑①的前提下,考虑④,显然当1<2,1出现的次数需要额外加10(因为有10,11,12,…,20的过程);而对于0<2,0出现的次数不能加10(因为没有00,01,02,…,10的过程,只有1,2,3,4…,10,在这个过程十位没有出现0)。

所以只需要得到n每一位上的VFRP,再设置一个长度为10的数组作为0-9每个数出现次数的计数器,循环计算并统计各个位上每个数的出现次数,最后通过计数器内的结果确定每个数字的出现次数。

代码2

#include<stdio.h>
int main(){
	int arr[10]={0};//保存每一个数出现次数的计数器数组 
	int pow10[10]={1};	//每一位的权值,即10^(P-1)
	for(int i=1;i<10;i++)pow10[i]=10*pow10[i-1];
		int n; scanf("%d",&n); //输入n
	int num[3][10]={0};
	//[0]存每一位的值V
	//[1]存每一位前面一串数的值F
	//[2]存每一位后面一串数的值R 
	
	int len=0; //记录位数 P
	while(n/10){ // 拆分n的每一位,获取VFR
		num[0][len] = n%10;
		num[2][len+1] += n%10*pow10[len] + num[2][len];
		n = n/10;
		num[1][len++]=n;
	}
	num[0][len++] = n;

		int i = 0; 
		for(i=0;i<len;i++){ 
			for(int j=0;j<10;j++){
				arr[j] += num[1][i]*pow10[i]; 
				//对于0-9中等于V的数,出现次数与还R有关,需加上:R + 1
				if(j==num[0][i]){
					arr[j] += num[2][i] + 1;
				}
				// 对于1-9中小于V的数,需加上:10^(P-1)
				else if(j<num[0][i] && j!=0){ 
					arr[j] += pow10[i];
				}
			}
		}
		for(int i=0;i<10;i++)printf("%d\n",arr[i]);
return 0;
}

代码测试

测试用例1:12345
在这里插入图片描述
测试用例2:45678
在这里插入图片描述

算法复杂度分析

方法一:
此算法一共包括三个核心循环,最外层大循环1 ~ n 需要循环n次。大循环内有两个小循环顺序执行,一个循环是提取每个数的每一位,循环次数由每次被提取数的位数决定,而被提取数的最大位数不大于 log ⁡ 10 n \log_{10} n log10n;第二个循环是根据提取的每一位,给对应计数器+1,循环次数也由被提取数的位数决定,即不大于 log ⁡ 10 n \log_{10} n log10n,所以程序总的时间复杂度为O ( 2 log ⁡ 10 n ⋅ n 2\log_{10} n · n 2log10nn), 即O ( n ⋅ log ⁡ 10 n n·\log_{10}n nlog10n)。

方法二:
此算法一共包括三个核心循环,第一个循环拆分n的每一位,并求各种值,一共循环 log ⁡ 10 n \log_{10} n log10n次;第二个循环逐位计算每一位上出现各种数的次数,共循环 log ⁡ 10 n \log_{10} n log10n次,其内嵌套着第三个循环,计算从0-9各个数的具体出现次数,共循环10次。所以程序总的时间复杂度为O( log ⁡ 10 n + 10 log ⁡ 10 n \log_{10} n + 10\log_{10} n log10n+10log10n ),即O( log ⁡ 10 n \log_{10} n log10n)。

分享的同时记录自己的学习,感谢阅读,有问题还请交流指正。^ ^

  • 18
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值