计数问题:1~n中x出现了多少次?

题目描述
​ 试计算在区间 1 到 n的所有整数中,数字x (0≤x≤9)共出现了多少次?

​ 例如,在 1到11中,即在 1,2,3,4,5,6,7,8,9,10,11 中,数字 1 出现了 4 次。

输入
​ 2个整数n, x,之间用一个空格隔开。

输出
​ 1个整数,表示x出现的次数。

分析

最简单的暴力循环,非常耗时:
在这里插入图片描述
优化方案:
可以逐位分析,比如输入6573,5时。逐位分析如下:(k为从右往左数第几位)

  1. k=0时,???5 ,前三位可取范围为:[0,656] 共657种;高位恰好为657
  2. k=1时,??5? ,前两位可取范围为:[0,65] 66种;最后一位可取范围为:[0,9]共10种,所以共计660种
  3. k=2时,?5??,
    3.1 前1位取范围:[0,5],后两位可取范围为:[0~99],可取范围为 6 ∗ 1 0 2 = 600 6*10^2=600 6102=600种;
    3.2 前1位取6时,即65??,后两位可取范围位[0~73]共74种
    3.3 综上,此时可取范围为674种
  4. k=3时,5???,后三位可取 1 0 3 = 1000 10^3=1000 103=1000
  5. 所以最终结果为:657+660+674+1000=2991种

从上述推导可以尝试推出一般规律,高位取值范围(当前位左边的数字)取决于当前位(cur)和输入x的相对大小,当输入n为6573时,以计算十位为例,cur=7(十位数字),此时cur左边高位 high=65,k=1(右边第一位,从0开始)

  1. cur<x时;假设x=8,??8?, 高位只能取[0,64],共65种,即high种,得到: a n s = ( h i g h ) ∗ 1 0 k = 650 ans=(high)∗10^k=650 ans=(high)10k=650
  2. cur>x时;假设x=5, ??5? , 高位可取[0,65],共66种,即high+1种, 低位10种,得到: a n s = ( h i g h + 1 ) ∗ 1 0 k = 660 ans=(high+1)∗10^k=660 ans=(high+1)10k=660
  3. cur==x时;假设x=7, ??7?, 此时分两种计算: 高位取 [0,64],低位取0~9;高位取65,即 657?,低位取[0,3]共4种。得到: a n s = h i g h ∗ 1 0 k + n ans=high*10^k+n ans=high10k+n% 1 0 k ( 低位 ) + 1 = 65 ∗ 10 + 1 ∗ ( 3 + 1 ) = 654 10^k(低位)+1=65*10+1*(3+1)=654 10k(低位)+1=6510+1(3+1)=654
  4. 特别的,考虑输入x为0的情况, ??0?,按照情形2(cur>x)之前分析,高位取[0,65]共66种,实际上此处不能取0,如果取0则变成了000?,去掉高位的0最后取到的数为0 ~ 9,很明显 1 ~ 9不包含0不符合题意,数字0虽然包含0,但题目要求范围是[1,n],所以此时高位可取范围为[1,65]共65种, 即需要在上面的情况下-1;

解决方案

备注:如果理解了上面的分析过程,那么不用看代码注释。

#include <stdio.h>
#include <math.h>
int func(int n,int x){
	int ans=0;
	int tmp=n;  //备份n,变更tmp不改变n;
	int high,mi,cur,k=0;//k代表从右往左数第几位,从0开始数
	while(tmp){
		cur=tmp%10; //cur 代表当前位数,从个位开始
		tmp=tmp/10; // tmp为商,比如最开始tmp为32487,当k=1时,tmp=324,cur=8;
		
	//分三种情况讨论(下例均讨论计算十位的情况,即k=1):
	//1. 当cur<x时:输入n=32487,x=9;当k=1时,tmp=324,cur=8,要想十位为9,前面三个数的范围只能为[0,323]共324种,个位数为0-9共十种, ==》 tmp*10^k=3240种
	//2. 当cur>x时:输入n=32487,x=7;当k=1时,tmp=324,cur=8,要想十位为7,前面三个数的范围为[0,324]共325种,即tmp+1种;个位同上十种情况,==》 (tmp+1)*10^k=3250
	//3. 当cur==x时:输入n=32487,x=8;当k=1时,tmp=324,cur=8,要想十位为8,前面三个数取[0,323]时,得到 tmp*10^k=3240种,当前三位数为324时,即3248?,cur右边的最大值为7,共8种情况(0~7),==》 tmp*10^k+n%(10^k)=3247种;
	//4. 考虑特殊情况(x输入0时),当n=32487,x=0;当k=1,tmp=324,cur=8,按照前面的讨论2,cur>x,那么 ???0?,前三位取值范围应该为[0,324]共325种,实际上此时不能取0,如果取0,则变成了0000?,去掉首位的0,得到的结果是0~9,1~9明显不包含0不符合题意,0虽然包含0,但题目要求是[1,n],故舍去。即此时前三位取值只能是[1,324]共324种情况
		high=tmp; //不同情况需要的tmp不同,但是tmp作为最外层循环条件,不应该改变,所以另外给一个变量来计算
		if(x==0) high--;
		if(cur>x) high++;
		mi=pow(10,k); //pow默认返回double,可以使用(int)强制转换
		ans+=high*mi;
		if(cur==x) ans+=n%mi+1;	
		k++;
	}
	return ans;
}
int main(){
	int n,x;
	scanf("%d%d",&n,&x);
	printf("%d\n",func(n,x));
	return 0;
}

效率明显好于暴力循环
在这里插入图片描述

小结

这类问题咋一看很复杂,其实只要静下心来仔细分析,也不是什么难题。就是高中数学的基本组合思维。工作中太久不用,生疏了而已

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值