Here's an answer that runs with constant memory, at the expense of CPU. This is not a good answer in the context of the original question (i.e. answer during an interview). But if the interview is 24 hours long, then it's not so bad. ;)
The idea is that if I have n which is a valid answer, then the next in the sequence is going to be n times some power of two, divided by some power of 5. Or else n times a power of 5, divided by a power of two. Provided it divides evenly. (...or the divisor can be 1 ;) in which case you're just multiplying by 2 or 5)
For example, to go from 625 to 640, multiply by 5 ** 4 / 2 ** 7. Or, more generally, multiply by some value of 2 ** m * 5 ** n
for some m, n where one is positive and one is negative or zero, and the multiplier divides the number evenly.
Now, the tricky part is find the multiplier. But we know a) the divisor must divide the number evenly, b) the multiplier must be greater than one (the numbers keep increasing), and c) if we pick the lowest multiplier greater than 1 (i.e. 1 < f < all other f's), then that's guaranteed to be our next step. The step after that will be it's lowest step.
The nasty part is finding the value of m, n. There are only log(n) possibilities, because there are only so many 2's or 5's to give up, but I had to add a factor of -1 to +1 as a sloppy way to deal with roundoff. So we only have to iterate through O(log(n)) each step. So it's O(n log(n)) overall.
The good news is, because it takes a value and finds the next value, you can start anywhere in the sequence. So if you want the next one after 1 billion, it can just find it by iterating through the 2 / 5's or 5 / 2's and picking the smallest multiplier greater than 1.
(python)
MAX = 30
F = - math.log(2) / math.log(5)
def val(i, j):
return 2 ** i * 5 ** j
def best(i, j):
f = 100
m = 0
n = 0
max_i = (int)(math.log(val(i, j)) / math.log(2) + 1) if i + j else 1
#print((val(i, j), max_i, x))
for mm in range(-i, max_i + 1):
for rr in {-1, 0, 1}:
nn = (int)(mm * F + rr)
if nn < -j: continue
ff = val(mm, nn)
#print(' ' + str((ff, mm, nn, rr)))
if ff > 1 and ff < f:
f = ff
m = mm
n = nn
return m, n
def detSeq():
i = 0
j = 0
got = [val(i, j)]
while len(got) < MAX:
m, n = best(i, j)
i += m
j += n
got.append(val(i, j))
#print('* ' + str((val(i, j), m, n)))
#print('- ' + str((v, i, j)))
return got
I validated the first 10,000 numbers this generates against the first 10,000 generated by the sorted list solution, and it works at least that far.
BTW the next one after a trillion seems to be 1,024,000,000,000.
...
Hm. I can get O(n) performance -- O(1) per value (!) -- and O(log n) memory usage by treating best()
as a lookup table that I extend incrementally. Right now it saves memory by iterating each time, but it's doing a lot of redundant calculations. By holding those intermediate values -- and a list of min values -- I can avoid the duplicate work & speed it up a lot. However, the list of intermediate values will grow with n, hence the O(log n) memory.