编程之美系列之三——计算1的个数
問(wèn)題描述:
?????? 給定一個(gè)十進(jìn)制整數(shù)N,求出從1到N的所有整數(shù)中出現(xiàn)”1”的個(gè)數(shù)。?
????? 例如:N=2,1,2出現(xiàn)了1個(gè)“1”。
???????? ?? N=12,1,2,3,4,5,6,7,8,9,10,11,12。出現(xiàn)了5個(gè)“1”。
問(wèn)題求解:
解法一:
???????最直接的方法就是從1開始遍歷到N,將其中每一個(gè)數(shù)中含有“1”的個(gè)數(shù)加起來(lái),就得到了問(wèn)題的解。
???? ?代碼如下:
public long CountOne3(long n)
? ? ? ? {
? ? ? ? ? ? long i = 0,j = 1;
? ? ? ? ? ? long count = 0;
? ? ? ? ? ? for (i = 0; i <= n; i++)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? j = i;
? ? ? ? ? ? ? ? while (j != 0)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? if (j % 10 == 1)
? ? ? ? ? ? ? ? ? ? ? ? count++;
? ? ? ? ? ? ? ? ? ? j = j / 10;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? return count;
? ? ? ? }
此方法簡(jiǎn)單,容易理解,但它的問(wèn)題是效率,時(shí)間復(fù)雜度為O(N * lgN),N比較大的時(shí)候,需要耗費(fèi)很長(zhǎng)的時(shí)間。
解法二:
?????????我們重新分析下這個(gè)問(wèn)題,對(duì)于任意一個(gè)個(gè)位數(shù)n,只要n>=1,它就包含一個(gè)“1”;n<1,即n=0時(shí),則包含的“1”的個(gè)數(shù)為0。于是我們考慮用分治的思想將任意一個(gè)n位數(shù)不斷縮小規(guī)模分解成許多個(gè)個(gè)位數(shù),這樣求解就很方便。
??????? 但是,我們?cè)撊绾谓档鸵?guī)模?仔細(xì)分析,我們會(huì)發(fā)現(xiàn),任意一個(gè)n位數(shù)中“1”的個(gè)位可以分解為兩個(gè)n-1位數(shù)中“1”的個(gè)數(shù)的和加上一個(gè)與最高位數(shù)相關(guān)的常數(shù)C。例如,f(12) = f(10 - 1) + f(12 - 10) + 3,其中3是表示最高位為1的數(shù)字個(gè)數(shù),這里就是10,11,12;f(132)=f(100 -1) + f(132 - 100) + 33,33代表最高位為1的數(shù)字的個(gè)數(shù),這里就是100~132;f(232) = 2*f(100 - 1) + f(32) + 100,因?yàn)?32大于199,所以它包括了所有最高位為1的數(shù)字即100~199,共100個(gè)。
??????? 綜上,我們分析得出,最后加的常數(shù)C只跟最高位n1是否為1有關(guān),當(dāng)最高位為1時(shí),常數(shù)C為原數(shù)字N去掉最高位后剩下的數(shù)字+1,當(dāng)最高位為1時(shí),常數(shù)C為10bit,其中bit為N的位數(shù)-1,如N=12時(shí),bit=1,N=232時(shí),bit=2。
?????? 于是,我們可以列出遞歸方程如下:
?????? if(n1 == 1)
?????????? f(n) = f(10bit-1) + f(n - 10bit)? + n - 10bit+ 1;
?????? else
?????????? f(n) = n1*f(10bit-1) + f(n – n1*10bit) + 10bit;
?????? 遞歸的出口條件為:
?????? if(1<n<10)? return 1;
????? ?else if (n == 0) return 0;
?????? 基于此,編寫如下代碼:
???
public long CountOne(long n)
? ? ? ? {?
? ? ? ? ? ? long count = 0;
? ? ? ? ? ? if (n == 0)
? ? ? ? ? ? ? ? count = 0;
? ? ? ? ? ? else if (n > 1 && n < 10)
? ? ? ? ? ? ? ? count = ?1;
? ? ? ? ? ? else
? ? ? ? ? ? {
? ? ? ? ? ? ? ? long highest = n;//表示最高位的數(shù)字
? ? ? ? ? ? ? ? ?int bit = 0;
? ? ? ? ? ? ? ? while (highest >= 10)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? highest = highest / 10;
? ? ? ? ? ? ? ? ? ? bit++;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? int weight = (int)Math.Pow(10, bit);//代表最高位的權(quán)重,即最高位一個(gè)1代表的大小
? ? ? ? ? ? ? ? ?if (highest == 1)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? count = CountOne(weight - 1)
? ? ? ? ? ? ? ? ? ? ? ? ? ? + CountOne(n - weight)
? ? ? ? ? ? ? ? ? ? ? ? ? ? + n - weight + 1;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? else
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? count = highest * CountOne(weight - 1)
? ? ? ? ? ? ? ? ? ? ? ? ? ? + CountOne(n - highest * weight)
? ? ? ? ? ? ? ? ? ? ? ? ? ? + weight;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? return count;
? ? ? ? }
此算法的優(yōu)點(diǎn)是不用遍歷1~N就可以得到f(N)。經(jīng)過(guò)我測(cè)試,此算法的運(yùn)算速度比解法一快了許多許多,數(shù)字在1010內(nèi)時(shí),算法都可以在毫秒級(jí)內(nèi)結(jié)束,而解法一在計(jì)算109時(shí),時(shí)間超過(guò)了5分鐘。但此算法有一個(gè)顯著的缺點(diǎn)就是當(dāng)數(shù)字超過(guò)1010時(shí)會(huì)導(dǎo)致堆棧溢出,無(wú)法計(jì)算。
??????? 還有就是,我嘗試了許久也沒有計(jì)算出此算法的時(shí)間復(fù)雜度到底是多少,似乎是O(lg2N),我并不確定,希望知道的高手能給予解答。
?
解法三:
??????? ?解法二告訴我們1~ N中“1”的個(gè)數(shù)跟最高位有關(guān),那我們換個(gè)角度思考,給定一個(gè)N,我們分析1~N中的數(shù)在每一位上出現(xiàn)1的次數(shù)的和,看看每一位上“1”出現(xiàn)的個(gè)數(shù)的和由什么決定。
???????1位數(shù)的情況:
?????? 在解法二中已經(jīng)分析過(guò),大于等于1的時(shí)候,有1個(gè),小于1就沒有。
???????2位數(shù)的情況:
?????? N=13,個(gè)位數(shù)出現(xiàn)的1的次數(shù)為2,分別為1和11,十位數(shù)出現(xiàn)1的次數(shù)為4,分別為10,11,12,13,所以f(N) = 2+4。
?????? N=23,個(gè)位數(shù)出現(xiàn)的1的次數(shù)為3,分別為1,11,21,十位數(shù)出現(xiàn)1的次數(shù)為10,分別為10~19,f(N)=3+10。
?????? 由此我們發(fā)現(xiàn),個(gè)位數(shù)出現(xiàn)1的次數(shù)不僅和個(gè)位數(shù)有關(guān),和十位數(shù)也有關(guān),如果個(gè)位數(shù)大于等于1,則個(gè)位數(shù)出現(xiàn)1的次數(shù)為十位數(shù)的數(shù)字加1;如果個(gè)位數(shù)為0,個(gè)位數(shù)出現(xiàn)1的次數(shù)等于十位數(shù)數(shù)字。而十位數(shù)上出現(xiàn)1的次數(shù)也不僅和十位數(shù)相關(guān),也和個(gè)位數(shù)相關(guān):如果十位數(shù)字等于1,則十位數(shù)上出現(xiàn)1的次數(shù)為個(gè)位數(shù)的數(shù)字加1,假如十位數(shù)大于1,則十位數(shù)上出現(xiàn)1的次數(shù)為10。
???????3位數(shù)的情況:
?????? N=123
?????? 個(gè)位出現(xiàn)1的個(gè)數(shù)為13:1,11,21,…,91,101,111,121
?????? 十位出現(xiàn)1的個(gè)數(shù)為20:10~19,110~119
?????? 百位出現(xiàn)1的個(gè)數(shù)為24:100~123
???????我們可以繼續(xù)分析4位數(shù),5位數(shù),推導(dǎo)出下面一般情況:?
?????? 假設(shè)N,我們要計(jì)算百位上出現(xiàn)1的次數(shù),將由三部分決定:百位上的數(shù)字,百位以上的數(shù)字,百位一下的數(shù)字。
?????? 如果百位上的數(shù)字為0,則百位上出現(xiàn)1的次數(shù)僅由更高位決定,比如12013,百位出現(xiàn)1的情況為100~199,1100~1199,2100~2199,…,11100~11199,共1200個(gè)。等于更高位數(shù)字乘以當(dāng)前位數(shù),即12 * 100。
?????? 如果百位上的數(shù)字大于1,則百位上出現(xiàn)1的次數(shù)僅由更高位決定,比如12213,百位出現(xiàn)1的情況為100~199,1100~1199,2100~2199,…,11100~11199,12100~12199共1300個(gè)。等于更高位數(shù)字加1乘以當(dāng)前位數(shù),即(12 + 1)*100。
????????如果百位上的數(shù)字為1,則百位上出現(xiàn)1的次數(shù)不僅受更高位影響,還受低位影響。例如12113,受高位影響出現(xiàn)1的情況:100~199,1100~1199,2100~2199,…,11100~11199,共1200個(gè),但它還受低位影響,出現(xiàn)1的情況是12100~12113,共114個(gè),等于低位數(shù)字113+1。
?????? 綜合以上分析,寫出如下代碼:
public long CountOne2(long n)
? ? ? ? {
? ? ? ? ? ? long count = 0;
? ? ? ? ? ? long i = 1;
? ? ? ? ? ? long current = 0,after = 0,before = 0;
? ? ? ? ? ? while((n / i) != 0)
? ? ? ? ? ? { ? ? ? ? ??
? ? ? ? ? ? ? ? current = (n / i) % 10;
? ? ? ? ? ? ? ? before = n / (i * 10);
? ? ? ? ? ? ? ? after = n - (n / i) * i;
? ? ? ? ? ? ? ? if (current > 1)
? ? ? ? ? ? ? ? ? ? count = count + (before + 1) * i;
? ? ? ? ? ? ? ? else if (current == 0)
? ? ? ? ? ? ? ? ? ? count = count + before * i;
? ? ? ? ? ? ? ? else if(current == 1)
? ? ? ? ? ? ? ? ? ? count = count + before * i + after + 1;
? ? ? ? ? ? ? ? i = i * 10;
? ? ? ? ? ? }
? ? ? ? ? ? return count;
? ? ? ? ? ??
? ? ? ? }
?此算法的時(shí)間復(fù)雜度僅為O(lgN),且沒有遞歸保存現(xiàn)場(chǎng)的消耗和堆棧溢出的問(wèn)題。
???? 不知道各位看官是否還有更高效的算法拿出來(lái)分享呢?
?
總結(jié)
以上是生活随笔為你收集整理的编程之美系列之三——计算1的个数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 淮北市成人学计算机学校,安徽淮北市成人学
- 下一篇: linux连接svn上代码,代码管理平台