44

Numbers whose only prime factors are 2, 3, or 5 are called ugly numbers.

Example:

1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, ... 

1 can be considered as 2^0.

I am working on finding nth ugly number. Note that these numbers are extremely sparsely distributed as n gets large.

I wrote a trivial program that computes if a given number is ugly or not. For n > 500 - it became super slow. I tried using memoization - observation: ugly_number * 2, ugly_number * 3, ugly_number * 5 are all ugly. Even with that it is slow. I tried using some properties of log - since that will reduce this problem from multiplication to addition - but, not much luck yet. Thought of sharing this with you all. Any interesting ideas?

Using a concept similar to Sieve of Eratosthenes (thanks Anon)

    for (int i(2), uglyCount(0); ; i++) {
        if (i % 2 == 0)
            continue;
        if (i % 3 == 0)
            continue;
        if (i % 5 == 0)
            continue;
        uglyCount++;
        if (uglyCount == n - 1)
            break;
    }

i is the nth ugly number.

Even this is pretty slow. I am trying to find the 1500th ugly number.

Penny Liu
  • 7,720
  • 5
  • 40
  • 66
Anil Katti
  • 1,305
  • 1
  • 14
  • 26
  • 27
    _Why_ are these numbers called ugly numbers? – SLaks Jan 05 '11 at 01:19
  • Are you trying to find the nth ugly number or trying to determine whether a number is ugly? – SLaks Jan 05 '11 at 01:20
  • 1
    In problems with integer arithmetics, avoid using floating point. – ruslik Jan 05 '11 at 01:21
  • I bet the people on *math* would be better suited to answering this. – Ben Jackson Jan 05 '11 at 01:22
  • 11
    +1 Interesting question :) These are called Hamming Numbers: http://en.wikipedia.org/wiki/Regular_number#Algorithms – AraK Jan 05 '11 at 01:22
  • 6
    I think the problem is equivalent to iterating over the exponents (x1, x2, x3) in 2**x1 * 3**x2 * 5**x3 in such a way so that the products come out in numerical order. – President James K. Polk Jan 05 '11 at 01:32
  • Python solution using generators that's very fast: http://code.activestate.com/recipes/576961/ – orangepips Jan 05 '11 at 03:11
  • I think you can get a sub-linear solution if you don't need the whole sequence (see my answer below). – jonderry Jan 05 '11 at 06:45
  • 2
    http://online-judge.uva.es/p/v1/136.html – starblue Jan 06 '11 at 07:19
  • the working O(n^(2/3)) solution is at http://stackoverflow.com/a/10160054/849891. see also http://rosettacode.org/wiki/Hamming_numbers#Direct_calculation_through_triples_enumeration. – Will Ness May 02 '15 at 12:17
  • I've just [**forked** (so not my solution) a codewars version in C sharp that's very small, simple and O(n)](http://www.codewars.com/kumite/557ee7b7c5a80a6ce20000cd?sel=557eea11e9ed9f49ad000121). – cregox Jun 15 '15 at 15:30
  • You don’t have to start from 1, you can go from last one you’ve found out.. – boateng Oct 09 '18 at 19:18

13 Answers13

41

A simple fast solution in Java. Uses approach described by Anon..
Here TreeSet is just a container capable of returning smallest element in it. (No duplicates stored.)

    int n = 20;
    SortedSet<Long> next = new TreeSet<Long>();
    next.add((long) 1);

    long cur = 0;
    for (int i = 0; i < n; ++i) {
        cur = next.first();
        System.out.println("number " + (i + 1) + ":   " + cur);

        next.add(cur * 2);
        next.add(cur * 3);
        next.add(cur * 5);
        next.remove(cur);
    }

Since 1000th ugly number is 51200000, storing them in bool[] isn't really an option.

edit
As a recreation from work (debugging stupid Hibernate), here's completely linear solution. Thanks to marcog for idea!

    int n = 1000;

    int last2 = 0;
    int last3 = 0;
    int last5 = 0;

    long[] result = new long[n];
    result[0] = 1;
    for (int i = 1; i < n; ++i) {
        long prev = result[i - 1];

        while (result[last2] * 2 <= prev) {
            ++last2;
        }
        while (result[last3] * 3 <= prev) {
            ++last3;
        }
        while (result[last5] * 5 <= prev) {
            ++last5;
        }

        long candidate1 = result[last2] * 2;
        long candidate2 = result[last3] * 3;
        long candidate3 = result[last5] * 5;

        result[i] = Math.min(candidate1, Math.min(candidate2, candidate3));
    }

    System.out.println(result[n - 1]);

The idea is that to calculate a[i], we can use a[j]*2 for some j < i. But we also need to make sure that 1) a[j]*2 > a[i - 1] and 2) j is smallest possible.
Then, a[i] = min(a[j]*2, a[k]*3, a[t]*5).

