【剑指Offer】整数中1出现的次数(从1到n整数中1出现的次数)

【题目】

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

【思路】

两种方法!分别是暴力解决和使用数学技巧。

【代码】

1. 暴力解决

从1到n遍历,每次通过对10求余数判断整数的个位数字是不是1,大于10的除以10之后再循环判断。我们对每个数字都要做除法和求余运算以求出该数字中1出现的次数。如果输入数字n,n有O(log10n+1)O(log_{10}n+1)位,我们需要判断每一位是不是1,那么时间复杂度为O(nlog10n)O(n*log_{10}n)

class Solution {
public:
	int NumberOf1Between1AndN_Solution(int n){
		if (n <= 0) {
			return 0;
		}
		int result = 0;
		for (int i = 1; i <= n; i++) {
			int tmp = i;
			while (tmp != 0) {
				if (tmp % 10 == 1) {
					result++;
				}
				tmp /= 10;
			}
		}
		return result;
	}
};

2. 数学技巧

从网上可以看到大量用编程之美给出的规律解题的思路,看了挺多篇文章,这里说说自己的理解。

这里以 736 为例进行分析:

1)个位

我们把736分成两个部分,一部分是个位左边的73,另一部分是个位右边的(因为没有,这里就不用考虑了)。个位可以从0~9变化10次,此记为一轮,每一轮出现一次数字1。然后,会出现74轮(第一部分73从0变化到73)。因此可以得出个位出现1的次数为74。

三种情况:

  • 当个位大于1,即上面的情况。可以得出个位出现1的次数为74。

  • 当个位等于1,和上面的情况相同。可以得出个位出现1的次数为74。

  • 当个位等于0时,即730。第74轮个位到0就结束了,所以个位没有出现1。因此可以得出个位出现1的次数为73。

2)百位

我们继续把736分成两个部分,一部分是个位左边的7,另一部分是个位右边的6。百位可以从09变化10次,个位同样是可以从09变化10次,个位变化一轮百位变化一次,百位变化一轮更高位才变化一次。另外百位变化一轮,1会出现10次,这是因为在这一轮中百位为1时,个位可以从0~9变化10次。 然后,百位会出现8轮(第一部分7从0变化到7)。因此可以得出百位出现1的次数为8*10=80次。

三种情况:

  • 当百位大于1时,即上面的情况。可以得出百位出现1的次数为80。
  • 当百位等于1时,即716。那么第8轮中百位出现1的次数和个位有关,即出现了7次(个位从0~6变化7次)。因此可以得出百位出现1的次数为7*10+7=77。
  • 当百位等于0时,即706。那么第8轮中百位到0就结束了,不会出现1。因此可以得出百位出现1的次数为7*10=70。

3)更高位

更高位的处理情况和百位的情况类似。


下面贴上AC代码的两种写法,时间复杂度都是O(log10n)O(log_{10}n)

写法一:

class Solution {
public:
	int NumberOf1Between1AndN_Solution(int n){
		if (n <= 0) {
			return 0;
		}
		int result = 0, tmp = n, base = 1;
		while (tmp > 0) {
			int a = tmp % 10;  // 本位的数
			tmp = tmp / 10;  // 高位(本位左边的数)

			result += tmp*base;  // a==0的情况
			if (a == 1) {
				result += n%base + 1;
			}
			else if (a > 1) {
				result += base;
			}
			base *= 10;
		}
		return result;
	}
};

写法二(简洁优美):

class Solution {
public:
	int NumberOf1Between1AndN_Solution(int n){
		// 统计次数
		int result = 0;
		for (int i = 1; i <= n; i *= 10) {
			// 计算高位和低位
			//之所以补8,是因为当百位为0,则a/10==(a+8)/10,
			//当百位>=2,补8会产生进位位,效果等同于(a/10+1)
			int a = n / i, b = n % i;
			result += (a + 8) / 10 * i + ((a % 10 == 1) ? (b + 1) : 0);
		}
		return result;
	}
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章