剑指Offer——计算1至n中数字x出现的次数

一、1的数目

编程之美上给出的规律:

1. 如果第i位(自左至右,从1开始标号)上的数字为0,则第i位可能出现1的次数由更高位决定(若没有高位,视高位为0),等于更高位数字乘以当前位数的权重10^(i-1)。

2. 如果第i位上的数字为1,则第i位上可能出现1的次数不仅受更高位影响,还受低位影响(若没有低位,视低位为0),等于更高位数字乘以当前位数的权重10^(i-1) + (低位数字+1)。

3. 如果第i位上的数字大于1,则第i位上可能出现1的次数仅由更高位决定(若没有高位,视高位为0),等于(更高位数字+1)乘以当前位数的权重10^(i-1)。

二、X的数目

这里的X∈[1, 9],因为X=0不符合下列规律,需要单独计算。

首先要知道以下规律:

  • 从1至10,在它们的个位数中,任意的X都出现了1次。
  • 从1至100,在它们的十位数中,任意的X都出现了10次。
  • 从1至1000,在它们的百位数中,任意的X都出现了100次。
以此类推,从1至10^i,在它们的左数第二位(右数第i位)中,任意的X都出现了10^(i-1)次。
这个规律很容易验证,这里不再多做说明。
接下来以n=2593,X=5为例来解释如何得到数学公式。从1至2593中,数字5总计出现了813次,其中有259次出现在个位,260次出现在十位,294次出现在百位,0次出现在千位。
现在依次分析这些数据,首先是个位。从1至2590中,包含了259个10,因此任意的X都出现了259次。最后剩余的三个数2591、2592和2593,因为它们最大的个位数字3<X,因此不会包含任何5。( 也可以这么看,3<X,则个位数上可能出现的X的次数仅由更高位决定,等于更高位数字(259)×10^(1-1)=259)。
然后是十位。从1至2500中,包含了25个100,因此任意的X都出现了25×10=250次。剩下的数字是从2501至2593,它们最大的十位数字9>X,因此会包含全部10个5。最后总计250+10=260。( 也可以这么看,9>X,则十位上可能出现的X的次数仅由更高位决定,等于更高位数字(25+1)×10^(2-1)=260)。
接下来是百位。从1至2000中,包含了2个1000,因此任意的X都出现了2×100次。剩下的数字是从2001至2593,它们最大的百位数字5==X,这时情况就略微复杂,它们的百位肯定是包含5的,但不会包含全部100个。如果把百位是5的数字列出来,是从2500至2593,数字的个数与百位和十位数字相关,是93+1=94。最后总计200+94=294。( 也可以这么看,5==X,则百位上可能出现X的次数不仅受更高位影响,还受低位影响,等于更高位数字(2)×10^(3-1)+(93+1)=294)。
最后是千位。现在已经没有更高位,因此直接看最大的千位数字2<X。所以不会包含任何5。( 也可以这么看,2<X,则千位上可能出现的X的次数仅由更高位决定,等于更高位数字(0)×10^(4-1)=0)。
到此为止,已经计算出全部数字5的出现次数。
总结一下上面的算法,可以看到,当计算右数第i位包含的X的个数时:
  1. 取第i位左边(高位)的数字,乘以10^(i-1),得到基础值a。
  2. 取第i位数字,计算修正值:如果大于X,则结果为a+10^(i-1);如果小于X,则结果为a;如果等于X,则取第i位右边(低位)数字,设为b,最后结果为a+b+1。
相应的代码也非常简单,效率也非常高,时间复杂度只有O(lgn)。
public class NumberOfXBetween1AndN {
	public int numberOfXBetween1AndN(int n, int x) {
		if(n < 0 || x < 1 || x > 9)
			return 0;
		int high, low, curr, tmp, i = 1;
		high = n;
		int total = 0;
		while(high != 0) {
			high = n / (int)Math.pow(10, i); // 获取第i位的高位
			tmp = n % (int)Math.pow(10, i);
			curr = tmp / (int)Math.pow(10, i-1); //获取第i位
			low = tmp % (int)Math.pow(10, i-1); //获取第i位的低位
			if(curr == x)
				total += high*(int)Math.pow(10, i-1) + low + 1;
			else if(curr > x)
				total += (high+1)*(int)Math.pow(10, i-1);
			else
				total += high*(int)Math.pow(10, i-1);
			i++;
		}
		return total;
	}
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值