Python解決 從1到n整數中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出現的次數