1. 程式人生 > >Python解決 從1到n整數中1出現的次數

Python解決 從1到n整數中1出現的次數

很好 time return 面試題 span mas 1=1 大於 獲取

最近在看《劍指Offer》,面試題32的題目:輸入一個整數n,求從1到n這n個整數的十進制表示中1出現的次數。例如輸入12,從1到12這些整數中包含1的數字有1、10、11和12,1一共出現了5次。

對於書中說的不考慮時間效率的解法很好理解,可以直接完成,但是對於書中介紹的另一種方法,沒有理解,於是按照自己的思路進行了分析。

1位數,1-9中,1一共出現了1次;

2位數,10-99中,10-19的十位上一共出現了10*1=10次,對於每個十位開頭的數字10-19、20-29,每個數個位上出現的是1-9中1出現的次數,共有9個區間9*1=9次;

3位數,100-999,100-199百位上出現了10**2=100次,對於每個百位數開頭,例如100-199,200-299,低位上其實就是0-99這個區間上1出現的次數,一共9個區間 9*19=171次;

由此推測,對於1-9,10-99,100-999,每個n位數中包含1的個數公式為:

f(1) = 1

f(2) = 9 * f(1) + 10 ** 1

f(3) = 9 * f(2) + 10 ** 2

f(n) = 9 * f(n-1) + 10 ** (n-1)

通過以上分析,我們可以確定對於任意一個給定的數,例如23456這個5位數,10000之前的數中包含的個數是確定的了,為f(1)+f(2)+f(3)+f(4),這是一個遞歸的過程,對此可以求出1-4位中包含1的總數,函數如下所示:

 1 def get_1_digits(n):
2 """ 3 獲取每個位數之間1的總數 4 :param n: 位數 5 """ 6 if n <= 0: 7 return 0 8 if n == 1: 9 return 1 10 current = 9 * get_1_digits(n-1) + 10 ** (n-1) 11 return get_1_digits(n-1) + current

通過上面的分析,我們知道了23456中,1-10000之間一共出現了多少個1.下一步需要分析10000-23456中包含的1.

我們首先把最高位單獨拿出來分析一下,求出最高位上1的個數,如果最高位是1,則最高位上一共會出現的1的次數是低位上數字+1,例如12345,最高位上一共出現了2346個1;如果最高位大於1,則會一共出現的次數是10000-19999一共10**4個數。

然後,根據最高位的不同,計算出該高位前面的相同位數範圍中的所有數中1的個數。例如對於34567,需要計算出10000-19999,20000-29999中一的個數,這時候計算一的個數,也就是計算0-9999中1的個數,這就可以轉化成上面的f(n)來計算了,調用上面函數可以直接得到,然後用得到的值和最高位和1的差值(這裏最高位是3)相乘就可以了。

分析完上面的部分後,我們現在只剩下最高位後面的部分了,我們發現剩下的部分還是一個整數,例如23456剩下了3456,這時候直接使用遞歸處理剩下的3456就行了。具體代碼如下:

 1 def get_1_nums(n):
 2     if n < 10:
 3         return 1 if n >= 1 else 0
 4     digit = get_digits(n)  # 位數
 5     low_nums = get_1_digits(digit-1)  # 最高位之前的1的個數
 6     high = int(str(n)[0])  # 最高位
 7     low = n - high * 10 ** (digit-1)  # 低位
 8 
 9     if high == 1:
10         high_nums = low + 1  # 最高位上1的個數
11         all_nums = high_nums
12     else:
13         high_nums = 10 ** (digit - 1)
14         all_nums = high_nums + low_nums * (high - 1)  # 最高位大於1的話,統計每個多位數後面包含的1
15     return low_nums + all_nums + get_1_nums(low)

對於上面使用的get_digits函數,是用來求給定的n是幾位數的。代碼如下:

1 def get_digits(n):
2     # 求整數n的位數
3     ret = 0
4     while n:
5         ret += 1
6         n /= 10
7     return ret

為了比較運行的效率,我用每次遍歷循環每個數中1的個數的方法進行了次數比較,發現使用以上方法效率提高了很多的,給定的數越大,效率提升越明顯。常規解法如下:

1 def test_n(num):
2     # 常規方法用來比較
3     ret = 0
4     for n in range(1, num+1):
5         for s in str(n):
6             if s == 1:
7                 ret += 1
8     return ret

使用下面的代碼進行了測試,發現效率提升非常明顯:

1 if __name__ == __main__:
2     test = 9923446
3     import time
4     t = time.clock()
5     print test_n(test)
6     print time.clock() - t
7     t1 = time.clock()
8     print get_1_nums(test)
9     print time.clock() - t1

運行結果如下,發現運行速率提升了很多很多倍:

6970095
18.284745
6970095
0.000223999999999

對於該問題的實現源代碼,請在這裏獲取。

Python解決 從1到n整數中1出現的次數