23

In the expression

2x * 3y * 5z

The x, y and z can take non negative integer value (>=0).

So the function would generate a series of number 1,2,3,4,5,6,8,9,10,12,15,16....

  • I have a brute force solution.
  • I would basically iterate in a loop starting with 1 and in each iteration I would find if the current number factors are only from the set of 2,3 or 5.

What I would like to have is an elegant algorithm.

This is an interview question.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
deeKay
  • 277
  • 3
  • 9
  • Could you rewrite the expression using clearer syntax, maybe with some elements ? – JRL Aug 27 '11 at 15:04
  • Hmm, I'm quite certain I saw a similar question on SO, dealing only with 2^x * 5^y. But I can't find it now. i think that one was an interview question, too. – Tom Zych Aug 27 '11 at 15:13
  • 2
    The priority solution is nice, but I think one of the O(n) solutions should be accepted. – Neil G Aug 27 '11 at 20:15
  • see also : http://stackoverflow.com/questions/5505894/tricky-google-interview-question – vine'th Aug 28 '11 at 06:04
  • possibly a duplicate of http://stackoverflow.com/questions/4600048/nth-ugly-number – Kowser Aug 29 '11 at 07:29
  • http://stackoverflow.com/a/10160054/849891 shows an algorithm for direct calculation of n-th number in ~ n^(2/3) time, without calculating all of its preceding numbers in the sequence. – Will Ness Oct 20 '15 at 22:07

9 Answers9

33

This can be solved using a priority queue, where you store triplets (x, y, z) sorted by the key 2x3y5z.

  1. Start with only the triplet (0, 0, 0) in the queue.

  2. Remove the triplet (x, y, z) with the smallest key from the queue.

  3. Insert the three triplets (x+1, y, z), (x, y+1, z) and (x, y, z+1) in the queue. Make sure you don't insert anything that was already there.

  4. Repeat from step 2 until you've removed k triplets. The last one removed is your answer.

In effect, this becomes a sorted traversal of this directed acyclic graph. (First three levels shown here, the actual graph is of course infinite).

infinite graph

