最简单直接的方法就是我们循环所有的1~n中的每个number,计算每个number出现的次数
#include <iostream>
using namespace std;
// 调试开关
#define __tmain main
#ifdef __tmain
#define debug cout
#else
#define debug 0 && cout
#endif // __tmain
class Solution
{
public:
/**
* [NumberOf1Between1AndN_Solution 统计1到n的整数中1出现的次数]
* @Author gatieme
* @DateTime 2016-05-01T22:03:43+0800
* @param n [整数n]
* @return [1到n的整数中1出现的次数]
*/
int NumberOf1Between1AndN_Solution(int n)
{
int count = 0;
for(int i = 1;
i <= n;
i++)
{
count += NumberOf1(i);
}
return count;
}
protected:
/**
* [NumberOf1 统计number中1出现的次数]
* @Author gatieme
* @DateTime 2016-05-01T22:01:08+0800
* @param number [待处理的数字]
* @return [number中1出现的次数]
*/
int NumberOf1(int number)
{
int count = 0;
while(number != 0)
{
if(number % 10 == 1)
{
count++;
}
number /= 10;
}
return count;
}
};
int __tmain( )
{
Solution solu;
cout <<solu.NumberOf1Between1AndN_Solution(12) <<endl;
cout <<solu.NumberOf1Between1AndN_Solution(100) <<endl;
cout <<solu.NumberOf1Between1AndN_Solution(1000) <<endl;
return 0;
}
此方法简单,容易理解,但它的问题是效率,时间复杂度为$O(N * lgN)$,N比较大的时候,需要耗费很长的时间。
我们重新分析下这个问题,
对于任意一个个位数n,只要n>=1,它就包含一个"1";
n<1,即n=0时,则包含的"1"的个数为0。
于是我们考虑用分治的思想将任意一个n位数不断缩小规模分解成许多个个位数,这样求解就很方便。
但是,我们该如何降低规模?
仔细分析,我们会发现,
例如,
对于n=12,可以拆分为01
09,1012,即 f(12) = f(10 - 1) + f(12 - 10) + 3,其中3是表示最高位为1的数字个数,这里就是10,11,12;对于n=132,可以拆分为0
99,100132,即f(132)=f(100 -1) + f(132 - 100) + 33,33代表最高位为1的数字的个数,这里就是100~132百位数字的1出新了33次对于232,可以拆分为0
99,100232,即f(232) = 2*f(100 - 1) + f(32) + 100,因为232大于199,所以它包括了所有最高位为1的数字即100~199,共100个。
综上,我们分析得出,最后加的常数C只跟最高位n1是否为1有关
当最高位为1时,常数C为原数字N去掉最高位后剩下的数字+1,如N=12时,$C = 2 + 1 = 3$,N=132时,$C = 32 + 1 = 33$
当最高位大于1时,常数C为$10^(bit-1)$,其中bit为N的位数,如N=232时,bit=3,$C = 10^(bit-1) = 10^2 = 100$。 于是,我们可以列出递归方程如下:
if(n1 == 1)
f(n) = f(10bit-1) + f(n - 10bit) + n - 10bit+ 1;
else
f(n) = n1*f(10bit-1) + f(n – n1*10bit) + 10bit;
进一步可以归结为
f(n) = n1*f(10bit-1) + f(n – n1*10bit) + LEFT;
其中
if(n1 == 1)
LEFT = n - 10bit+ 1;
else
LEFT = 10bit;
此算法的优点是不用遍历1~N就可以得到f(N)。经过我测试,此算法的运算速度比解法一快了许多许多,数字在1010内时,算法都可以在毫秒级内结束,
class Solution
{
public:
/**
* [NumberOf1Between1AndN_Solution 统计1到n的整数中1出现的次数]
* @Author gatieme
* @DateTime 2016-05-01T22:03:43+0800
* @param n [整数n]
* @return [1到n的整数中1出现的次数]
*/
int NumberOf1Between1AndN_Solution(int n)
{
return CountOne(n);
}
int CountOne(int n)
{
long count = 0;
if (n == 0)
{
count = 0;
}
else if (n > 1 && n < 10)
{
count = 1;
}
else
{
// 计算n的位数
long highest = n; //表示最高位的数字
int bit = 0;
while (highest >= 10)
{
highest = highest / 10;
bit++;
}
// 循环结束时, bit表示n的位数, 而highest是其最高位的数字
//int weight = (int)Math.Pow(10, bit);//代表最高位的权重,即最高位一个1代表的大小
int weight = pow(10, bit);//代表最高位的权重,即最高位一个1代表的大小
if (highest == 1)
{
count = CountOne(weight - 1)
+ CountOne(n - weight)
+ (n - weight + 1);
}
else
{
count = highest * CountOne(weight - 1)
+ CountOne(n - highest * weight)
+ weight;
}
}
return count;
}
};
或者直接
class Solution
{
public:
/**
* [NumberOf1Between1AndN_Solution 统计1到n的整数中1出现的次数]
* @Author gatieme
* @DateTime 2016-05-01T22:03:43+0800
* @param n [整数n]
* @return [1到n的整数中1出现的次数]
*/
int NumberOf1Between1AndN_Solution(int n)
{
return CountOne(n);
}
int CountOne(int n)
{
debug <<"now N = " <<n <<endl;
long count = 0;
if (n == 0)
{
debug <<"Fun(0) = " <<0 <<endl;
count = 0;
}
else if (n > 1 && n < 10)
{
debug <<"Fun(" <<n <<") = " <<1 <<endl;
count = 1;
}
else
{
// 计算n的位数
long highest = n; //表示最高位的数字
int bit = 0;
while (highest >= 10)
{
highest /= 10;
bit++;
}
// 循环结束时, bit表示n的位数, 而highest是其最高位的数字
//int weight = (int)Math.Pow(10, bit);//代表最高位的权重,即最高位一个1代表的大小
int weight = pow(10, bit);//代表最高位的权重,即最高位一个1代表的大小
int left = (highest == 1) ? (n - weight + 1) : weight;
debug <<"Fun(" <<n <<") = "
/*
<<"Fun(10^" <<bit <<" - 1) + "
<<"Fun(" <<n <<" - 10^" <<bit <<") + "
<<weight <<" = "
*/
<<highest <<" * Fun(" <<weight <<" - 1) + "
<<"Fun(" <<n <<" - " <<highest <<" * " <<weight <<") + "
<<weight <<endl;
count = highest * CountOne(weight - 1)
+ CountOne(n - highest * weight)
+ left;
debug <<"Now will continue " <<weight - 1 <<", " <<n - highest * weight <<endl <<endl;
}
return count;
}
};
这种思路每次去掉最高位然后进行递归,递归的次数和位数相同。
一个数字n有$O(logN)$位,因此算法的时间复杂度是$O(logN)$
总结
f(n) = n1f(10bit-1) + f(n – n110bit) + LEFT;
如果最高为n1 = 1, 如132, 则剩余100~132的最高位百位的1出现了LEFT = n - 10 * bit+ 1次
如果最高位n1 > 1, 如232, 则100~199(其实是到232)最高位的1出现了LEFT=10*bit次
解法二(分治法)告诉我们1~ N中"1"的个数跟最高位有关,那我们换个角度思考,给定一个N,我们分析1~N中的数在每一位上出现1的次数的和,看看每一位上"1"出现的个数的和由什么决定。
1位数的情况
2位数的情况
N=13,个位数出现的1的次数为2,分别为1和11,十位数出现1的次数为4,分别为10,11,12,13,所以f(N) = 2+4。
N=23,个位数出现的1的次数为3,分别为1,11,21,十位数出现1的次数为10,分别为10~19,f(N)=3+10。
由此我们发现,
个位数出现1的次数不仅和个位数有关,和十位数也有关
如果个位数大于等于1,则个位数出现1的次数为十位数的数字加1;
如果个位数为0,个位数出现1的次数等于十位数数字。
而十位数上出现1的次数也不仅和十位数相关,也和个位数相关
如果十位数字等于1,则十位数上出现1的次数为个位数的数字加1
假如十位数大于1,则十位数上出现1的次数为10。
3位数的情况
N=123,个位出现1的个数为13:1,11,21,…,91,101,111,121。十位出现1的个数为20:1019,110119。百位出现1的个数为24:100~123。
4,5位数的情况
我们可以继续分析4位数,5位数,推导出下面一般情况:
假设N,我们要计算百位上出现1的次数,将由三部分决定:百位上的数字,百位以上的数字,百位以一下的数字。
如果百位上的数字为0,则百位上出现1的次数仅由更高位决定,比如12013,百位出现1的情况为100199,11001199,21002199,…,1110011199,共1200个。等于更高位数字乘以当前位数,即12 * 100。
如果百位上的数字大于1,则百位上出现1的次数仅由更高位决定,比如12213,百位出现1的情况为100199,11001199,21002199,…,1110011199,12100~12199共1300个。等于更高位数字加1乘以当前位数,即(12 + 1)*100。
如果百位上的数字为1,则百位上出现1的次数不仅受更高位影响,还受低位影响。例如12113,受高位影响出现1的情况:100199,11001199,21002199,…,1110011199,共1200个,但它还受低位影响,出现1的情况是12100~12113,共114个,等于低位数字113+1。
class Solution
{
public:
/**
* [NumberOf1Between1AndN_Solution 统计1到n的整数中1出现的次数]
* @Author gatieme
* @DateTime 2016-05-01T22:03:43+0800
* @param n [整数n]
* @return [1到n的整数中1出现的次数]
*/
int NumberOf1Between1AndN_Solution(int n)
{
long count = 0;
long i = 1;
long current = 0,after = 0,before = 0;
while((n / i) != 0)
{
before = n / (i * 10);
current = (n / i) % 10;
after = n - (n / i) * i;
if (current > 1)
{
count = count + (before + 1) * i;
}
else if (current == 0)
{
count = count + before * i;
}
else if(current == 1)
{
count = count + before * i + after + 1;
}
debug <<before <<" " <<current <<" " <<after <<", count = " <<count <<endl;
i = i * 10;
}
return count;
}
};
以下分析摘自每日一题(58) - 从1到n整数中1出现的次数,方便大家理解 假设数字为abcde,对于abcde中的每一个数字,可以根据该数字与1的关系,求在该数字对应位置上1出现的次数。
具体来说:
假设我们要求百位出现1的次数,此时我们可以根据c与1的关系,求出百位1出现的次数。 (1)如果c = 0,则1出现的次数等于ab * 100,即 c前面的数 * c对应的基数 在该情况下,百位出现1的次数只与c前面的数有关。 (2)如果c = 1,则1出现的次数等于(ab * 100) + (de + 1),即(c前面的数 * c对应的基数) +( c后面的数 + 1) 在该情况下,百位出现1的次数与c前面和c后面的数有关。 (3)如果c = 2,则1出现的次数等于(ab + 1)100,即(c前面的数 +1) c对应的基数 在该情况下,百位出现1的次数只与c前面的数有关。 举例: 对于12013,在百位处出现1的次数= 12 * 100 = 1200次 其分别是,00100 -00199,01100 -01199,02100 - 02199,...,11100 - 11199。 (1)由于因为百位 = 0,则以12为开头的数不会含有1。 (2)百位前面的数值能有00 - 11,即出现12次。由于百位后的数字有两位,因此其基数为100。 即,以00 - 11为开头的数,后面都可以由00 变化到99,即包含了所有1的情况。
对于12113,在百位处出现1的次数= (12 * 100 ) + (13+1)= 1214次 (1)包含足够1的情况:00100 - 00199,01100 - 01199,02100 - 02199,...,11100 - 11199 (2)包含部分1的情况:12100 - 12113 即,由于百位 = 1,则以00 - 12为开头的数在百位都含有1。 (1)当百位前面的数字为00-11时,此情况包含了所有1的情况。(00-99) (2)当百位前面为数字为12时,此情况仅仅包含了部分1的情况(00-13)
对于12213,在百位处出现1的次数= (12+1) * 100 = 1300次 其分别是,00100 - 00199,01100 - 01199,02100 - 02199,...,11100 - 11199,12100 - 12199。 此时,百位前面的数无论怎么变化,后面都可以由00 变化到99,即包含了所有1的情况。