leetcode第233题:
给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。
解法1:暴力
题目中让计算所有小于n的非负整数的出现数字1的个数,那就将每个数字中1的个数都统计数来,然后相加,就得出了所有1的个数。这样思路很简单,代码更简单。下面是实现:
int countDigitOne(int n)
{
int count = 0;
for (int i = 1; i <= n; ++i)
{
int num = i;
//分解每一个数,统计1的个数
while (num)
{
if (num % 10 == 1)
{
++count;
}
num = num / 10;
}
}
return count;
}
这个算法的时间复杂度为O(n^2)。一旦数值过大,消耗的时间就会非常多。
解法2:寻找规律。
将1的总数记为count。分情况讨论。
n为个位数时:
当个位为0时:0
当个位大于等于1时:1
n为十位数时:
我们可以分步计算1在个位上出现的次数,1在10位上出现的次数。
1在个位上出现的次数:
当个位为0时:出现次数为十位上的数字(十位增加一位,个位就需要从0-9循环一次。)
当个位大于等于1时:出现次数为十位上的数字+1。
1在10位上出现的次数:
当十位为0时:1出现的次数为0。
当十位为1时:1出现的次数为(个位的数值加上1)。如:11(1+1=2),包括:10,11。
当十位大于1时:1出现的次数10。只会在10-19的十位出现1。
所以1在十位数中出现的次数的规律为:
当十位为0时:个位上1出现的次数。
当十位为1时:(1)个位为0,1+1=2。(2)个位大于等于1,个数数字+1+个位的1+多循环的1。
当十位大于1时:(1)个位为0,10+十位上的数字。(2)个位大于等于1,10+十位的数字+多循环的1。
例如:
n=12,1出现的次数为(2+1+1+1=5)
n=95,1出现的次数为(10+9+1=20)
n为百位数时:
规律和之前10位上的差不多,我们举一个列子。假设n=625时,计算1出现的个数。
1在个位上出现的次数:62*1+1=63. 1==>10^0
高位为62说明个位一共循环了62次,个位为5,说明还循环了一次0-5,也出现了1次1.即为62*1+1.
1在十位上出现的次数:6*10+10=70. 10==>10^1
高位为6,说明十位一共循环了6次,因为十位循环一次1会出现10次,十位为2,还循环了一次0-2,6*10+10.
1在百位上出现的次数:100. 100==>10^2
高位为0,说明循环了0次,因为百位循环一次1会出现100次,百位为6,还循环了一次0-6. 0*100+100。
所以1在625上出现的次数位:63+70+100=233.
通过上述的分析我们可以发现如下规律:
1在n的第i为出现的次数为:
m为n的第i位的高位表示的数字,k表示第i位低位表示的数字。
第i位的数字为0:m*(10^(i-1))
第i位的数字为1: m*(10^(i-1))+k+1
第i位数字大于1:m*(10^(i-1))+10^(i-1)
我们将10^(i-1)记为t,可得:
第i位的数字为0:mt
第i位的数字为1: mt+k+1
第i位数字大于1:mt+t
我们取3105验证一下:
1在个位出现的次数:i=1,m=310,k=0, 310*(10^(i-1))+10^(i-1)=310+1=311
1在十位出现的次数:i=2,m=31,k=5, 31*(10^(i-1))=31*10=310
1在百位上出现的次数:i=3,m=3,k=5, 3*(10^(i-1))+5+1=3*100+6=306
1在千位上出现的次数:i=4,m=0,k=105, 0*(10^(i-1))+10^(i-1)=0*1000+1000=1000
所以1在3105中出现的次数为:311+310+306+1000=1927.
代码实现:
int countOne(int n)
{
int count = 0;
//m:第i位的高位
//k:第i位的低位
int i = 1, m = n / 10, k = 0;
int num = n,w;
while (num)
{
count = count + m*int(pow(10.0, i-1));
w = num % 10;
if (w == 1)
{
count = count + k + 1;
}
else if (w > 1)
{
count = count + int(pow(10.0, i-1));
}
num = num / 10;
m = m / 10;
i++;
k = n % int(pow(10.0, i - 1));
}
return count;
}
注意输入数据的范围。