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

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM