一、原題
Write a program to find the n-th ugly number.
Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. For example, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 is the sequence of the first 10 ugly numbers.
Note that 1 is typically treated as an ugly number, and n does not exceed 1690.
二、題意
定義丑數:因子只由2、3、5組成的正整數,例如前十個丑數為:1, 2, 3, 4, 5, 6, 8, 9, 10, 12。問如何找出第n個丑數。
三、思路
如果寫出下面這個判斷丑數的函數來判斷每一個數是不是丑數,則最大的問題就是每個整數都需要計算,包括一個不是丑數的數字,而且需要重復求余和除法,算法時間效率不高。
bool isUgly(int num){
while(num % 2 == 0){
num /= 2;
}
while(num % 3 == 0){
num /= 3;
}
while(num % 5 == 0){
num /= 5;
}
return num == 1;
}
前面的方法效率之所以低是因為不管一個數是不是丑數都要判斷一遍。我們可以考慮找到一種計算丑數的方法,而不在非丑數的整數上花費時間即可。根據丑數的定義,丑數應該是另一個丑數乘以2、3或5(1除外),所以可以創建數組保存已經找到的丑數,后面的丑數都是前面的某一個丑數乘以2、3或5的結果。
可以通過創建數組來保存已排好序的丑數,假設數組中已經有若干排好序的丑數,并把最大的丑數記為M,則分析如何生成下一個丑數:
- 假設數組中已經有若干個丑數已經排好序存放在數組中,并把已有最大的丑數記為M;
- 下一個丑數肯定是前面某一個丑數乘以2、3、5的結果;
- 先考慮把已有的每個丑數乘以2,在乘以2的時候,能得到若干個小于或等于M的結果,由于是按照順序生成的,小于或等于M的已經在數組中了,不許再考慮;而得到若干個大于M的結果,我們只需要第一個大于M的結果,因為我們希望丑數是按從小到大的順序生成的,其他更大的結果以后再說。
- 把第一個乘以2后大于M的結果記為M2,同樣,把以后的每個丑數乘以3和5,能得到第一個大于M的結果M3和M5,那么下一個丑數應該是M2、M3、M5這三個數的最小值;
- 以上過程把以后的每個丑數乘以2、3、5,事實上并不是必須的,因為已有的丑數是按順序存放在數組中的。對乘以2而言,肯定存在某一個丑數T2,排在它之前的每一個丑數乘以2得到的結果都會小于已有的最大丑數;在它之后的每一個丑數乘以2得到的結果都會太大,只需要記下這個丑數的位置,同時每次生成新的丑數的時候,去更新這個T2,對于3、5而言,也存在同樣的T3和T5。
- 使用p2來記錄T2的坐標,p3記錄T3的坐標,p5記錄T5的坐標,初始狀態p2=p3=p5=0,p2、p3和p5依次遞增指向前面已排好序的丑數。
四、代碼
class Solution {
public:
int min(int a, int b, int c){
int m = a < b ? a : b;
return m < c ? m : c;
}
int nthUglyNumber(int n) {
if(n == 1) return 1;
int *dp = new int[n + 1];
dp[0] = 1;
int p2 = 0;
int p3 = 0;
int p5 = 0;
for(int i = 1; i <= n; ++i){
dp[i] = min(2 * dp[p2], 3*dp[p3], 5*dp[p5]);
if(2 * dp[p2] == dp[i]){
p2++;
}
if(3 * dp[p3] == dp[i]){
p3++;
}
if(5 * dp[p5] == dp[i]){
p5++;
}
}
int res = dp[n - 1];
delete[] dp;
return res;
}
};