java丑数算法_求第n个丑数 两种方法实现 java实现

参考《剑指offer》

先说明题目:

我们把只包含因子2,3和5的数称为丑数(Ugly Number)。例如,6,8都是丑数,但14不是,因为它包含因子7,习惯上我们把一当作第一个丑数。

6=2x3;8=2x2x2; 14=2x7;

丑数顺序:1,2,3,4,5,6,8,9 可用于验证

相信大家已经读懂题目了,所以大家可以先试着写一写。

一开始,我把思路已经完全构架出来了,可是在最关键的判断是不是丑数这里没想明白(或许是当时脑子有点抽,再加上对这个概念接收得比较慢)。然后大家来看我的第一版本的代码,然后再分析:

public class UglyNumber {

public static void main(String[] args) {

int num = getUglyNumber(7);

System.out.println(num);

}

private static int getUglyNumber(int n) {

int count, flag = 0;

for (count = 1; flag < n; count++) {

boolean judge = isUgly(count);

if (judge) {

flag++;

}

}

return count - 1;

}

private static boolean isUgly(int count) {

while (count % 2 == 0) {

count /= 2;

}

while (count % 3 == 0) {

count /= 3;

}

while (count % 5 == 0) {

count /= 5;

}

return count == 1 ? true : false;

}

}

先看我搭建的框架吧,也就是getUglyNumber这个方法,相信大家很容易理解:

一个count从1开始往上计数,然后用flag来记录这是第几个丑数。也就是一种完全的遍历,把符合添加的挑出来,不符合的不理它,但还是要经过是否是丑数的验证。

然后我们来理解这个isUgly方法怎么判断是否为丑数的:

我是这样理解的,根据丑数的定义,将数字变为几个因子相乘的式子。

然后,把因子里面的2,3,5都去掉。

while (count % 2 == 0) {

count /= 2;

}

while (count % 3 == 0) {

count /= 3;

}

while (count % 5 == 0) {

count /= 5;

}

这部分代码就是把数字的因子里面的2,3,5去掉的。去掉因子这种思想很关键。我看了很多解释有点稀里糊涂的,直接说这作用具体是啥不就好了嘛。非得搞得跳过关键步骤或者花里胡哨的。

然后剩下的如果只有1,那么就说明这是一个丑数,否则不是。

还有,这里有个小秘籍,就是你在写代码的时候先不要理太细节的东西,比如我究竟该返回哪个数,先把主体的逻辑写出来,然后确认自己逻辑没错了,就直接填数进去看输出是啥。然后再自己去改变返回值,以满足程序的需要,当然,这些就是小细节的调整啦。能然你不要分心做其它事,先把主逻辑写完写正确才是最关键的。

如果在面试中,说出上面这种方法往往还不行,这时候就需要下面这种方法了。

前面的方法是从1往上逐个遍历,每个数字都判断一次是不是丑数,所以就导致了时间效率非常低下,所以这方法不行。

然后我们再重新观察丑数的定义,因子中都是2,3,5嘛。那么7,14这类数肯定就不会是的啦。接着我们就发现这样观察下来,丑数肯定是由丑数乘以2,3或者5再产生丑数的。这就是丑数不断产生的根本法则。

所以,我们会考虑,用一个数组把从小到大的丑数都装起来,然后你直接取第几个丑数就行了。

所以,关键在于怎么用代码把我们前面说的根本法则实现出来。大家可以自己先想想。

下面是《剑指offer》原文:(怕自己解释得不到位,觉得原文的解释更好理解)

我们需要确保数组里面的丑数十排好序的。假设数组里已经有的最大丑数记为M。那怎么产生下一个丑数呢?该丑数肯定是前面某一个丑数乘以2,3,5的结果,所以我们首先考虑把已有的每个丑数乘以2。在乘以2的时候,能得到若干个小于或等于M的结果。由于是按顺序生成,小于或者等于M的数肯定已经在数组中了,我们不需要在此考虑;还会得到若干个大于M的结果,但我们只需要第一个大于M的结果。因为我们希望丑数是按顺序从小到大生成的,其它更大的结果以后再说。我们把得到的第一个乘以2后大于M的结果记为M2,同样地有M3,M5。那么下一个丑数应该就是M2,M3,M5的最小值。

前面分析的时候提到把已有的每一个丑数分别乘以2,3,5.事实上这不是必须的,因为已有的丑数是按照顺序存放在数组中的。对于乘以2而言,肯定存在一个丑数T2,排在它之前的每个丑数乘以2得到的结果都会小于已有的最大丑数,在它之后的每个丑数乘以2得到的结果都会太大。我们只需记下这个丑数的的位置,同时每次生成新的丑数的时候去更新这个T2即可,对于3和5而言也是如此。

然后看我用java实现的代码:

private static int getUglyNumber(int n) {

int[] ugly = new int[n];

// nextIndex记录下一个存放数据的下标

// result2,result3,result5用于存储最小的边界的值。

// index系列是用于存储各个临界的下标。

int nextIndex = 0, result2 = 1, result3 = 1, result5 = 1, index2 = 0, index3 = 0, index5 = 0;

ugly[0] = 1;

while (nextIndex < n - 1) {

int temp = Min(result2 * 2, result3 * 3, result5 * 5);// 在这里进行真实的扩大,取最小值

nextIndex++;

ugly[nextIndex] = temp;

while (2 * result2 <= ugly[nextIndex]) {// 这里判断扩大后会不会超过边界值,小于的是已经有的,所以不要了。

index2++;

if (index2 >= n) {

break;

}

result2 = ugly[index2];

}

while (3 * result3 <= ugly[nextIndex]) {

index3++;

if (index3 >= n) {

break;

}

result3 = ugly[index3];

}

while (5 * result5 <= ugly[nextIndex]) {

index5++;

if (index5 >= n) {

break;

}

result5 = ugly[index5];

}

}

return ugly[n - 1];

}

private static int Min(int i, int j, int k) {

int min = i < j ? i : j;

min = min < k ? min : k;

return min;

}

这份代码用java写起来比较繁琐,c实现起来可能更容易理解。

第二种方法我理解起来花了比较多的时间,但是只要踏踏实实去认真理解就很快的。

这种方法用用空间换取了时间,用数组保存已经找到的丑数。在面试中能想到这种方法的话应该能给你的表现加不少分。

欢迎交流讨论。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值