題目:丑數
- 我們把只包含因子2,3,5的數稱為丑數(Ugly Number).
- 求按從小到大的順序的第1500個丑數。
- 例如6,8都是丑數,但14不是,因為它含有因子7.習慣上我們把1當作第一個丑數
方法一:逐個判斷每個整數是不是丑數,直觀但不夠高效:
所謂一個數m是另一個數n的因子,是指n能被m整除,也就是說n%m==0.根據丑數的定義,丑數只能被2,3,5整除。也就是說如果一個數能被2整除,我們把它連續除以2;如果能被3整除,就連續除以3;如果能被5整除,就除以5.如果最后我們得到的是1,那么這個數就是丑數,否則不是。
public boolean isUgly(int number){
while(number % 2 == 0)
number/=2;
while(number % 3 == 0)
number /=3;
while(number % 5 == 0)
number /=5;
return (number ==1)? true:false;
}
接下來,只需要按照順序判斷每個整數是不是丑數即可。
public int getUglyNumber(int index){
if(index <= 0)
return 0;
int number = 0;
int uglyFound = 0;
while(uglyFound < index){
number++;
if(isUgly(number)){
++uglyFound;
}
}
return number;
}
我們只需要在函數getUglyNumber 中傳入參數1500,就能得到第1500個丑數。該算法非常直觀,代碼也非常簡潔,但最大的問題是每個整數都需要計算。即使一個數字不是丑數,我們還是需要對它做求余和除法操作。因此該算法的時間效率不是很高,面試官也不會就此滿足,還會提示我們有更高效的算法。
方法二:創建數組保存已經找到的丑數,用空間換時間的解法:
前面的算法之所以效率低,很大程度上是因為不管一個數是不是丑數我們對它都要作計算。接下來我們試著找到一種只要計算丑數的方法,而不在非丑數的整數上花費時間。根據丑數的定義,丑數應該是另一個丑數乘以2,3,5的結果。因此我們可以創建一個數組,里面的數字是排序好的丑數,每一個丑數都是前面的丑數乘以2,3,5得到的。
這種思路的關鍵在于怎樣確定數組里面的丑數是排序好的。假設數組中已經有若干個丑數排好后存放在數組中,并且把已有的最大的丑數記作M,我們接下來分析如何生成下一個丑數。該丑數肯定是前面某個丑數乘以2,3,5的結果。所以我們首先考慮把已有的每個丑數乘以2.在乘以2的時候,能得到若干個小于或等于M的結果。由于是按照順序生成的,小于或者等于M肯定已經在數組中了,我們不需要再次考慮;還會得到若干個大于M的結果,但我們只需要第一個大于M的結果,因為我們希望丑數是指按從小到大的順序生成的,其他更大的結果以后再說。我們把得到的第一個乘以2后大于M的結果即為M2.同樣,我們把已有的每一個丑數乘以3,5,能得到第一個大于M的結果M3和M5.那么下一個丑數應該是M2,M3,M5。這3個數的最小者。
前面分析的時候,提到把已有的每個丑數分別都乘以2,3,5.事實上這不是必須的,因為已有的丑數都是按順序存放在數組中的。對乘以2而言,肯定存在某一個丑數T2,排在它之前的每一個丑數乘以2得到的結果都會小于已有的最大丑數,在它之后的每一個丑數乘以2得到的結果都會太大。我們只需記下這個丑數的位置,同時每次生成新的丑數的時候,去更新這個T2.對乘以3和5而言,也存在這同樣的T3和T5.
因此java代碼實現如下:
public int getUglyNumber_Solution2(int index){
if(index <=0)
return 0;
int[] uglyArray = new int[index];
uglyArray[0] = 1;
int multiply2 = 0;
int multiply3 = 0;
int multiply5 = 0;
for(int i = 1;i<index;i++){
int min = min(uglyArray[multiply2]*2,uglyArray[multiply3]*3,uglyArray[multiply5]*5);
uglyArray[i] = min;
//生成新的丑數后及時更新三個游標
while(uglyArray[multiply2]*2 <= uglyArray[i])
++multiply2;
while(uglyArray[multiply3]*3 <= uglyArray[i])
++multiply3;
while(uglyArray[multiply5]*5 <= uglyArray[i])
++multiply5;
}
return uglyArray[index-1];
}
public int min(int number1,int number2,int number3){
int min = (number1<number2)?number1:number2;
return min <number3?min:number3;
}
和第一種思路相比,第二種思路不需要在非丑數的整數上做任何計算,因此時間效率有明顯上升。但也需要指出,第二種算法由于需要保存已經生成的丑數,因此需要一個數組,從而增加了空間消耗。如果是求第1500個丑數,將創建一個能容納1500個丑數的數組,這個數組占內存6KB。而第一種思路沒有這樣的內存開銷。總的來說,第二種思路相當于用較少的空間換取了時間效率上的提升。