A friend of mine is interviewing for a job. One of the interview questions got me thinking, just wanted some feedback.
There are 2 non-negative integers: i and j. Gi
You have to keep track of the individual exponents of them, and what their sums would be
so you start with f(0,0) --> 1
now you have to increment one of them:
f(1,0) = 2
f(0,1) = 5
so we know 2 is the next - we also know we can increment i's exponent up until the sum surpases 5.
You keep going back and forth like this until you're at your deisred number of rounds.
Dijkstra derives an eloquent solution in "A Discipline of Programming". He attributes the problem to Hamming. Here is my implementation of Dijkstra’s solution.
int main()
{
const int n = 20; // Generate the first n numbers
std::vector<int> v(n);
v[0] = 1;
int i2 = 0; // Index for 2
int i5 = 0; // Index for 5
int x2 = 2 * v[i2]; // Next two candidates
int x5 = 5 * v[i5];
for (int i = 1; i != n; ++i)
{
int m = std::min(x2, x5);
std::cout << m << " ";
v[i] = m;
if (x2 == m)
{
++i2;
x2 = 2 * v[i2];
}
if (x5 == m)
{
++i5;
x5 = 5 * v[i5];
}
}
std::cout << std::endl;
return 0;
}
If you draw a matrix with i as the row and j as the column you can see the pattern. Start with i = 0 and then just traverse the matrix by going up 2 rows and right 1 column until you reach the top of the matrix (j >= 0). Then go i + 1, etc...
So for i = 7 you travel like this:
7, 0 -> 5, 1 -> 3, 2 -> 1, 3
And for i = 8:
8, 0 -> 6, 1 -> 4, 2 -> 2, 3 -> 0, 4
Here it is in Java going up to i = 9. It prints the matrix position (i, j) and the value.
for(int k = 0; k < 10; k++) {
int j = 0;
for(int i = k; i >= 0; i -= 2) {
int value = (int)(Math.pow(2, i) * Math.pow(5, j));
System.out.println(i + ", " + j + " -> " + value);
j++;
}
}
here is a more refined way of doing it (more refined than my previous answer, that is):
imagine the numbers are placed in a matrix:
0 1 2 3 4 5 -- this is i
----------------------------------------------
0| 1 2 4 8 16 32
1| 5 10 20 40 80 160
2| 25 50 100 200 400 800
3| 125 250 500 1000 2000 ...
4| 625 1250 2500 5000 ...
j on the vertical
what you need to do is 'walk' this matrix, starting at (0,0)
. You also need to keep track of what your possible next moves are. When you start at (0,0)
you only have two options: either (0,1)
or (1,0)
: since the value of (0,1)
is smaller, you choose that. then do the same for your next choice (0,2)
or (1,0)
. So far, you have the following list: 1, 2, 4
. Your next move is (1,0)
since the value there is smaller than (0,3)
. However, you now have three choices for your next move: either (0,3)
, or (1,1)
, or (2,0)
.
You don't need the matrix to get the list, but you do need to keep track of all your choices (i.e. when you get to 125+, you will have 4 choices).
My implementation is based on the following ideas:
Example:
Start with 1 and 1 (to handle i=0;j=0 case):
Q2: 1
Q5: 1
Dequeue 1, print it and enqueue 1*2 and 1*5:
Q2: 2
Q5: 5
Pick 2 and add 2*2 and 2*5:
Q2: 4
Q5: 5 10
Pick 4 and add 4*2 and 4*5:
Q2: 8
Q5: 5 10 20
....
Code in Java:
public void printNumbers(int n) {
Queue<Integer> q2 = new LinkedList<Integer>();
Queue<Integer> q5 = new LinkedList<Integer>();
q2.add(1);
q5.add(1);
for (int i = 0; i < n; i++) {
int a = q2.peek();
int b = q5.peek();
int min = Math.min(a, b);
System.out.println(min);
if (min == a) {
q2.remove();
}
if (min == b) {
q5.remove();
}
q2.add(min * 2);
q5.add(min * 5);
}
}
The algorithm implemented by user515430 by Edsger Dijkstra (http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD792.PDF) is probably as fast as you can get. I call every number that is a form of 2^i * 5^j
a "special number". Now vlads answer would be O(i*j)
but with a double algorithm, one to generate the special numbers O(i*j)
and one to sort them (according to the linked article also O(i*j)
.
But let's check Dijkstra's algorithm (see below). In this case n
is the amount of special numbers we are generating, so equal to i*j
. We are looping once, 1 -> n
and in every loop we perform a constant action. So this algorithm is also O(i*j)
. And with a pretty blazing fast constant too.
My implementation in C++ with GMP (C++ wrapper), and dependancy on boost::lexical_cast
, though that can be easily remove (I'm lazy, and who doesn't use Boost?). Compiled with g++ -O3 test.cpp -lgmpxx -o test
. On Q6600 Ubuntu 10.10 time ./test 1000000
gives 1145ms
.
#include <iostream>
#include <boost/lexical_cast.hpp>
#include <gmpxx.h>
int main(int argc, char *argv[]) {
mpz_class m, x2, x5, *array, r;
long n, i, i2, i5;
if (argc < 2) return 1;
n = boost::lexical_cast<long>(argv[1]);
array = new mpz_class[n];
array[0] = 1;
x2 = 2;
x5 = 5;
i2 = i5 = 0;
for (i = 1; i != n; ++i) {
m = std::min(x2, x5);
array[i] = m;
if (x2 == m) {
++i2;
x2 = 2 * array[i2];
}
if (x5 == m) {
++i5;
x5 = 5 * array[i5];
}
}
delete [] array;
std::cout << m << std::endl;
return 0;
}