面试题43:从1 到 n 整数中1出现的次数

博客围绕计算1到n中数字1出现的次数展开。先介绍直观方法,时间复杂度为O(N*logN),易超时。接着重点讲解找规律的方法,结合实例分析,总结出计算数字n第i位上1个数的规律,该算法时间复杂度为O(logN),此规律也适用于1到9的其他数字。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

思路:

方法1:最直观的是,对于1~n中的每个整数,分别判断n中的1的个数,每次通过对10求余判断整数的个位数字是不是1,如果这个数字大于10,则除以10之后再判断个位数字是不是1。具体见《剑指offer》。这种方法的时间复杂度为O(N*logN),当N比较大的时候,一般会超时。

  方法2:这种类别的题目,如果直观求解不行的话,那么通常是进行找规律,转化成一个数学问题。这道题目在《编程之美》上有着比较详细的描述,下面就结合一个实例进行具体的分析:

在分析之前,首先需要知道一个规律:

  • 从 1 至 10,在它们的个位数中,数字1出现了 1 次。
  • 从 1 至 100,在它们的十位数中,数字1出现了 10 次。
  • 从 1 至 1000,在它们的百位数中,数字1出现了 100 次。

依此类推,从 1 至 10^i,在它们向右数第二位中,数字1出现了10 ^ (i - 1)次。

对于 n = 2134,要找到从1 ~ 2134这2134个数字中所有1的个数。我们可以对2134进行逐位分析:

(1)在个位上,从1~2130,包含213个10,因此数字1出现了213次,剩下的数字2131、2132、2133、2134中个位数上只有2131包含数字1,剩下的都不包含。所以个位数上的数字1的总数为213 + 1 = 214。

(2)在十位上,从1 ~ 2100,包含了21个100,因此数字1出现了21 * 10 = 210次,剩下的数字从2101 ~ 2134,只有2110 ~ 2119这10个数字中十位的数字为1,所以十位上的数字1的总数为210 + 10 = 220。

(3)在百位上,从1 ~ 2000,包含了2个1000,因此数字1出现了2 * 100 = 200次,剩下的数字从2001 ~ 2134,只有2100 ~ 2134这35个数字中的百位的数字为1,所以百位数上数字1的总数为200 + 35= 235。

(4)在千位上,包含了0个10000,因此数字1出现了0 * 1000 = 0次,剩下的数字中只有1000 ~ 1999这1000个数字中的千位的数字为1,所以千位上的数字1的总数为1000。

因此从1 ~ 2134这n个数字中,数字出现的总的次数为 214 + 220 + 235 +1000 = 1669。

总结一下以上的步骤,可以得到这么一个规律:

对于数字n,计算它的第i(i从1开始,从右边开始计数)位数上包含的数字1的个数:

假设第i位上的数字为x的话,则

1.如果x > 1的话,则第i位数上包含的1的数目为:(高位数字 + 1)* 10 ^ (i-1)  (其中高位数字是从i+1位一直到最高位数构成的数字)

2.如果x < 1的话,则第i位数上包含的1的数目为:(高位数字 )* 10 ^ (i-1)

3.如果x == 1的话,则第i位数上包含1的数目为:(高位数字) * 10 ^ (i-1) +(低位数字+1)   (其中低位数字时从第i - 1位数一直到第1位数构成的数字)

 

代码如下:

int NumberOfDigitOne(int n) {
    	if (n < 0)
    		return 0;
    	int i = 1;
    	int high = n;
    	int cnt = 0;
    	while (high != 0)
    	{
    		high = n / pow(10, i);//high表示当前位的高位
		int temp = n / pow(10, i - 1);
		int cur = temp % 10;//cur表示第i位上的值,从1开始计算
		int low = n - temp * pow(10, i - 1);//low表示当前位的低位
		if (cur < 1)
		{
			cnt += high * pow(10, i - 1);
		} 
		else if(cur > 1)
		{
			cnt += (high + 1) * pow(10, i - 1);
		} else {
			cnt += high * pow(10, i - 1);
			cnt += (low + 1);
		}
		i++;
	}
	return cnt;
}

该算法的时间复杂度为O(logN)。

 

上述算法是计算数字1的个数,对于其他的数字k(1~9中的任意数字),这个规律同样也是适用的,分析的过程也是一样的,只不过将上述的1改为k即可。下面代码是计算1 ~n中任意数字出现的次数:

int NumberOfDigitX(int n, int x) {
    	if (x > 9 || x < 0 || n < 0)
    		return 0;
    	int i = 1;
    	int high = n;
    	int cnt = 0;
    	while (high != 0)
    	{
    		high = n / pow(10, i);//high表示当前位的高位
		int temp = n / pow(10, i - 1);
		int cur = temp % 10;//cur表示第i位上的值,从1开始计算
		int low = n - temp * pow(10, i - 1);//low表示当前位的低位
		if (cur < x)
		{
			cnt += high * pow(10, i - 1);
		} 
		else if(cur > x)
		{
			cnt += (high + 1) * pow(10, i - 1);
		} else {
			cnt += high * pow(10, i - 1);
			cnt += (low + 1);
		}
		i++;
	}
	return cnt;
}

求1~n中数字0的个数

int NumberOfDigitZero(int n) {
    	if (n < 0)
    		return 0;
    	int i = 1;
    	int high = n;
    	int cnt = 0;
    	while (high != 0)
    	{
    		high = n / pow(10, i);//high表示当前位的高位
		int temp = n / pow(10, i - 1);
		int cur = temp % 10;//cur表示第i位上的值,从1开始计算
		int low = n - temp * pow(10, i - 1);//low表示当前位的低位
		if (cur < 0)//实际上这步不会执行
		{
			cnt += (high - 1) * pow(10, i - 1);
		} 
		else if(cur > 0)
		{
			cnt += (high) * pow(10, i - 1);
		} else {
			cnt += (high - 1) * pow(10, i - 1);
			cnt += (low + 1);
		}
		i++;
	}
	return cnt;
}

参考博客:

https://www.cnblogs.com/wangkundentisy/p/8946858.html

http://www.cnblogs.com/cyjb/p/digitOccurrenceInRegion.html

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值