計算正整數中1的數目

這是從編程之美上看到的一道題,簡述題目內容如下:
給定正整數N,計算出從1到N的所有數字的十進制表示中出現1的次數,並找出能夠滿足f(N)==N的最大的N值。比如f(12) = 5, 因爲存在的數字有:1,10,11,12,總共五個1。

1. 尋找1出現的次數
暴力方法
最簡單的方法是將N個數全部遍歷一遍,對每個數計算出其中1的個數,然後累加。具體代碼如下:

func f(N uint) uint {
    var count uint = 0
    for i := uint(1); i <= N; i++ {
        n := i
        for n > 0 {
            if n % 10 == 1 { count++ }
            n /= 10
        }
    }
    return count
}

這種方法效率非常低下,但是最不容易出錯。

數學歸納方法
最高效的方法是先觀察1出現的規律,然後通過數學公式來對邏輯進行優化。觀察下表:
在這裏插入圖片描述
通過類推可以得到:對於最大的i位數,在其前面的數字中包含1的總數爲 G(i) = i*(10^(i-1))
假設有一個數abcd,其千位數爲a,所以我們可以將其拆解爲兩部分:0~a000-1, a000~abcd。
對於0a000-1,總共有a+1個0999,所以至少包含 aG(3)個1。但是考慮到a>=1, 如果a>1,則因爲存在1000~1999這個區間,故而額外再加上1000,即aG(3)+1000。如果a=1,那麼千位數爲1的值總共應該有(bcd+1)個,即a*G(3) + bcd+1。
對於a000~abcd, 我們抽取出bcd,將之分解爲0~b00-1, b00~bcd兩部分,接下來計算方法同上。

總而言之,我們得到f(abcd)如下:

if a == 1:
    f(abcd) = a *G(3) + bcd+1 + f(bcd)
else:
    f(abcd) = a *G(3) + pow(10, 3)+ f(bcd)

轉換爲實際代碼:

func f(N uint) uint {
    if N < 10 { if N == 0 { return 0 } else { return 1} }
    var firstVal uint = 0     // 最高位的值
    var size uint = 0         // 十進制位數
    n := N
    for n > 0 {
        firstVal = n
        n /= 10
        size++
    }
    base := pow(10, size-1)   // 最小的size位數
    if firstVal == 1 {
        return f(N % base) + (N % base + 1) + (size-1) * pow(10, size-2)
    }
    return pow(10, size-1) + firstVal*(size-1) * pow(10, size-2) + f(N % base)
}

func pow(x uint, y uint) uint {
    var res uint = 1
    for y > 0 {
        res *= x
        y--
    }
    return res
}

2. 滿足f(N) == N的最大N值
首先需要證明我們的函數f(N)和函數y=x相交。期望的圖形如下:
在這裏插入圖片描述
從最上面的圖片我們總結出:
f(9) = 1
f(99) = 20
f(999) = 300
f(9999) = 4000

通過歸納,不難得出 f(10^n - 1) = n * 10^(n-1)
使10^n - 1 <= n * 10^(n-1)成立的最小n爲10。這意味着 10^9 - 1 <= N <= 10^10 - 1。
很明顯這個N值很大,直接遍歷上述範圍所需要花費的時間也會很大。所以採取二分查找法,代碼如下:

func getMaxN() uint {
    max := pow(10, 10) - 1
    min := pow(10, 9) - 1
    for true {
        mid := (max + min) / 2
        val := f(mid)
        if (val < mid) {
            min = mid
        } else if (val > mid) {
            max = mid
        } else {
            return mid;
        }
    }
    return 0
}

使f(N) == N的最大N值爲1111111110

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章