在從1到n的正數中1出現的次數

題目:輸入一個整數n,求從1nn個整數的十進制表示中1出現的次數。

例如輸入12,從112這些整數中包含1的數字有11011121一共出現了5次。

 

分析:這是一道廣爲流傳的google面試題。用最直觀的方法求解並不是很難,但遺憾的是效率不是很高;而要得出一個效率較高的算法,需要比較強的分析能力,並不是件很容易的事情。當然,google的面試題中簡單的也沒有幾道。

首先我們來看最直觀的方法,分別求得1到n中每個整數中1出現的次數。而求一個整數的十進制表示中1出現的次數,就和本面試題系列的第22題很相像了。我們每次判斷整數的個位數字是不是1。如果這個數字大於10,除以10之後再判斷個位數字是不是1。基於這個思路,不難寫出如下的代碼:

int NumberOf1(unsignedint n);

/////////////////////////////////////////////////////////////////////////////
// Find the number of 1 in the integers between 1 and n
// Input: n - an integer
// Output: the number of 1 in the integers between 1 and n
/////////////////////////////////////////////////////////////////////////////
int NumberOf1BeforeBetween1AndN_Solution1(unsignedint n)
{
      int number = 0;

      // Find the number of 1 in each integer between 1 and n
      for(unsignedint i = 1; i <= n; ++ i)
            number += NumberOf1(i);

      return number;
}

/////////////////////////////////////////////////////////////////////////////
// Find the number of 1 in an integer with radix 10
// Input: n - an integer
// Output: the number of 1 in n with radix
/////////////////////////////////////////////////////////////////////////////
int NumberOf1(unsignedint n)
{
      int number = 0;
      while(n)
      {
            if(n % 10 == 1)
                  number ++;

            n = n / 10;
      }

      return number;
}

這個思路有一個非常明顯的缺點就是每個數字都要計算1在該數字中出現的次數,因此時間複雜度是O(n)。當輸入的n非常大的時候,需要大量的計算,運算效率很低。我們試着找出一些規律,來避免不必要的計算。

我們用一個稍微大一點的數字21345作爲例子來分析。我們把從1到21345的所有數字分成兩段,即1-1235和1346-21345。

先來看1346-21345中1出現的次數。1的出現分爲兩種情況:一種情況是1出現在最高位(萬位)。從1到21345的數字中,1出現在10000-19999這10000個數字的萬位中,一共出現了10000(104)次;另外一種情況是1出現在除了最高位之外的其他位中。例子中1346-21345,這20000個數字中後面四位中1出現的次數是2000次(2*103,其中2的第一位的數值,103是因爲數字的後四位數字其中一位爲1,其餘的三位數字可以在0到9這10個數字任意選擇,由排列組合可以得出總次數是2*103)。

至於從1到1345的所有數字中1出現的次數,我們就可以用遞歸地求得了。這也是我們爲什麼要把1-21345分爲1-1235和1346-21345兩段的原因。因爲把21345的最高位去掉就得到1345,便於我們採用遞歸的思路。

分析到這裏還有一種特殊情況需要注意:前面我們舉例子是最高位是一個比1大的數字,此時最高位1出現的次數104(對五位數而言)。但如果最高位是1呢?比如輸入12345,從10000到12345這些數字中,1在萬位出現的次數就不是104次,而是2346次了,也就是除去最高位數字之後剩下的數字再加上1。

基於前面的分析,我們可以寫出以下的代碼。在參考代碼中,爲了編程方便,我把數字轉換成字符串了。

public class Test_25 {
	public static void main(String[] args) {
		int n = 21345;
		System.out.println("1的個數爲:"+NumOf1Between1AndN(n));
	}
	public static int NumOf1Between1AndN(Integer n){
		if(n<1)return 0;
		String str = n.toString();
		return NumberOf1(str);
	}
	public static int NumberOf1(String str){
		//獲得輸入數字的第一位數字,例如輸入21345,firstDig=2
		int firstDig=Integer.parseInt(str.charAt(0)+"");
		int length = str.length();
		if(length == 1 && firstDig ==0)return 0;
		if(length==1 && firstDig>0) return 1;
	    //首位爲1的個數
		int firstNumDigit = 0;
		if(firstDig>1)firstNumDigit=PowerBase10(length-1);
		if(firstDig==1)firstNumDigit=Integer.parseInt(str.substring(1))+1;
		//其他位爲1的個數
		int otherNumDigit = firstDig*(length-1)*PowerBase10(length-2);
		// 遞歸的位爲1的個數
		int numrecursive = NumberOf1(str.substring(1));
		return firstNumDigit+otherNumDigit+numrecursive;
	}
	public static int PowerBase10(int n){
		int result = 1;
		for(int i=0;i<n;i++){
			result*=10;
		}
		return result;
	}
}


輸出結果:1的個數爲:18821

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