Nikita Rybak
  • 64,889
  • 22
  • 150
  • 172
  • 7
    @vardhan If you don't understand something, ask. Don't just 'fix' things. – Nikita Rybak Aug 22 '11 at 09:11
  • 4
    @vardhan "The 2nd solution is not completely linear -- The 3 while loops inside the for loops cannot be described as constant time." -- Um, utterly wrong. Each lasti ranges from 0 to at most n, *once total*, so they're O(n) *total*. Put another way, per iteration of the for loop, the average number of iterations of each of the 3 inner loops is <= 1, which is indeed constant time. – Jim Balter Mar 29 '13 at 06:39
  • 1
    Is the while loop necessary though? Won't prev be one of the three last? Like the top solution here: http://stackoverflow.com/questions/5505894/tricky-google-interview-question – Kakira Jun 29 '15 at 21:21
  • 1
    @Kakira `if` is enough ; but no, sometimes two or even all three have to be advanced at once ; in the linked solution the two `if`s are sequential, not exclusive; I think Dijkstra himself wrote this algo down with the `while`s, so as not to leave room for any doubt correctness-wise, I think his reasoning was. – Will Ness Dec 26 '15 at 13:25
  • 1
    The internal while is making this a bad reading, it looks like we can move last2 last3 or last5 for more than 1, which we cannot. :( It is confused if last2 is pointer or it is a power of 2 on first reading. :( Really no reason for that. We do not loop for more than 1. – Alex Peter Jun 15 '19 at 20:11
  • @AlexPeter correct, but needs knowledge / mathematical reasoning external to code. as per Dijkstra (and the guy is pretty famous), with the `while`s the code's correctness is self-evident, redundancy notwithstanding. – Will Ness Feb 03 '20 at 18:41
  • The first fragment will fail to find ugly numbers greater than `1845281250000000000`, which is the `11389`-th ugly number because `cur * 5` will overflow the range of type `long`. – chqrlie Apr 10 '20 at 16:02
11

I am working on finding nth ugly number. Note that these numbers are extremely sparsely distributed as n gets large.

I wrote a trivial program that computes if a given number is ugly or not.

This looks like the wrong approach for the problem you're trying to solve - it's a bit of a shlemiel algorithm.

Are you familiar with the Sieve of Eratosthenes algorithm for finding primes? Something similar (exploiting the knowledge that every ugly number is 2, 3 or 5 times another ugly number) would probably work better for solving this.

With the comparison to the Sieve I don't mean "keep an array of bools and eliminate possibilities as you go up". I am more referring to the general method of generating solutions based on previous results. Where the Sieve gets a number and then removes all multiples of it from the candidate set, a good algorithm for this problem would start with an empty set and then add the correct multiples of each ugly number to that.

Community
  • 1
  • 1
Anon.
  • 52,397
  • 8
  • 74
  • 83
  • 3
    +1 This solves the problem of finding the nth number fast. You should also add that going through the multiples of 2,3,5 in parallel will remove the need for a bool array. – moinudin Jan 05 '11 at 01:27
  • I was familiar with Sieve of Eratosthenes.. First I started thinking about generating a sorted list of all the ugly number - which was not quite clean. Then I ventures into the trivial solution (which was damn slow obviously). Sieve of Eratosthenes should help me solve the problem in O(U(n)) where U(n) is the nth ugly number. – Anil Katti Jan 05 '11 at 01:37
  • @Anil You don't have to store elements in array, you can use any other type of container, like heap. This can give you `O(n*logn)` easily. There's also an approach described by **marcog**: it'll give `O(n)`, but it's a bit trickier. – Nikita Rybak Jan 05 '11 at 01:39
  • 1
    @Anil: When I made the comparison to the Sieve, I didn't really mean "keep an array of bools and eliminate possibilities as you go up" - I was more referring to the general method of generating solutions based on previous results. Where the Sieve gets a result and the *removes* all multiples of it from the candidate set, a good algorithm for this problem would start with an empty set and then *add* the correct multiples of each ugly number to that. – Anon. Jan 05 '11 at 01:50
8

My answer refers to the correct answer given by Nikita Rybak. So that one could see a transition from the idea of the first approach to that of the second.

from collections import deque
def hamming():
    h=1;next2,next3,next5=deque([]),deque([]),deque([])
    while True:
        yield h
        next2.append(2*h)
        next3.append(3*h)
        next5.append(5*h)
        h=min(next2[0],next3[0],next5[0])
        if h == next2[0]: next2.popleft()
        if h == next3[0]: next3.popleft()
        if h == next5[0]: next5.popleft()

What's changed from Nikita Rybak's 1st approach is that, instead of adding next candidates into single data structure, i.e. Tree set, one can add each of them separately into 3 FIFO lists. This way, each list will be kept sorted all the time, and the next least candidate must always be at the head of one ore more of these lists.

If we eliminate the use of the three lists above, we arrive at the second implementation in Nikita Rybak' answer. This is done by evaluating those candidates (to be contained in three lists) only when needed, so that there is no need to store them.

Simply put:

In the first approach, we put every new candidate into single data structure, and that's bad because too many things get mixed up unwisely. This poor strategy inevitably entails O(log(tree size)) time complexity every time we make a query to the structure. By putting them into separate queues, however, you will see that each query takes only O(1) and that's why the overall performance reduces to O(n)!!! This is because each of the three lists is already sorted, by itself.

laike9m
  • 14,908
  • 16
  • 92
  • 123
chanp
  • 609
  • 7
  • 14
6

I believe you can solve this problem in sub-linear time, probably O(n^{2/3}).

To give you the idea, if you simplify the problem to allow factors of just 2 and 3, you can achieve O(n^{1/2}) time starting by searching for the smallest power of two that is at least as large as the nth ugly number, and then generating a list of O(n^{1/2}) candidates. This code should give you an idea how to do it. It relies on the fact that the nth number containing only powers of 2 and 3 has a prime factorization whose sum of exponents is O(n^{1/2}).

def foo(n):
  p2 = 1  # current power of 2
  p3 = 1  # current power of 3
  e3 = 0  # exponent of current power of 3
  t = 1   # number less than or equal to the current power of 2
  while t < n:
    p2 *= 2
    if p3 * 3 < p2:
      p3 *= 3
      e3 += 1
    t += 1 + e3
  candidates = [p2]
  c = p2
  for i in range(e3):
    c /= 2
    c *= 3
    if c > p2: c /= 2
    candidates.append(c)
  return sorted(candidates)[n - (t - len(candidates))]

The same idea should work for three allowed factors, but the code gets more complex. The sum of the powers of the factorization drops to O(n^{1/3}), but you need to consider more candidates, O(n^{2/3}) to be more precise.

Will Ness
  • 62,652
  • 8
  • 86
  • 167
jonderry
  • 21,385
  • 29
  • 93
  • 160
  • yes, the n^{2/3} is correct, though I didn't follow your arguments here. This is done by enumerating the `i,j,k` triples to not reach above an estimated value of n-th member of the sequence (since ln2, ln3, ln5 are known). Code and links [in this answer](http://stackoverflow.com/a/10160054/849891). – Will Ness Mar 26 '14 at 09:59
  • It's a shame that the only fast solution has so few votes. It will easily find the one millionth ugly number around 10^253 by my estimate. – gnasher729 Aug 30 '15 at 19:51
  • @gnasher729 1000000-th Hamming number is [5.19312780448E+83](http://ideone.com/q3fma), actually. – Will Ness Dec 26 '15 at 12:55
  • works for 100, 10000 (I verified that the results are correct -- the value returned is at the index `n` in the sequence, zero-based), but fails for 1000 with "list index out of range" error. https://ideone.com/6hnIxg – Will Ness Feb 11 '19 at 18:45
5

A lot of good answers here, but I was having trouble understanding those, specifically how any of these answers, including the accepted one, maintained the axiom 2 in Dijkstra's original paper:

Axiom 2. If x is in the sequence, so is 2 * x, 3 * x, and 5 * x.

After some whiteboarding, it became clear that the axiom 2 is not an invariant at each iteration of the algorithm, but actually the goal of the algorithm itself. At each iteration, we try to restore the condition in axiom 2. If last is the last value in the result sequence S, axiom 2 can simply be rephrased as:

For some x in S, the next value in S is the minimum of 2x, 3x, and 5x, that is greater than last. Let's call this axiom 2'.

Thus, if we can find x, we can compute the minimum of 2x, 3x, and 5x in constant time, and add it to S.

But how do we find x? One approach is, we don't; instead, whenever we add a new element e to S, we compute 2e, 3e, and 5e, and add them to a minimum priority queue. Since this operations guarantees e is in S, simply extracting the top element of the PQ satisfies axiom 2'.

This approach works, but the problem is that we generate a bunch of numbers we may not end up using. See this answer for an example; if the user wants the 5th element in S (5), the PQ at that moment holds 6 6 8 9 10 10 12 15 15 20 25. Can we not waste this space?

Turns out, we can do better. Instead of storing all these numbers, we simply maintain three counters for each of the multiples, namely, 2i, 3j, and 5k. These are candidates for the next number in S. When we pick one of them, we increment only the corresponding counter, and not the other two. By doing so, we are not eagerly generating all the multiples, thus solving the space problem with the first approach.

Let's see a dry run for n = 8, i.e. the number 9. We start with 1, as stated by axiom 1 in Dijkstra's paper.

+---------+---+---+---+----+----+----+-------------------+
| #       | i | j | k | 2i | 3j | 5k | S                 |
+---------+---+---+---+----+----+----+-------------------+
| initial | 1 | 1 | 1 | 2  | 3  | 5  | {1}               |
+---------+---+---+---+----+----+----+-------------------+
| 1       | 1 | 1 | 1 | 2  | 3  | 5  | {1,2}             |
+---------+---+---+---+----+----+----+-------------------+
| 2       | 2 | 1 | 1 | 4  | 3  | 5  | {1,2,3}           |
+---------+---+---+---+----+----+----+-------------------+
| 3       | 2 | 2 | 1 | 4  | 6  | 5  | {1,2,3,4}         |
+---------+---+---+---+----+----+----+-------------------+
| 4       | 3 | 2 | 1 | 6  | 6  | 5  | {1,2,3,4,5}       |
+---------+---+---+---+----+----+----+-------------------+
| 5       | 3 | 2 | 2 | 6  | 6  | 10 | {1,2,3,4,5,6}     |
+---------+---+---+---+----+----+----+-------------------+
| 6       | 4 | 2 | 2 | 8  | 6  | 10 | {1,2,3,4,5,6}     |
+---------+---+---+---+----+----+----+-------------------+
| 7       | 4 | 3 | 2 | 8  | 9  | 10 | {1,2,3,4,5,6,8}   |
+---------+---+---+---+----+----+----+-------------------+
| 8       | 5 | 3 | 2 | 10 | 9  | 10 | {1,2,3,4,5,6,8,9} |
+---------+---+---+---+----+----+----+-------------------+

Notice that S didn't grow at iteration 6, because the minimum candidate 6 had already been added previously. To avoid this problem of having to remember all of the previous elements, we amend our algorithm to increment all the counters whenever the corresponding multiples are equal to the minimum candidate. That brings us to the following Scala implementation.

def hamming(n: Int): Seq[BigInt] = {
  @tailrec
  def next(x: Int, factor: Int, xs: IndexedSeq[BigInt]): Int = {
    val leq = factor * xs(x) <= xs.last
    if (leq) next(x + 1, factor, xs)
    else x
  }

  @tailrec
  def loop(i: Int, j: Int, k: Int, xs: IndexedSeq[BigInt]): IndexedSeq[BigInt] = {
    if (xs.size < n) {
      val a = next(i, 2, xs)
      val b = next(j, 3, xs)
      val c = next(k, 5, xs)
      val m = Seq(2 * xs(a), 3 * xs(b), 5 * xs(c)).min

      val x = a + (if (2 * xs(a) == m) 1 else 0)
      val y = b + (if (3 * xs(b) == m) 1 else 0)
      val z = c + (if (5 * xs(c) == m) 1 else 0)

      loop(x, y, z, xs :+ m)
    } else xs
  }

  loop(0, 0, 0, IndexedSeq(BigInt(1)))
}
Abhijit Sarkar
  • 16,021
  • 13
  • 78
  • 152
  • what is the value of `Iterator.from(6).drop(1).next()`? isn't it **7**? if so, it would mean this code is wrong. as a test, what is the 1000th hamming number produced by this code, please? is it 51200000? – Will Ness Feb 11 '19 at 11:52
  • this code is wrong. it [produces e.g. 14=7*2](https://ideone.com/uOFrnK), 21 = 7*3, 22 = 11*2... – Will Ness Feb 11 '19 at 17:11
  • @WillNess fixed, thanks for finding the bug. I didn't try to generate the 1000 number, but I tested up to 15. Also, if I was going to use this code for generating a large sequence, I'd probably use a mutable sequence, and also try not to repeat `BigInt` multiplications. – Abhijit Sarkar Feb 12 '19 at 03:57
4

Basicly the search could be made O(n):

Consider that you keep a partial history of ugly numbers. Now, at each step you have to find the next one. It should be equal to a number from the history multiplied by 2, 3 or 5. Chose the smallest of them, add it to history, and drop some numbers from it so that the smallest from the list multiplied by 5 would be larger than the largest.

It will be fast, because the search of the next number will be simple:
min(largest * 2, smallest * 5, one from the middle * 3),
that is larger than the largest number in the list. If they are scarse, the list will always contain few numbers, so the search of the number that have to be multiplied by 3 will be fast.

ruslik
  • 13,918
  • 1
  • 33
  • 38
2

To find the n-th ugly number in O (n^(2/3)), jonderry's algorithm will work just fine. Note that the numbers involved are huge so any algorithm trying to check whether a number is ugly or not has no chance.

Finding all of the n smallest ugly numbers in ascending order is done easily by using a priority queue in O (n log n) time and O (n) space: Create a priority queue of numbers with the smallest numbers first, initially including just the number 1. Then repeat n times: Remove the smallest number x from the priority queue. If x hasn't been removed before, then x is the next larger ugly number, and we add 2x, 3x and 5x to the priority queue. (If anyone doesn't know the term priority queue, it's like the heap in the heapsort algorithm). Here's the start of the algorithm:

1 -> 2 3 5
1 2 -> 3 4 5 6 10
1 2 3 -> 4 5 6 6 9 10 15
1 2 3 4 -> 5 6 6 8 9 10 12 15 20
1 2 3 4 5 -> 6 6 8 9 10 10 12 15 15 20 25
1 2 3 4 5 6 -> 6 8 9 10 10 12 12 15 15 18 20 25 30
1 2 3 4 5 6 -> 8 9 10 10 12 12 15 15 18 20 25 30
1 2 3 4 5 6 8 -> 9 10 10 12 12 15 15 16 18 20 24 25 30 40

Proof of execution time: We extract an ugly number from the queue n times. We initially have one element in the queue, and after extracting an ugly number we add three elements, increasing the number by 2. So after n ugly numbers are found we have at most 2n + 1 elements in the queue. Extracting an element can be done in logarithmic time. We extract more numbers than just the ugly numbers but at most n ugly numbers plus 2n - 1 other numbers (those that could have been in the sieve after n-1 steps). So the total time is less than 3n item removals in logarithmic time = O (n log n), and the total space is at most 2n + 1 elements = O (n).

gnasher729
  • 47,695
  • 5
  • 65
  • 91
  • finding _n_ first members of Hamming sequence is an _O(n)_ time calculation. _n log n_ is totally unnecessary. the accepted answer's second version (under "edit") is _O(n)_. (it is also what Dijkstra wrote, down to the `while`s -- `if`s are enough really, but he wrote that using `while` leaves no room for doubt, correctness-wise). – Will Ness Dec 25 '15 at 12:31
2

Here is a correct solution in ML. The function ugly() will return a stream (lazy list) of hamming numbers. The function nth can be used on this stream.

This uses the Sieve method, the next elements are only calculated when needed.

datatype stream = Item of int * (unit->stream);
fun cons (x,xs) = Item(x, xs);
fun head (Item(i,xf)) = i;
fun tail (Item(i,xf)) = xf();
fun maps f xs = cons(f (head xs), fn()=> maps f (tail xs));

fun nth(s,1)=head(s)
  | nth(s,n)=nth(tail(s),n-1);

fun merge(xs,ys)=if (head xs=head ys) then
                   cons(head xs,fn()=>merge(tail xs,tail ys))
                 else if (head xs<head ys) then
                   cons(head xs,fn()=>merge(tail xs,ys))
                 else
                   cons(head ys,fn()=>merge(xs,tail ys));

fun double n=n*2;
fun triple n=n*3;

fun ij()=
    cons(1,fn()=>
      merge(maps double (ij()),maps triple (ij())));

fun quint n=n*5;

fun ugly()=
    cons(1,fn()=>
      merge((tail (ij())),maps quint (ugly())));

This was first year CS work :-)

fredley
  • 29,323
  • 39
  • 131
  • 223
1

I guess we can use Dynamic Programming (DP) and compute nth Ugly Number. Complete explanation can be found at http://www.geeksforgeeks.org/ugly-numbers/

#include <iostream>
#define MAX 1000

using namespace std;

// Find Minimum among three numbers
long int min(long int x, long int y, long int z) {

    if(x<=y) {
        if(x<=z) {
            return x;
        } else {
            return z;
        }
    } else {
        if(y<=z) {
            return y;
        } else {
            return z;
        }
    }   
}


// Actual Method that computes all Ugly Numbers till the required range
long int uglyNumber(int count) {

    long int arr[MAX], val;

    // index of last multiple of 2 --> i2
    // index of last multiple of 3 --> i3
    // index of last multiple of 5 --> i5
    int i2, i3, i5, lastIndex;

    arr[0] = 1;
    i2 = i3 = i5 = 0;
    lastIndex = 1;


    while(lastIndex<=count-1) {

        val = min(2*arr[i2], 3*arr[i3], 5*arr[i5]);

        arr[lastIndex] = val;
        lastIndex++;

        if(val == 2*arr[i2]) {
            i2++;
        }
        if(val == 3*arr[i3]) {
            i3++;
        }
        if(val == 5*arr[i5]) {
            i5++;
        }       
    }

    return arr[lastIndex-1];

}

// Starting point of program
int main() {

    long int num;
    int count;

    cout<<"Which Ugly Number : ";
    cin>>count;

    num = uglyNumber(count);

    cout<<endl<<num;    

    return 0;
}

We can see that its quite fast, just change the value of MAX to compute higher Ugly Number

1

Using 3 generators in parallel and selecting the smallest at each iteration, here is a C program to compute all ugly numbers below 2128 in less than 1 second:

#include <limits.h>
#include <stdio.h>

#if 0
typedef unsigned long long ugly_t;
#define UGLY_MAX  (~(ugly_t)0)
#else
typedef __uint128_t ugly_t;
#define UGLY_MAX  (~(ugly_t)0)
#endif

int print_ugly(int i, ugly_t u) {
    char buf[64], *p = buf + sizeof(buf);

    *--p = '\0';
    do { *--p = '0' + u % 10; } while ((u /= 10) != 0);
    return printf("%d: %s\n", i, p);
}

int main() {
    int i = 0, n2 = 0, n3 = 0, n5 = 0;
    ugly_t u, ug2 = 1, ug3 = 1, ug5 = 1;
#define UGLY_COUNT  110000
    ugly_t ugly[UGLY_COUNT];

    while (i < UGLY_COUNT) {
        u = ug2;
        if (u > ug3) u = ug3;
        if (u > ug5) u = ug5;
        if (u == UGLY_MAX)
            break;
        ugly[i++] = u;
        print_ugly(i, u);
        if (u == ug2) {
            if (ugly[n2] <= UGLY_MAX / 2)
                ug2 = 2 * ugly[n2++];
            else
                ug2 = UGLY_MAX;
        }
        if (u == ug3) {
            if (ugly[n3] <= UGLY_MAX / 3)
                ug3 = 3 * ugly[n3++];
            else
                ug3 = UGLY_MAX;
        }
        if (u == ug5) {
            if (ugly[n5] <= UGLY_MAX / 5)
                ug5 = 5 * ugly[n5++];
            else
                ug5 = UGLY_MAX;
        }
    }
    return 0;
}

Here are the last 10 lines of output:

100517: 338915443777200000000000000000000000000
100518: 339129266201729628114355465608000000000
100519: 339186548067800934969350553600000000000
100520: 339298130282929870605468750000000000000
100521: 339467078447341918945312500000000000000
100522: 339569540691046437734055936000000000000
100523: 339738624000000000000000000000000000000
100524: 339952965770562084651663360000000000000
100525: 340010386766614455386112000000000000000
100526: 340122240000000000000000000000000000000

Here is a version in Javascript usable with QuickJS:

import * as std from "std";

function main() {
    var i = 0, n2 = 0, n3 = 0, n5 = 0;
    var u, ug2 = 1n, ug3 = 1n, ug5 = 1n;
    var ugly = [];

    for (;;) {
        u = ug2;
        if (u > ug3) u = ug3;
        if (u > ug5) u = ug5;
        ugly[i++] = u;
        std.printf("%d: %s\n", i, String(u));
        if (u >= 0x100000000000000000000000000000000n)
            break;
        if (u == ug2)
            ug2 = 2n * ugly[n2++];
        if (u == ug3)
            ug3 = 3n * ugly[n3++];
        if (u == ug5)
            ug5 = 5n * ugly[n5++];
    }
    return 0;
}
main();
chqrlie
  • 98,886
  • 10
  • 89
  • 149
  • 1
    are you aware of [this](https://stackoverflow.com/questions/4600048/nth-ugly-number#comment48122940_4600048)? the [linked answer](https://stackoverflow.com/a/10160054/849891)'s code calculates 1 billionth H.N. in 0.02s and 1 trillionth in about 2s [on Ideone](https://ideone.com/gHPmTo). – Will Ness Apr 10 '20 at 19:26
  • 1
    @WillNess: Amazing contribution! but Haskell is so alien to non-afficionados. Do your published timings include the computation of the exact values, and the conversion to base 10? – chqrlie Apr 10 '20 at 19:40
  • the code calculates (2,3,5) exponents triples; exact values is a matter of simple BIGNUM arithmetic. it shows also its decimal approximation, e.g. 1B --> (1334,335,404) --> "6.216075755562335E+843". there's nothing especially haskelly about the algorithm. – Will Ness Apr 10 '20 at 19:45
  • I mean, the triples are exact, of course. exponentiation and printing (in decimal) is already provided by Haskell, so I haven't bothered reimplementing it. the interpreter responds to `2^1334*3^335*5^404` printing the result without delay (it says 0.02s after printing). It is easy to add this to the code on Ideone, I just didn't want to clutter up the output. – Will Ness Apr 10 '20 at 20:00
  • I've added the full exact number printout to [the Ideone entry](https://ideone.com/gHPmTo); the run time didn't change for the 1Bth number. for the 1Tth though the time grew by almost a second on top the previous 2 seconds. – Will Ness Apr 11 '20 at 08:53
  • @WillNess: The algorithm is simple and elegant, and Louis Klauder's top band idea is brilliant. Computing with base 2 logs saves about 10%. Just one remark on the algorithm: you sort the band at the end, but you just need to find the m-th entry in the sorted array, which can be computed faster with a custom partitioning function. Does Haskell provide such a primitive? The sorting time is probably negligible compared to the enumeration loops... – chqrlie Apr 11 '20 at 23:38
  • Louis Klauder's code in that (vanished?) DDJ blog thread was using an arbitrary top logvalue; then it would find the top index of the enumerated band that it chanced upon (this I don't exactly remember; maybe it was just printing few big HN's). My contribution was to add the estimation of that top logvalue and figuring out the empirical corrections (and band indexing). then, switching to logbase 2 and realizing that only *one* top `i` index needs to be used for each `(j,k)` pair if the width is < 1 (base 2). – Will Ness Apr 12 '20 at 06:05
  • re: finding the m-th entry, yes, but the band sorting time is indeed negligible AFAIR, complexity-wise for sure. re: Haskell's quick-partition, I don't know. the built-in sort is mergesort, so with it, laziness doesn't help and counting n-1 elements in the prefix to get to the nth is actually sorting the whole prefix AFAICS. taking just the head element is O(n), due to laziness. – Will Ness Apr 12 '20 at 06:06
  • er, Haskell's *quickselect... searching for "quickselect Haskell" does produce some hits, e.g. [on RosettaCode](https://rosettacode.org/wiki/Quickselect_algorithm#Haskell), but that one's O(n^2) in worst case. there's also a proper package [on hackage](http://hackage.haskell.org/package/quickselect-0.1.0.0/docs/Data-Select-Intro.html) but it's for vectors (though lists can be converted to and from, of course). – Will Ness Apr 12 '20 at 09:22
0

here is my code , the idea is to divide the number by 2 (till it gives remainder 0) then 3 and 5 . If at last the number becomes one it's a ugly number. you can count and even print all ugly numbers till n.

int count = 0;
for (int i = 2; i <= n; i++) {
    int temp = i;
    while (temp % 2 == 0) temp=temp / 2;
    while (temp % 3 == 0) temp=temp / 3;
    while (temp % 5 == 0) temp=temp / 5;
    if (temp == 1) {
        cout << i << endl;
        count++;
    }

}
  • this code is *exponential* in the (qubic root of the) number `k` of ugly numbers it produces: [`n ~ exp (k ^ (1/3))`](https://en.wikipedia.org/wiki/Regular_number#Number_theory). Dijkstra's algo is linear in `k`. It is shown in several answers here, e.g. [this](https://stackoverflow.com/a/32299730/849891). – Will Ness Feb 11 '19 at 09:37
-3

This problem can be done in O(1).

If we remove 1 and look at numbers between 2 through 30, we will notice that there are 22 numbers.

Now, for any number x in the 22 numbers above, there will be a number x + 30 in between 31 and 60 that is also ugly. Thus, we can find at least 22 numbers between 31 and 60. Now for every ugly number between 31 and 60, we can write it as s + 30. So s will be ugly too, since s + 30 is divisible by 2, 3, or 5. Thus, there will be exactly 22 numbers between 31 and 60. This logic can be repeated for every block of 30 numbers after that.

Thus, there will be 23 numbers in the first 30 numbers, and 22 for every 30 after that. That is, first 23 uglies will occur between 1 and 30, 45 uglies will occur between 1 and 60, 67 uglies will occur between 1 and 30 etc.

Now, if I am given n, say 137, I can see that 137/22 = 6.22. The answer will lie between 6*30 and 7*30 or between 180 and 210. By 180, I will have 6*22 + 1 = 133rd ugly number at 180. I will have 154th ugly number at 210. So I am looking for 4th ugly number (since 137 = 133 + 4)in the interval [2, 30], which is 5. The 137th ugly number is then 180 + 5 = 185.

Another example: if I want the 1500th ugly number, I count 1500/22 = 68 blocks. Thus, I will have 22*68 + 1 = 1497th ugly at 30*68 = 2040. The next three uglies in the [2, 30] block are 2, 3, and 4. So our required ugly is at 2040 + 4 = 2044.

The point it that I can simply build a list of ugly numbers between [2, 30] and simply find the answer by doing look ups in O(1).

  • There are 17 ugly numbers between 2 and 30, not 22. And adding 30 will not make another one. For example, 3 is ugly but 33 isn't. – interjay Oct 02 '14 at 17:52
  • 1
    Oops. I should have read the question more carefully. The problem that needs to be solved should be for numbers of the form 2^a*3^b*5^c. What I solved was for numbers that are **multiples** of 2, 3, and 5 and these include primes such as 7, 11, etc. – guidothekp Oct 02 '14 at 19:04
  • this answer makes absolutely no sense to me at all. you "can simply build a list of ugly numbers"?? the question is ***how***? – Will Ness Feb 11 '19 at 11:56
-3

Here is another O(n) approach (Python solution) based on the idea of merging three sorted lists. The challenge is to find the next ugly number in increasing order. For example, we know the first seven ugly numbers are [1,2,3,4,5,6,8]. The ugly numbers are actually from the following three lists:

  • list 1: 1*2, 2*2, 3*2, 4*2, 5*2, 6*2, 8*2 ...     ( multiply each ugly number by 2 )
  • list 2: 1*3, 2*3, 3*3, 4*3, 5*3, 6*3, 8*3 ...     ( multiply each ugly number by 3 )
  • list 3: 1*5, 2*5, 3*5, 4*5, 5*5, 6*5, 8*5 ...     ( multiply each ugly number by 5 )

So the nth ugly number is the nth number of the list merged from the three lists above:

1, 1*2, 1*3, 2*2, 1*5, 2*3 ...

def nthuglynumber(n):
    p2, p3, p5 = 0,0,0
    uglynumber = [1]
    while len(uglynumber) < n:
        ugly2, ugly3, ugly5 = uglynumber[p2]*2, uglynumber[p3]*3, uglynumber[p5]*5
        next = min(ugly2, ugly3, ugly5)
        if next == ugly2: p2 += 1        # multiply each number
        if next == ugly3: p3 += 1        # only once by each
        if next == ugly5: p5 += 1        # of the three factors
        uglynumber += [next]
    return uglynumber[-1]
  1. STEP I: computing three next possible ugly numbers from the three lists
    • ugly2, ugly3, ugly5 = uglynumber[p2]*2, uglynumber[p3]*3, uglynumber[p5]*5
  2. STEP II, find the one next ugly number as the smallest of the three above:
    • next = min(ugly2, ugly3, ugly5)
  3. STEP III: moving the pointer forward if its ugly number was the next ugly number
    • if next == ugly2: p2+=1
    • if next == ugly3: p3+=1
    • if next == ugly5: p5+=1
    • note: not using if with elif nor else
  4. STEP IV: adding the next ugly number into the merged list uglynumber
    • uglynumber += [next]
Will Ness
  • 62,652
  • 8
  • 86
  • 167
Zhan
  • 11
  • 3
  • Please format your answer properly. Python is meaningless if you don't. – Teepeemm Aug 30 '15 at 20:02
  • That's wrong. Ugly numbers include for example 60 = 2^2 * 3^1 * 5^1 which is not on any of the lists. – gnasher729 Aug 31 '15 at 15:50
  • 1
    no, i think the function covers the ugly number 60. try the the function: nthuglynumber(26) in python. it will return 60. – Zhan Aug 31 '15 at 16:38
  • @gnasher729 no, 60 is on all three lists: 60 = 30 * 2 = 10 * 3 = 12 * 5. – Will Ness Dec 26 '15 at 13:41
  • The explanation is wrong. Suppose we add "7*2", "7*3", "7*5" to the 3 lists. – derek Sep 04 '17 at 21:39
  • @derek the explanation might be incomplete but the code is actually *right*. too bad I can't upvote several times to compensate for the wrong downvotes. I asked for such capability on meta, and was voted down into oblivion, too. – Will Ness Feb 11 '19 at 09:03
  • @derek the explanation was incomplete but the code is actually right. I've updated the explanation and the example, for clarity. – Will Ness Feb 11 '19 at 09:26