计算4000000000以内最大的f(n)=n的值---字符串问题python实现(五)
問題:
? ? ? ?寫一個函數,計算4 000 000 000 以內的最大的那個f(n)=n的值,函數f的功能是統計所有0到n之間所有含有數字1的數字和。比如:f(13)= 6,因為“1”在“1,2,3,4,5,6,7,8,9,10,11,12,13”中的總數是6(1,10,11,12,13)。
分析:
? ? ? ?一、簡單算法 — 枚舉
? ? ? ?采用“枚舉法”對每個數都計算一遍1的個數,直到枚舉完給定范圍所有數,找到符合f(n)=n的數。此方法,代碼效率極低,運算所需時間巨大。
python版本代碼如下:
# -*- coding:utf-8 -*- # 問題:寫一個函數,計算4 000 000 000 以內的最大的那個f(n)=n的值, # 函數f的功能是統計所有0到n之間所有含有數字1的數字和。比如:f(13)= 6, # 因為“1”在“1,2,3,4,5,6,7,8,9,10,11,12,13”中的總數是6(1,10,11,12,13)。 # by chasdmeng import time def Calculation_One_Times(n):res = 0while n > 0:if n == 1:res +=1n /=10return resif __name__ == '__main__':times = 0i = 0gMAX = 4000000000Lstart = time.time()while i < gMAX:times +=Calculation_One_Times(i)if times ==i:print "f(", i, ") = ", timesi +=1end = time.time()print "time:",end - start ? ? ? ? 二、高效算法 — 剪枝
? ? ? ?主要采用剪枝算法對代碼執行效率進行優化。求解MAX(f(n)=n)具體算法構建分析如下:
? ? ? ?1、求數字“0~n”中“1”的個數
? ? ? ?基本思想:
? ? ? ?t位數字n,求0到n所有數字“1”的數字和,可對1到t位(1對應個位),每個位置上“1”出現的次數分別統計,然后求和。
? ? ? ?假設,0到n之間的自然數范圍內,統計第m位為“1”的個數和,可以分四步驟。第一步驟,假設第二、三步驟的前提條件為第1到第m-1位內各位數字取固定值a。第二步驟,求大于m位的數字中有多少個數字第m位為“1”。第三步驟,求等于m位的數字中有多少個數字第m位為“1”。第四步驟,把第1到第m-1位內各位數字變化考慮進去。如下圖所示。
? ? ? ?在第二步驟中,需要分兩種情況考慮:
? ? ? (a)給定數n的第m位非0
? ? ? ?大于m位的數字中第m位為“1”的數字個數是n/10^m。
????????(b)給定數n的第m位為0
????? ?對于m位的數字中第m位為“1”的數字個數為(n/10^m)-1。這里舉例說明,n=300,m=2,由于300的第2位為0,此時n/10^m=3,若按(a)中方法求解得出在第1位固定取a 的條件下(第一步驟中規定的)大于2位的數字中第2位為“1”的數字個數是3,這顯然是錯誤的,因為在0到300中這樣的數只有“11a”、“21a”,此時正確個數應該是n/10^m-1=2。
????? (c)合并考慮
???????大于m位的數字中第m位為“1”的數字個數為(n/(10^(m-1))-1)/10。構造了一個通用公式可以包含(a)、(b)兩種情況。原理是,n/10^m=(n/(10^(m-1)))/10,由于是對n做除是向下取整運算,在給定數n第m位非0情況下,n/10^m=(n/(10^(m-1)))/10=(n/(10^(m-1))-1)/10;而當給定數n第m位為0情況下,(n/10^m)-1=(n/(10^(m-1))-1)/10。可以舉具體數字對其驗證。
???????在第三步驟中,由于在第一步驟假定的前提條件,滿足求等于m位的數字中第m位為“1”的個數只有1個。這里舉例說明,n=300,m=2,2位數字中第2位為“1”的數僅有“1a”。
? ? ? ?在第四步驟中,綜合第一、二、三步驟后,在第1到第m-1位內各位數字取固定值a的條件下,第m位為“1”的個數和為(n/(10^(m-1))- 1)/10+1。下面把第1到第m-1位內各位數字變化考慮進去,由于在第1到第m-1位中每一位可以放0~9之間任意數,m-1位數共有“10^m-1”個。最終,0到n之間的自然數范圍內,第m位為“1”的個數和((n/(10^(m-1))- 1)/10+1)*(10^(m-1))。
? ? ? ?小結:對以上分析結果給予公式化定義。Cm=((n/i - 1)/10 + 1)*i,其中i=10^(m-1),n為任意自然數,Cm為0到n的數中第m位為1 的 個數和。
? ? ? ?求數字“0~n”中“1”個數的GetTimes()函數,python版代碼如下:
def GetTimes(n):temp = ntemp2 =1ret = 0i = 1while temp/i:ret +=(((temp/i-1)/10)+1)*iif(((temp/i)%10) == 1):ret -=iret +=temp2i*=10temp2=n%i+1return ret
? ? ? ?2、從0到n位最大數之間1的總個數,尋找數學規律
? ? ? ?當n=1,2,3,...,6,...時,分別計算從0到n位最大數之間1的總個數,部分數據如下:
? ? ? ?0~9 ? ? ? ? ?:1
? ? ? ?0~99 ? ? ? ?:20 = 10*1 + 10
? ? ? ?0~999 ? ? ?:300 = 10*20 + 100
? ? ? ?0~9999 ? ?:4000 = 10*300 + 1000
? ? ? ?0~99999 ?:50000 = 10*4000 + 10000
? ? ? ?0~999999:600000 = 10*50000 + 100000
? ? ? ?… ? ? ? ? ? ? ? ? ??…
? ? ? ?從中,可以發現如下規律:
? ? ? ?a1 = 1
? ? ? ?a2 = 10*a1 + 10
? ? ? ?…
? ? ? ?an = 10*an - 1 + 10^(n - 1)
? ? ? ?即0~9…9(n個9)之間1的個數為an=10*an-1+10^(n-1)個。
? ? ? ?這樣,當計算任意一個數字n的0到n之間所有數字1的個數和時,可以采用將數字n拆分成0~9、0~99... 等部分進行之間計算。例如:n=123,可將n拆分為0~99和100~123計算:(1)0~99對應1的個數和直接可知為20;(2)百位數1的個數為23+1個;(3)剩下的0~23繼續拆分為0~9、10~19、20~23,共兩個0~9個數為2;(4)因為23中十位數2>1,十位為1的個數10;(5)現在就剩下20~23了,個數為1。所以,0~123中1的個數為20+24+2+10+1=57。
? ? ? ?因此,在求任意數0~n內所有1的個數和時,可以首先建立“int ?gTable[10]”結構用于存放a1、a2、a3、...、a9、a10,其中an表示0~n內所有1的個數,然后將數n拆分為0~9、0~99... 等部分直接使用gTable的值進行計算。這種方式可以提高算法效率。
? ? ? ?生成gTable[]結構的TimesTable()函數,python版代碼如下:
def TimesTable(n):return [GetTimes(10**(i+1) - 1) for i in xrange(n)]
? ? ? ?3、采用剪枝算法搜索0~n之間滿足f(q)=q的數
? ? ? ?由于若對0~n的所有數據進行枚舉驗證f(q)=q,當n值很大時運算相當費時,從本文開頭給出的“最簡單方法”中可以感受到代碼效率有多么低。為了優化算法提高效率可采用減枝算法,其思想是對0~n之間所有數建立搜索樹,對不符合條件的搜索樹進行剪枝,這樣就可以極大縮小搜索范圍,提高算法效率。如何構建剪枝規則呢?首先對這個問題進行數學化描述:
? ? ? ?定義1:設 f(x)=y,其中x∈ (0,n),f為x與對應的(0,x)上所有數字1的個數總和y的映射。
? ? ? ?先觀察下f(n)的特點,可以發現f(n)是一個單調遞增函數,這樣可以歸納出它的特性:
? ? ? ?性質1:若有f(m)=a,f(t)=b,且m<t,則存在q∈(m,t)滿足f(q)=q成立的條件是:b>m 且a< t。
? ? ? ?現在就可以應用“性質1”進行減枝操作。具體來說通過“性質1”可知,當對0~n的所有數據進行枚舉驗證f(q)=q時,若當f(t)=b<m或f(m)=a>n,則在[m,t]范圍內不存在滿足f(q)=q成立的數q,這樣就可忽略掉[m,t]內這些自然數,直接跳到n+1往后繼續枚舉。在應用剪枝算法枚舉符合條件的f(q)=q時,是以t-m為步長在0~n內逐段排查是否可剪枝,若t-m范圍內不滿足剪枝條件則進行枚舉搜索。因此在進行剪枝算法時[m,t]的范圍取多大適合呢?這是需要慎重考慮的問題,合理的[m,t]取值能讓大幅提升算法執行效率。下面就對[m,t]步長L取值進行分析。
? ? ? ?[m,t]的取值依據是能最大化提升算法效率,由于在“2、從0到n位最大數之間1的總個數,尋找數學規律。”中設置了“int ?gTable[10]”結構用于計算f(n),gTable分別表示0~9中1個數和1、0~99中1個數和20、0~999中1的個數和300...,可見是以10^p倍增長,而且f(n)還需要使用gTable去求解,因此初步考慮將[m,t]步長L=10^p,雖然[m,t]取10的倍數,但p具體取多少,還需要繼續分析。
? ? ? ? 對于任意s位數m,設s位數的最大值為max(s),則f(max(s))=gTable[s-1],假定取[m,t]步長L=10^s也就是 t=m+10^s,設f(m)=a、f(m+10^s)=b,則有m<max(s)<m+10^s,由于f()為單調增函數,推出gT[s-1]=f(max(s))<f(m+10^s)=b,a<gTable[s-1],又因為在0~10^10-1的范圍內有max(s)>gTable[s-1](可以舉例驗證),推出a<gTable[s-1]<max(s)<10+10^s,即a<m+10^s。f(m+10^s)=f(10^s-1)+m+1+f(m)=b,即b>m,因此根據性質1可知m=d*10^s時,取[m,t]步長L=10^s恒不滿足減枝條件,同理步長L=10^(s+1)也無法滿足剪枝條件,因此只能往下取值。對于任意s位數m,取步長L<=10^(s-1)某些數內可以滿足減枝條件,舉例來說,m=20,s=2,L=10^(s-1)=10,則在[20,30]內f(20)=12、f(30)=13,根據性質1可以剪枝掉[20,30]范圍內的數。綜上所述,對任意s位數m,m∈ (0,n),剪枝算法初始步長L=10^(s-1)? ?。
? ? ? ?性質2:若有f(m)=a,f(t)=b,且m<t,滿足b>m 且a< t時,有f(q)=q,且q∈(m,b]。
? ? ? ?性質2是由性質1推到而來,性質2中將原來性質1中q∈(m,t)縮小為q∈(m,b)。首先在0~10^10-1的范圍內滿足性質1條件下可知b∈(m,t],設f(b+1)=c,則在(b+1,t)內f(b+1)=c、f(t)=b根據性質1不存在q∈(b+1,t)使f(q)=q成立。所以,在(m,t)內使f(q)=q成立的q∈(m,b]。
? ? ? ? 剪枝算法整體思想是:n位數m,步長L=10^(n-1),在區間(m-1,m+L-1)判斷是否滿足f(m-1)>boundary或f(m+L-1)<m ,滿足就直接跳到m=m+L計算,否則在區間(m-1,f(m+L-1)+L-1)上置步長L=L/10遞歸重復執行剪枝判斷,當遞歸到步長L=1時,進行逐值枚舉f(number)是否等于number。由于算法中m是從0開始枚舉取值,以10的倍數遞增,因此所有的m都應滿足abc*10^s格式,也就是說,m取到的是10的倍數。因此有如下性質。
? ? ? ?性質3:m=abc*10^(n-1),L=10^(n-1),可知f(m-1+L)=f(m-1)+gTable[n-2]+count_one_m*L,其中count_one_m=f(m)-f(m-1),也就是代表數字m本身包含1的個數。
? ? ? ?剪枝算法CalculationTimes()函數,python版代碼如下 :?
def CalculationTimes(number, weight, count_one, count, table):if weight == 0:count += count_oneif number == count:print "f(", number, ") = ", numberreturn countL=10**weightmaxcount = count + table[weight - 1]maxcount += count_one*Lif count > (number + L -1):return maxcountif maxcount < number:return maxcountL /= 10for i in range(10):if i == 1:count = CalculationTimes(number + i*L, weight - 1, count_one + 1, count, table)elif i == maxcount/n - 9:return maxcountelse:count = CalculationTimes(number + i*L, weight - 1, count_one , count, table)return count
? ? ? ?其中,對應前面剪枝算法思想,這里number代表m,weight代表步長L=10^(s-1)?中的s-1,count_one表示數n中各個位置上1的個數,比如n=112,這count_one=2,table代表gTable[],count代表f(m-1)。具體解釋如下:
if weight == 0:count += count_oneif number == count:print "f(", number, ") = ", numberreturn count 此部分代碼判斷當前步長L若為1(L=10^weight)計算f(number),f(number)通過count+=count_one實現,這是由于在步長L=1前提下,f(number)等于f(number-1)加上數字mumber本身1的個數count_one,既f(number)=f(number-1)+count_one,若f(number)=number,則輸出,否則返回當f(number)。
L=10**weightmaxcount = count + table[weight - 1]maxcount += count_one*Lif count > (number + L -1):return maxcountif maxcount < number:return maxcount
此部分代碼功能執行剪枝操作,首先設置步長L=10^weight,根據性質3得f(number+L-1)=count+table[weight-1]+count_one*L,然后根據性質1判斷是否可以剪枝,若符合條件直接忽略(number-1,number+L-1)范圍內數字并跳轉到number+L再計算。
L /= 10for i in range(10):if i == 1:count = CalculationTimes(number + i*L, weight - 1, count_one + 1, count, table)elif i == maxcount/n - 9:return maxcountelse:count = CalculationTimes(number + i*L, weight - 1, count_one , count, table)return count 若不滿足剪枝條件執行此部分代碼。 首先根據前面描述的 剪枝算法整體思想 將步長縮短L=L/10,然后 根據性質2僅需遞歸計算( number-1,f(number+L-1)) 范圍內數即可,當i==1時,number+i*L這個數本身將多出一個”1“需要將count_one值加1,當i==maxcount/n-9時,number+i*L將超出范圍終止計算。
? ? ? ?最后,給出整體實現代碼。
# -*- coding:utf-8 -*- # 問題:寫一個函數,計算4 000 000 000 以內的最大的那個f(n)=n的值, # 函數f的功能是統計所有0到n之間所有含有數字1的數字和。比如:f(13)= 6, # 因為“1”在“1,2,3,4,5,6,7,8,9,10,11,12,13”中的總數是6(1,10,11,12,13)。 # by chasdmeng import time def GetTimes(n):temp = ntemp2 =1ret = 0i = 1while temp/i:ret +=(((temp/i-1)/10)+1)*iif(((temp/i)%10) == 1):ret -=iret +=temp2i*=10temp2=n%i+1return ret def TimesTable(n):return [GetTimes(10**(i+1) - 1) for i in xrange(n)] def CountOne(n):count = 0while n:if (n%10) == 1:count +=1n /=10return count def CalculationTimes(number, weight, count_one, count, table):if weight == 0:count += count_oneif number == count:print "f(", number, ") = ", numberreturn countL=10**weightmaxcount = count + table[weight - 1]maxcount += count_one*Lif count > (number + L -1):return maxcountif maxcount < number:return maxcountL /= 10for i in range(10):if i == 1:count = CalculationTimes(number + i*L, weight - 1, count_one + 1, count, table)elif i == maxcount/n - 9:return maxcountelse:count = CalculationTimes(number + i*L, weight - 1, count_one , count, table)return countif __name__ == '__main__':n = 0weight = 0count = 0count_one = 0gMAX = 4000000000Lstart = time.time()table = TimesTable(10)while n < gMAX:count = CalculationTimes(n, weight, count_one , count, table)L = 10**weightn += Lif (n/L)/10 == 1: weight +=1count_one = CountOne(n)end = time.time()print "time:",end - start? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?by ? ? ? chasdmeng
參考文獻:
? ? ? 博客:http://blog.csdn.net/livelylittlefish/article/details/2768348
? ? ? 書籍:《程序員面試寶典(第4版)》P240,面試例題5
總結
以上是生活随笔為你收集整理的计算4000000000以内最大的f(n)=n的值---字符串问题python实现(五)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 音频分类-数据集:Urbansound8
- 下一篇: Python+Twisted+Autob