時間:2014.09.06
地點:基地
一、問題描述
給定一個十進制正整數N,從1,2,... ,N ,求出所有出現1的個數。比如N=12時,有 1 10 11 12三個數中有1出現,出現次數爲5次,返回結果爲5 。
二、分析
1.暴力法
一般暴力法都比較容易想到,雖然效率上不是很好,但往往能從暴力法中發現一些規律或技巧,所以暴力法還是值得思考的。
首先想到真對某一個數字統計1的個數問題,即是不斷的將數字右移,看最低位是否爲1,這個問題解決後,剩下的問題就是對數據集的每一個數字進行一次遍歷,統計1的總數的簡單問題了。
對於十進制數字右移有一些常用的常識,值得好好加以利用,雖然說是常識,但一不小心容易出錯。
第一條:十進制數字N每右移一位,即是將該十進制數字除以10,每左移移位即是將該十進制數字乘以10。這種技巧在二進制數字表達方式也是如此,二進制數字右移即除以2,左移即乘以2,不過對於二進制而言,有效率更高的移位操作。
第二條:獲取一個十進制數字的最低位即是將該十進制數字模10。在二進制數字中也一樣,不過那裏更爲人所知的是換了一種說法,模2看奇偶性,模2結果有兩種,爲1則最低位爲1,爲奇數,模2結果爲0則最低位爲0,爲偶數。
暴力法只涉這兩條,看十進制數字最低位,因此不斷地右移看最低位即可。代碼如下:
static int get_ones_count(int n)
{
int count = 0;
while (n != 0)
{
count += (n % 10 == 1) ? 1 : 0;
n /= 10;
}
return count;
}
int GetTotalOnes(int N)
{
int count = 0;
for (int i = 1; i <= N; ++i)
{
count += get_ones_count(i);
}
return count;
}
2.深入分析
淺層分析只能得暴力法,粗暴而不給力,如果能利用數字之間的一些規律和信息,變能更有效的解決問題。《編程之美》裏有談論到這個問題的分析,但我更喜歡傾向於藉助高中數學裏的排列組合知識,雖然道理大致相同,但更容易理解,思路如下:
對於一個十進制正整數數不妨設其表示爲N=123x56,百位用x表示,目的是統計集合[ 1,2,...... 123x56 ]中1的個數,將集合中每個數字都用0補齊爲6位數,對結果毫無影響,於是接下來的工作就是統計該集合中所有數的個位、十位、百位、...爲1的情況。假設此時要統計百位爲1的情況。即問題轉換爲:集合中百位爲1且比123x56小的問題。
第一種情況:N的百位x=0,那麼在集合中數的模式爲,百位以上部分可取【000,001,...,122】均可,共123中情況,百位固定(因爲正是要統計百位爲1的情況),百位一下的位沒一位都有10中情況,組合起來即122*10*10種。於是總結爲:
若給定十進制整數中目標位爲0,則所有數中該位上1的個數爲:
目標位高位數字*(10^目標位以下數字的位數)
第二種情況:N的百位x=1,首先上面情況包含在其中,即高位部分取【000,001,...,122】時和上面情況一一樣,另外,當取123也還有部分滿足,這一部分表現爲低位取【00,01,...56】。於是可總結爲:
若給定十進制整數中目標位爲0,則所有數中該位上1的個數爲:
目標位以上的高位數字*(10^目標位以下數字的位數)+目標位以下的低位數字+1
第三種情況:N的百位爲x>1,則更加容易分析,即高位部分取【000,001,002,......123】供,123中情況,低位可能依舊。於是可總結爲:
若給定十進制整數中目標位大於1,則所有數中該位上1的個數爲:
(目標位以上的高位數字+1)*(10^目標位以下數字的位數)
有了這些分析,代碼很容易寫出:
static int get_ones_count(int n)
{
int count = 0;
while (n != 0)
{
count += (n % 10 == 1) ? 1 : 0;
n /= 10;
}
return count;
}
int GetTotalOnes(int N)
{
int count = 0;
for (int i = 1; i <= N; ++i)
{
count += get_ones_count(i);
}
return count;
}
第三條:獲取一個十進制數字的低位部分即用源數字減去源數字低位數字清零的數。
第四條:獲得一個十進制數字的高位部分即用該數字右移。