丑数的定义:丑数分解因子中只能有2,3,5,不能有其他数字。根据定义可知一个丑数是另外一个丑数乘以2,或3,或5得到的。第一个丑数为1.
本题的要求,输入一个正整数n,要求得到按升序排列的第n个丑数
public int getUglyNumber() {}
首先我们用最容易想到的解题方法:从1开始一个一个数判断,并用计数器累计目前的丑数序号,直至计数器为n,返回该数。
判断一个数是否为丑数:因为丑数由一系列2,3,5相乘得到,故我们可以让num一直除以2,3,5,若最后num==1,则该数是丑数。
public boolean isUglyNumber(int num) {
while (num % 2 == 0) {
num = num / 2;
}
while (num % 3 == 0) {
num = num / 3;
}
while (num % 5 == 0) {
num = num / 5;
}
if (num == 1) return true;
else return false;
然后采用逐个判断的方式,得到第n个丑数:
public int getUglyNumber(int targetIndex) {
int count = 0, num = 0;
while (count != targetIndex) {
if (isUglyNumber(num)) count++;
num++;
}
return num;
}
上面的解题方法理论上可行,但是面试是100%不会通过的,接下来讲解比较合理的方法。
根据上述可知,丑数都是由第一个丑数1乘以2,3,5得到的,那我们无须根据num++来一个个判断num是不是丑数,可以直接根据前面的丑数乘以2,3,5得到新的丑数。关键问题是,得到新的丑数之后,我们怎么知道它们的顺序问题,因为要求返回的是升序的第n个丑数。
每次得到新丑数有三种方式:a✖2,b✖3,c✖5,我们可以根据Math.Min(Math.min(a✖2,b✖3),c✖5)得到下一个丑数。a,b,c的选取很关键,因为num之前的所有丑数都可以当作a,b,c,但是这样选取会增加计算量,如果我们每次计算新丑数时,可以直接确定a,b,c那简直perfect。
最理想的状态是\[\left\{ \begin{array}{l}
{a_i} \times 2 > num > {a_{i - 1}} \times 2\\
{b_i} \times 3 > num > {b_{i - 1}} \times 3\\
{a_i} \times 5 > num > {a_{i - 1}} \times 5
\end{array} \right.\]
即刚好限制a,b,c,让他们分别乘2,3,5后的丑数刚好大于当前丑数num,刚好的意思时此处的a,b,c前面的a', b', c' 乘以2, 3, 5后还是小于num的。下面时具体的实现代码:
public int getUglyNumber(int targetIndex) {
int[] dp = new int[targetIndex];
int a = 0, b = 0, c = 0;
for (int i = 1; i < dp.length; i++) {
dp[i] = Math.min(Math.min(a * 2, b * 3), c * 5);
if (dp[i] == a * 2) a++;
if (dp[i] == b * 3) b++;
if (dp[i] == c * 5) c++;
}
return dp[targetIndex - 1];
}