丑数II(Ugly Number II)

本文介绍了一种高效算法用于找到第N个丑数。丑数是只包含2、3和5作为质因数的正整数。文章首先概述了基本的遍历方法,随后提出了一种利用动态规划原理的优化方案,该方案通过维护一个有序数组来快速生成丑数序列。
摘要由CSDN通过智能技术生成

Question:
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.

简单地说就是找出第n个丑数。我们知道,1是第一个丑数,2是第二个丑数,3是第三个丑数,……,所以按照丑数的性质找出第n个丑数,第10个丑数是12。
最直接的算法就是,从1开始,对每一个数都判断一下,这个数是不是丑数,如果是的话,那么丑数的个数加1,直至第n个丑数。如何判断一个数是不是丑数,请看上一篇博客。伪代码如下:

    GET-Nth-Ugly-Number(n)
    1. if n <= 0 
    2.      return -1
    3. index = 0
    4. num = 1
    5. while true 
    6.     isUglyNum = Is-Ugly-NUMBER(num)
    7.     if isUglyNum
    8.         index ++
    9.     if index == n
    10.         break
    11.     num ++
    12. return num

这个算法看起来实在是太直接了,虽然能解决问题,但是时间效率不高。因为丑数远远比自然数要少,如第100个丑数是1536。如果要求第100个丑数,则while的整个循环体需要循环1536次。当n更大时,这个循环的次数将会大大增加。
为了寻找更快的算法,我们需要另辟蹊径。不知道还记得斐波那契数列否?斐波那契数列的n个数的产生是依赖于第n-1n-2个数的(n>=3)。所以我们可以想这样的一个问题: 第n个丑数是否和之前的丑数相关?一个丑数其实就是若干个2、3和5相乘而得到的。也就说第n个丑数的因子在前面的丑数中肯定出现过。如第10个丑数12,它的因子6就是6个丑数。那么第n个丑数的产生由前哪个丑数来决定的呢?我们可以肯定的是,是由前面某的一个丑数乘以2,3或者5得到的。现在的关键问题是:我们如何得到一个排序好的丑数序列?
假设现在有一个已经排好序的丑数数组,其中最大的丑数M排在数组的最后一个位置。如果我们将这所有的丑数都乘以2,那么就会得到很多丑数,这些丑数有的比M大,有的比M小。我们假设第一个比M大的数为M2。同理,我们可以将所有的丑数乘以3和5,得到第一个比M大的数称为M3和M5。这样M2,M3和M5都比M大。取这三者之中的最小者,就是下一个丑数了。
实际上,我们每次在计算下一个丑数的时候,并不需要每次都将丑数全部乘以2,乘以3和乘以5。我们可以用L2、L3和L5来表示M2,M3和M5在丑数序列U中的位置。即U[L2]=M2, U[L3] = M3, U[L5] = M5。假设2*M2是三者中最小的,那么下一个丑数N=2*M2,此时L2 = L2 + 1,即L2向右挪了一个位置,L3和L5保持不变。那么在产生下下一个丑数的时候,由于3*M3,5*M5保持不变,依旧比N大。而L2向右挪了一个位置,我们知道,原来的丑数序列是排好序的,所以2*U[L2]比N大,所以三者的最小值依旧比N大,以此就产生了一个依次递增的丑数序列了。
基于这样的思路,我们可以写出下面的伪代码:

    GET-Nth-Ugly-Number(n)
    1. if n <= 0
    2.     return -1
    3. index_two = index_three_index_five = 0
    4. let UglyNum be an array of size n
    5. UglyNum[0] = 1
    6. for i = 1 to n -1
    7.     min = Min(UglyNum[index_two]*2, UglyNum[index_three]*3, UglyNum[index_five]*5)
    8.     UglyNum[i] = min
    9.     if min == UglyNum[index_two]*2
    10.         index_two ++
    11.     if min == UglyNum[index_three]*3
    12.         index_three ++
    13.     if min == UglyNum[index_five]*5
    14.         index_five ++
    15. return UglyNum[n-1]

第1~2行是输入参数检测,如果是非正整数,则输出-1,说明这是一个无效值。第3行是初始化三个下标,分别指示M2,M3和M5所在的位置,假设一刚开始是指向的都是第一个位置。第4行是初始化一个n维数组,这个数组用来保存这n个丑数的。6~14行是用来生成除了1之外的其他n-1个丑数。第7行是找出U2*2、U3*3U5*5当中的最小值。第8行是将这个最小值最为第i个丑数。第9~14行是判断这个丑数是由之前的哪个丑数产生,并将其位置向右挪一位。以下使用Java和Swift分别实现的算法程序。

用Java语言实现这个算法:

public class Solution {

    private int min(int a, int b, int c){

        int min = a;
        if(b < min) min = b;
        if(c < min) min = c;

        return min;
    }


    public int nthUglyNumber(int n) {

        if(n <= 0) return 0;

        int[] uglyArray = new int[n];
        int twoIndex = 0;
        int threeIndex = 0;
        int fiveIndex = 0;
        uglyArray[0] = 1;

        for(int i = 1; i < n; i ++){
            int ugly = min(uglyArray[twoIndex]*2, uglyArray[threeIndex]*3, uglyArray[fiveIndex]*5);

            if (ugly == uglyArray[twoIndex]*2){
                twoIndex ++;
            }

            if (ugly == uglyArray[threeIndex]*3){
                threeIndex ++;
            }

            if (ugly == uglyArray[fiveIndex]*5){
                fiveIndex ++;
            }

            uglyArray[i] = ugly;

        }
        return uglyArray[n-1];
    }
}

用Swift语言实现这个算法

import Foundation

func min(a: Int, b: Int, c: Int) -> Int {

    var min = a
    if b < min {
        min = b
    }
    if c < min {
        min = c
    }

    return min
}

func nthUglyNumber(n: Int) -> Int {

    if n <= 0 {
        return -1
    }
    if n == 1 {
        return 1
    }

    var twoIndex = 0;
    var threeIndex = 0;
    var fiveIndex = 0;
    var uglyArray: [Int] = Array(count: n, repeatedValue: 0)
    uglyArray[0] = 1

    for i in 1...n-1 {

        let arglNum = min(uglyArray[twoIndex]*2, b: uglyArray[threeIndex]*3, c: uglyArray[fiveIndex]*5)
        uglyArray[i] = arglNum
        if arglNum == uglyArray[twoIndex]*2 {
            twoIndex += 1
        }
        if arglNum == uglyArray[threeIndex]*3 {
            threeIndex += 1
        }
        if arglNum == uglyArray[fiveIndex]*5 {
            fiveIndex += 1
        }

    }


    return uglyArray[n-1]
}

题目来源:
LeetCode 算法题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值