hammar
  • 138,522
  • 17
  • 304
  • 385
  • That wont work because for example 2^2=4 comes before 5^1 = 5 – Yochai Timmer Aug 27 '11 at 15:19
  • 7
    @Yochai, it will work, because the solution uses *priority* queue. – svick Aug 27 '11 at 15:21
  • So you define the priority as the lowest result from the triplets... ok, and remember which combination gave you the result so you can add the next three triplets... – Yochai Timmer Aug 27 '11 at 15:24
  • @Alexandre: I mean that when you visit _(0,1,0)_ in the graph above, _(1,1,0)_ would already have been added to the queue when you visited _(1,0,0)_. It is of course possible to ignore the duplicate later, although I'm not sure whether this would be more efficient than checking for duplicates as you go. – hammar Aug 27 '11 at 15:34
  • 2
    That solution takes O(k log k) time, because the priority queue will reach size O(k). My solution is faster :-) – meriton Aug 27 '11 at 16:06
  • @meriton The priority queue will exceed the size of k in fact. I am not sure by how many but looks like it would be exponential. Since for every removal you are adding three more elements in the queue. – deeKay Aug 28 '11 at 04:59
  • @deeKay: No, due to the duplicate removal it will not be exponential. My tests show that the size of the queue just after step 3 will be exactly _2k+1_, which is _O(k)_. – hammar Aug 28 '11 at 05:51
  • 1
    @hammar you can check for duplicates with a binary search in O(ln n), which is the same cost as inserting to a priority queue, so makes no change to the algorithmic complexity. – Dijkstra Sep 02 '11 at 16:39
  • @deeKay perhaps you meant that the PQ will be generated *past the end* of the sequence; but its *size* is `O(n^(2/3))`. Which is also bad, since the right solution uses only very small constant amount of memory in addition to the last `O(n^(2/3))` portion of the sequence itself, and runs in `O(n)` instead of `O(n log n)` time, to find the `n` elements. There is also a [direct solution](http://stackoverflow.com/a/10160054/849891) to find the n-th element in O(n^(2/3)) time, with extremely small constant; or a local `O(n)` one with `O(1)` memory (something about *ratios*). – Will Ness Apr 16 '12 at 14:51
15

This page lists solutions in bazillion programming languages. As usual, the Haskell version is particularly compact and straightforward:

hamming = 1 : map (2*) hamming `merge` map (3*) hamming `merge` map (5*) hamming
     where merge (x:xs) (y:ys)
            | x < y = x : xs `merge` (y:ys)
            | x > y = y : (x:xs) `merge` ys
            | otherwise = x : xs `merge` ys

Update As Will Ness has noted, there is a ready-made function in Data.List.Ordered which is a better choice than my merge (and it has a better name, too).

import Data.List.Ordered (union)
hamming = 1 : map (2*) hamming `union` map (3*) hamming `union` map (5*) hamming
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Laziness makes this quite elegant indeed. – hammar Aug 27 '11 at 15:49
  • The 'Alternate version using "Cyclic Iterators"' is a very pretty Python solution for anyone deciding which Python solution to read. – Neil G Aug 27 '11 at 20:13
  • This duplicates-removing merging function is called `union` now. It is in `Data.List.Ordered` package. The name `merge` should be left for the duplicates-preserving variant, as a part of `mergesort`. – Will Ness Apr 16 '12 at 13:27
  • @NeilG looks like the Python's `tee()` function used in "Cyclic iterators" creates three copies of the sequence, each consumed at its own pace - unlike Haskell which uses shared storage for all three. – Will Ness Apr 16 '12 at 13:41
11

The most straightforward solution I can think of:

    int[] factors = {2, 3, 5};
    int[] elements = new int[k];
    elements[0] = 1;
    int[] nextIndex = new int[factors.length];
    int[] nextFrom = new int[factors.length];
    for (int j = 0; j < factors.length; j++) {
        nextFrom[j] = factors[j];
    }
    for (int i = 1; i < k; i++) {
        int nextNumber = Integer.MAX_VALUE;
        for (int j = 0; j < factors.length; j++) {
            if (nextFrom[j] < nextNumber) {
                nextNumber = nextFrom[j];
            }
        }
        elements[i] = nextNumber;
        for (int j = 0; j < factors.length; j++) {
            if (nextFrom[j] == nextNumber) {
                nextIndex[j]++;
                nextFrom[j] = elements[nextIndex[j]] * factors[j];
            }
        }
    }
    System.out.println(Arrays.toString(elements));

This generates the first k elements of that set in ascending order in O(k) space and time.

Note that it is necessary to consume nextNumber from all j that provide it in order to eliminate duplicates (2*3 = 3*2 after all).

Edit: The algorithm uses the same approach as the haskell one posted by n.m.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
meriton
  • 68,356
  • 14
  • 108
  • 175
  • this is actually **the correct answer to the question** here (as well as the Haskell code - but this is in Java, as asked). I only made some very minor code improvement there, corresponding to the pseudocode in http://stackoverflow.com/a/10160054/849891 . – Will Ness Sep 17 '12 at 20:30
  • this actually corresponds to the [original code by Edsger Dijkstra](http://i.imgur.com/tPygG.gif). – Will Ness Sep 17 '12 at 20:37
6

This might be testing more than your knowledge of algorithms, to include how you think, solve problems, and work in a team.

It is important to have a decent specification of the problem before you begin. Some of the unknowns, as described, include:

  • are there bounds on K?
  • do you want a known algorithm or is ad-hoc brute force ok?
  • memory usage vs compute time? (maybe one or the other matters)
  • how fast does it have to calculate vs how much time do I have to develop it?
  • should results be cached?

Asking the interviewer about some or all of these questions may be at least as important as being able to answer the question asked. Of course, you can paint yourself into a corner this way, which can even be part of the test....

Paul
  • 26,170
  • 12
  • 85
  • 119
  • 1
    +1... You're right on spot. The one that cracks me up all the time in these "interview questions" is the lack of specs, which makes the question usually totally stupid. That's why problems stated like the ones from TopCoder or SPOJ are just **soooo** much better than most stupid interview question stupid interviewers come up with (and, yup, I've been conducting interview and, yup, they looked like TopCoder or SPOJ questions ; ) – SyntaxT3rr0r Aug 27 '11 at 17:21
2

Since the problem can be converted to finding Kth least number of

 f(x,y,z) = x log(2) + y log(3) + z log(5),

the algorithm might be following

  1. starts with f(x,y,z) = f(0,0,0)
  2. given current least number f(i,j,k) = v, you gotta find (x,y,z) such that f(x,y,z) is the closest to v and > v. Since

    log(2)<log(3)<2log(2)<log(5)

    We can say

    0<=i-2<=x<=i+2, 0<=j-1<=y<=j+1 & 0<=k-1<=z<=k+1 such that f(x,y,z) > v

So since this is to find the minimum of 45 values in each step and I would say it's O(K) algorithm. Of course, the number 45 can be reduced by imposing more conditions such as (x,y,z)!=(i,j,k).

Tae-Sung Shin
  • 20,215
  • 33
  • 138
  • 240
  • 1
    this is wrong, although thinking in correct direction (there *is* a local solution to this, which I still hasn't mastered myself though). To see why it's wrong, consider the number `2^64` corresponding to the tuple `(64,0,0)`, and its neighbors. The difference in `(i,j,k)` will be much more than 3 or 5. – Will Ness Apr 16 '12 at 14:20
1

There is a very elegant solution to this kind of problem. Algorithm and coding is simple. Time complexity is O(n)

I saw a similar problem somewhere. The problem was to generate the numbers of the form 2^x.3^y in ascending order.

So here goes.

int kthsmallest(int k){

    int two = 0, three = 0, five = 0;
    int A[k];
    A[0] = 1;
    for (int i=1; i<k; i++){
        int min = (A[two] * 2 <= A[three] * 3)? A[two] * 2: A[three] * 3;
        min = (min <= A[five] * 5)? min: A[five] * 5;
        A[i] = min;
        if (min == A[two] * 2)
            two++;
        if (min == A[three] * 3)
            three++;
        if (min == A[five] * 5)
            five++;
    }
    return A[k-1];
}

The algorithm is basically - keep three pointers for x, y, z. In the code, I used two, three and five. In every iteration, check which one smaller (2^x, 3^y or 5^z). Put that number in the ith index and increment the corresponding value of x or y or z. If there are more than one min values, then increment both the pointers.

Him
  • 318
  • 3
  • 11
1

Below is a working java based solution to find kth smallest number which has factors as only 2,3 and 5. Here 2*3*5 is considered as the smallest factor.

import java.util.Comparator;
import java.util.PriorityQueue;
public class KthSmallestFactor {

    public static void main(String[] args){

        for(int i=1;i<=10;i++){
            System.out.println(kthSmallest(i));
        }
    }

    private static int kthSmallest(int k){
        PriorityQueue<Triplet> p = new PriorityQueue<Triplet>(10, new Comparator<Triplet>() {
            public int compare(Triplet t1, Triplet t2) {
                int score1 = (int) (Math.pow(2, t1.a) * Math.pow(3, t1.b) * Math.pow(5, t1.c)) ; 
                int score2 = (int) (Math.pow(2, t2.a) * Math.pow(3, t2.b) * Math.pow(5, t2.c));
                return score1 -score2;
            }
        });

        p.add(new Triplet(1, 1, 1));
        int count =1;
        while(count <k){
            Triplet top = p.poll();
            count++;
            int a = top.a;
            int b = top.b;
            int c = top.c;
            Triplet t = new Triplet(a+1, b, c);
            if(!p.contains(t)){
                p.add(t);
            }
            t = new Triplet(a, b+1, c);
            if(!p.contains(t)){
                p.add(t);
            }
            t = new Triplet(a, b, c+1);
            if(!p.contains(t)){
                p.add(t);
            }
        }
        Triplet kth = p.poll();
        System.out.println("a: "+kth.a+"b: "+kth.b+"c: "+kth.c);
        return (int) (Math.pow(2, kth.a) * Math.pow(3, kth.b) * Math.pow(5, kth.c));
    }
}
class Triplet{
    int a ;
    int b;
    int c;

    public Triplet(int a , int b, int c){
        this.a = a;
        this.b=b;
        this.c = c;
    }

    public boolean equals(Object other){
        Triplet t = (Triplet)other;
        return this.a== t.a && this.b==t.b && this.c == t.c; 
    }
}
Ashish
  • 11
  • 2
1

These are the Hamming numbers, which I used as an example in SRFI-41. This was the code I used there:

(define hamming
  (stream-cons 1
    (stream-unique =
      (stream-merge <
        (stream-map (lsec * 2) hamming)
        (stream-map (lsec * 3) hamming)
        (stream-map (lsec * 5) hamming)))))
Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
user448810
  • 17,381
  • 4
  • 34
  • 59
  • 1
    only tangentially related, the duplicates-preserving `stream-merge` can (should?) be easily changed, with a little tweak, into a duplicates-removing `stream-union`, so that `stream-unique` call won't be needed at all. – Will Ness Apr 16 '12 at 14:12
-1

Start with x = y = z = 0; At each iteration compute three n's:

nx = 2^(x+1)*3^y*5^z
ny = 2^x*3^(y+1)*5^z
nz = 2^x*3^y*5^(z+1)

Find the least n among the three:

n = min(nx, ny, nz).

Increase either x, y, or z:

If n == nx -> x = x + 1
If n == ny -> y = y + 1
If n == nz -> z = z + 1

Stop after the K-th iteration and return n.

Itay Maman
  • 30,277
  • 10
  • 88
  • 118
  • 3
    This way, you would only ever generate numbers in the form `2^x`. Incrementing `x` always makes smaller number than incrementing `y` or `z`. – svick Aug 27 '11 at 15:18
  • I don't thinks this works, look at 8 to 9 . 8 = 2^3 , and 9 = 3^2 .. you would have find 2^4. (or i'm missing something ?) – Ricky Bobby Aug 27 '11 at 15:22
  • Looks like an incorrect solution. In the second iteration, I have x=1,y=0,z=0. Now at third iteration, nx = 4, ny=6, nz=10. The least of it is 4 (nx). But here the expected value should have been 3 and not 4. – deeKay Aug 27 '11 at 15:31
  • Let's say x = 1, y=0, z=0. There is no way to get x = 0, y = 1, z = 0 from your algorithm. – Tae-Sung Shin Aug 27 '11 at 15:34