[2021校招必看之Java版《剑指offer》-33] 丑数

1、题目描述

  【JZ33】把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
  知识点:穷举
  难度:☆☆☆☆

2、解题思路

2.1 无脑穷举

  写一个函数来判断某个数字是不是丑数:
  1、这个数字先不断除以2,直到不能整除,就进入下一步;
  2、第1步的结果不断除以3,直到不能整除,进入下一步;
  3、第2步的结果不断除以5,直到不能整除,进入下一步;
  4、判断第3步的结果是否为1,是则丑数,不是则不是丑数。

  题意要求写一个函数,返回从小到大的第 N 个丑数。
  1、定义一个数字 num = 1,和一个数组 result 用来装丑数;
  2、判断 num 是不是丑数:
  2.1 是丑数,装入 result,并且num++;
  2.2 不是丑数,num++,回到第2步;

  当装了 N 个丑数后,返回最后一个即可。

  这种穷举只适合 N 比较小的情况,而且这个方法在牛客网中直接不通过,因为时间复杂度太高。

2.2 动态规划

  首先分析本题的丑数的定义:num = 2x × 3y × 5z,x≥0,y≥0,z≥0。

  当 x=y=z=0 时,就是第一个丑数:1。

  因为丑数集合要求从小到大排列,通过分析,我们可以知道,第 N 个丑数,一定是第 1 到第 N-1 的丑数集合中的某一个丑数乘以 2 或乘以 3 或乘以 5 的结果。

  我们设置三个指针:x、y、z:
  x:表示所指丑数乘以2是下一个可能加入到丑数集合中的丑数;
  y:表示所指丑数乘以3是下一个可能加入到丑数集合中的丑数;
  z:表示所指丑数乘以5是下一个可能加入到丑数集合中的丑数。

  这三个指针一开始:x = y = z = 0。

  下面通过计算推到,最后再总结三个指针的更新标准。

  定义一个 result[] 来存储丑数,result[0] = 1

  当 N = 2时,我们要计算 result[1] 的值。
  result[t2] * 2 = 1 * 2 = 2;
  result[t3] * 3 = 1 * 3 = 3;
  result[t5] * 5 = 1 * 5 = 5;
  通过Math.min(int a, int b);函数得出上面三个最小值为 2,且是 x 的作用,所以 result[1] = 2,x++。

  当 N = 3 时,要计算 result[2] 的值。此时:x = 1,y = 0,z = 0。
  result[t2] * 2 = 2 * 2 = 4;
  result[t3] * 3 = 1 * 3 = 3;
  result[t5] * 5 = 1 * 5 = 5;
  通过Math.min(int a, int b);函数得出上面三个最小值为 3,且是 y 的作用,所以 result[2] = 3,y++。

  通过上面的计算,我们可以总结出:对于第 N 个丑数,它是前面已经计算出来的某个丑数乘以2或乘以3或乘以5的结果,那这个“某个丑数”到底是哪个丑数呢?

  1、如果这个“某个丑数”是要乘以2的,那么这个“某个丑数”就是 result[x];
  2、如果这个“某个丑数”是要乘以3的,那么这个“某个丑数”就是 result[y];
  3、如果这个“某个丑数”是要乘以5的,那么这个“某个丑数”就是 result[z];

3、解题代码

3.1 无脑穷举

package pers.klb.jzoffer.hard;

/**
 * @program: JzOffer2021
 * @description: 丑数
 * @author: Meumax
 * @create: 2020-07-24 10:40
 **/
public class UglyNumber {

    public int GetUglyNumber_Solution(int index) {
        if (index < 1) return 0;
        int[] result = new int[index];

        int num = 1;

        for (int i = 0; i < index; i++) {
            while (!isUglyNum(num)) num++;
            result[i] = num;
            num++;
        }

        return result[index - 1];
    }

    private boolean isUglyNum(int num) {
        if (num < 1) return false;
        while (num % 2 == 0) num = num / 2;
        while (num % 3 == 0) num = num / 3;
        while (num % 5 == 0) num = num / 5;
        return num == 1;
    }
}

3.2 动态规划

package pers.klb.jzoffer.hard;

/**
 * @program: JzOffer2021
 * @description: 丑数
 * @author: Meumax
 * @create: 2020-07-24 10:40
 **/
public class UglyNumber {

    public int GetUglyNumber_Solution(int index) {
        if (index < 1) return 0;
        int[] result = new int[index];
        result[0] = 1;
        int t2 = 0;
        int t3 = 0;
        int t5 = 0;
        for (int i = 1; i < index; i++) {
            result[i] = Math.min(result[t2] * 2, Math.min(result[t3] * 3, result[t5] * 5));
            if (result[i] == result[t2] * 2) t2++;
            if (result[i] == result[t3] * 3) t3++;
            if (result[i] == result[t5] * 5) t5++;
        }
        return result[index - 1];
    }
}

4、解题心得

  编程题有时候就是数学题,当数学原理明白了,其实编程已经是最后一步了。最难的,往往是数学。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值