1. 程式人生 > >#資料結構與演算法學習筆記#劍指Offer29:整數中1出現的次數 + 分段思想/按位考慮 + 測試用例(Java、C/C++)

#資料結構與演算法學習筆記#劍指Offer29:整數中1出現的次數 + 分段思想/按位考慮 + 測試用例(Java、C/C++)

2018.10.5

感受到開學之後工作和課業的雙重壓力,加上近段時間自己出了點小事故,因此斷更了許久。沒事,繼續。

這道題有兩種複雜度為o\left ( log_{10} N \right )的演算法。

方法1:遞迴(分段思想)。

所有數字出現1的個數 = 每一段數字中出現1的個數之和

1. 對於輸出的數字n,其最高位為x,將其分成1-i、i+1-n兩段。其中,i為n除以x的餘數,i-n的數字數目為x倍數(例如n=21345,x為10000,則將n分為1-1345,1346-21345)。

2. 後半段中最高位上取1的情況分為兩種:若n最高位數字>1,則最高位出現1的次數為x次;若n最高位數字=1,這最高位出現1的次數為i+1次。(例如後半段為1346-21345,則最高位萬位上1出現的次數為10000次;若後半段為1346-11345,則最高位萬位上1出現的次數為1346次)。

3. 後半段中其他數位上出現1的情況:將後半段等分為m份,每份數字數目為x(例如後半段為1346-21345,則將其分為1346-11345、11346-21345,每份數字數目為10000個)。除去最高位後,剩下的數字有y位。在任一一位上置1,其餘y-1位上可以任取0-9,則根據排列組合,後半段其餘數位總共出現1的次數為m\times y\times 10^{y-1}.(後半段分為01346-11345與11346-21345,去掉萬位後,剩餘數字有4位,根據公式,後半段數位總共出現1的次數為2\times 4\times 10^{3}=8000

4.計算後半段,遞迴處理前半段。

方法2:歸納(按位考慮)。

所有數字出現1的個數 = 每一位數位上出現1的個數之和

從低到高遍歷數字的每一數位loc,對於每一位loc,其當前位數字為now(1位),高位數字high(多位,若不存在則為0),低位數字low(多位,不存在則為0)。對於每一位loc上可能取1的情況:

1. now==0(次數=high*loc)

例如21045,對於百位來說,loc=100,high=21,1出現的情況有00100-00199(100次)、01100-01199(100次)……19100-19199(100次)、20100-20199(100次),一共2100次。

2. now==1(次數=high*loc + (low+1))

例如21145,low=145,對於百位來說,除去上述的2100次,還有21100-21145,一共145+1=146次。總計2246次

3. now>=2(次數=(high+1)*loc)

例如21545,對於百位來說,除去上述的2100次,還有21100-21199,一共100次。總計2200次。

方法二還可以更一般地歸納為一條公式,具體見程式碼具體實現。

兩種方法可拓展到整數中數字x出現的次數求解

題目描述

求出1~13的整數中1出現的次數,並算出100~1300的整數中1出現的次數?為此他特別數了一下1~13中包含1的數字有1、10、11、12、13因此共出現6次,但是對於後面問題他就沒轍了。ACMer希望你們幫幫他,並把問題更加普遍化,可以很快的求出任意非負整數區間中1出現的次數(從1 到 n 中1出現的次數)。

Java實現(按位考慮):

/**
 * 
 * @author ChopinXBP
 * 求從1到n中1在數位上出現的次數
 *
 */

public class NumberOf1Between1AndN_30 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println(NumberOf1Between1AndN_Solution1(12023));
		System.out.println(NumberOf1Between1AndN_Solution2(12023));
		System.out.println(NumberOf1Between1AndN_Solution3(12023));
	}

	
	//歸納:按位考慮
	public static int NumberOf1Between1AndN_Solution1(int n) {
		if(n <= 0) return 0;
		int result = 0;
		int loc = 1;	//當前位數
		int high = 0;	//高位數字(多位)
		int now = 0;	//當前位數字(1位)
		int low = 0;	//低位數字(多位)
		
		while(n / loc > 0){
			high = n / (loc * 10);
			now = n / loc % 10;
			low = n % loc;
			
			if(now == 0){
				result += high * loc;
			}
			else if(now == 1){
				result += high * loc + low + 1;
			}
			else if(now >= 1){
				result += (high + 1) * loc;
			}
			
			loc *= 10;
		}
		
		return result;
	}
	
	//簡潔寫法1,在C++下可歸納為一條公式
	//當前位now=0/1時,+8對高位high無影響;當前位now>=2時,+8會產生進位,效果等同於high+1
	public static int NumberOf1Between1AndN_Solution2(int n) {
		int result = 0;
		for (long loc = 1; loc <= n; loc *= 10) {
			if(n / loc % 10 == 1){
				result += (n / loc + 8) / 10 * loc + (n % loc + 1);
			}else{
				result += (n / loc + 8) / 10 * loc;
			}
		}
		return result;
	}
	
	
	//簡潔寫法2,歸納為一條公式
	//判斷去掉高位後的餘數,對於後半式子,若當前位小於1,輸出0;若當前位等於1,輸出low低位數字+1;若當前位大於1,輸出一個loc當前位,等效於(high+1)*loc
	public static int NumberOf1Between1AndN_Solution3(int n) {
		if (n <= 0)
			return 0;
		int result = 0;
		for (long loc = 1; loc <= n; loc *= 10) {
			long high = n / (loc * 10);		//高位數字
			long rest = n % (loc * 10);		//去掉高位數字後的餘數
			result += high * loc + Math.min(Math.max(rest - loc + 1, 0), loc);
		}
		return result;
	}
}

C++實現(分段思想):

int NumberOf1(const char* strN);
int PowerBase10(unsigned int n);

int NumberOf1Between1AndN_Solution2(int n)
{
    if(n <= 0)
        return 0;

    char strN[50];
    sprintf(strN, "%d", n);

    return NumberOf1(strN);
}

int NumberOf1(const char* strN)
{
    if(!strN || *strN < '0' || *strN > '9' || *strN == '\0')
        return 0;

    int first = *strN - '0';
    unsigned int length = static_cast<unsigned int>(strlen(strN));

    if(length == 1 && first == 0)
        return 0;

    if(length == 1 && first > 0)
        return 1;

    // 假設strN是"21345"
    // numFirstDigit是數字10000-19999的第一個位中1的數目
    int numFirstDigit = 0;
    if(first > 1)
        numFirstDigit = PowerBase10(length - 1);
    else if(first == 1)
        numFirstDigit = atoi(strN + 1) + 1;

    // numOtherDigits是01346-21345除了第一位之外的數位中1的數目
    int numOtherDigits = first * (length - 1) * PowerBase10(length - 2);
    // numRecursive是1-1345中1的數目
    int numRecursive = NumberOf1(strN + 1);

    return numFirstDigit + numOtherDigits + numRecursive;
}

int PowerBase10(unsigned int n)
{
    int result = 1;
    for(unsigned int i = 0; i < n; ++ i)
        result *= 10;

    return result;
}

C++實現(按位考慮):

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n){ 
        int ones = 0;
        for (long long m = 1; m <= n; m *= 10)
            ones += (n/m + 8) / 10 * m + (n/m % 10 == 1) * (n%m + 1);
         return ones;
     
    }
};

#Coding一小時,Copying一秒鐘。留個言點個讚唄,謝謝你#