一、原题
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;
}
};