Как генерировать числа с учетом их основных факторов, но с неизвестными показателями?

Возможные дубликаты:
nth уродливый номер
Найти наименьшее число Kth для выражения (2 ^ x) * (3 ^ y) * (5 ^ z)

Мне интересно, как решить эту проблему быстрым и элегантным способом:

Мы определяем "уродливое" каждое число n, которое можно записать в виде: 2 ^ x * 3 ^ y * 5 ^ z;, где x, y и z - натуральные числа. Найдите 1500-е уродливое число.

например. первые "уродливые" цифры:

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

Я попытался решить эту проблему с помощью грубой силы следующим образом:

import itertools as it

def is_ugly(n):
    '''Return `True` if *n* is an ugly number.'''

    if n == 1:
        return True
    while not n % 2:
        n //= 2
    while not n % 3:
        n //= 3
    while not n % 5:
        n //= 5
    return n == 1

def nth_ugly(n):
    '''Return the nth ugly number.'''

    num = 0
    for i in it.count(1):
        if is_ugly(i):
            num += 1
            if num == n:
                return i

Но это занимает довольно много времени, и я бы хотел найти более быстрое и лучшее решение.

Я знаю основные факторы уродливых чисел, но я не могу придумать способ генерации этих чисел в правильном порядке.

Я думаю, что должен быть способ генерировать эти числа без необходимости проверять все числа. Проблема состоит в том, что, по-видимому, экспоненты простых факторов распределены довольно случайным образом.

Посмотрите на эту таблицу:

n   |number| x | y | z |
------------------------
1   |  1   | 0 | 0 | 0 |
------------------------
2   |  2   | 1 | 0 | 0 |
------------------------
3   |  3   | 0 | 1 | 0 |
------------------------
4   |  4   | 2 | 0 | 0 |
------------------------
5   |  5   | 0 | 0 | 1 |
------------------------
6   |  6   | 1 | 1 | 0 |
------------------------
7   |  8   | 3 | 0 | 0 |
------------------------
8   |  9   | 0 | 2 | 0 |
------------------------
9   |  10  | 1 | 0 | 1 |
------------------------
10  |  12  | 2 | 1 | 0 |
------------------------
11  |  15  | 0 | 1 | 1 |
------------------------
12  |  16  | 4 | 0 | 0 |
------------------------
13  |  18  | 1 | 2 | 0 |
------------------------
14  |  20  | 2 | 0 | 1 |
------------------------
15  |  24  | 3 | 1 | 0 |
------------------------

Как вы можете видеть, значения x, y и z, похоже, не следуют никакому правилу.

Может ли кто-нибудь из вас найти какое-либо решение этой проблемы?

Я собираюсь разделить проблему в разных частях. Поскольку проблема определяется случайностью экспонентов, я мог бы попытаться независимо генерировать степени 2s, 3s, 5s, а затем числа вида 2 ^ x * 3 ^ y, 2 ^ x * 5 ^ z и т.д. И, наконец, собрать их вместе, но я не знаю, решит ли это мою проблему.

Ответ 1

Вот полное решение. O (n), он генерирует каждое число один раз и в порядке.

# http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD792.PDF

n = 15
bases = [2, 3, 5]

nums = [1] * n
candidates_indexes = [0 for _ in bases]
candidates = [base for base in bases]

for i in range(1, n):
    nextn = min(candidates)
    nums[i] = nextn

    for index, val in enumerate(candidates):
        if val == nextn:
            candidates_indexes[index] += 1
            candidates[index] = bases[index] * nums[candidates_indexes[index]]

print(nums)

Ответ 2

Здесь решение с использованием кучи. Идея состоит в том, что мы отслеживаем экспонентов вместе с уродливым продуктом. Для каждой итерации алгоритм выполняет до 3 операций find_min и 3 операции вставки. Параметры find_mins могут быть избыточными, поскольку вы можете получить их каждый, добавив один к любому экспоненте, и есть три показателя. Мы вставляем три раза, потому что добавляем один к каждому экспоненту и вставляем его в кучу. Чтобы найти n-е уродливое число, мы должны выполнить N операций, которые являются 6 * O (log n), поэтому алгоритм имеет временную сложность O (n log n). Сама куча, поскольку она может расти только на постоянную величину для каждой итерации, - это SPACE (O (n))

>>> import heapq, itertools
>>> class UglyGen(object):
...     def __init__(self, x, y, z):
...         self.x, self.y, self.z = x, y, z
...         self.value = 2**self.x * 3**self.y * 5**self.z
...     def incr(self):
...         x, y, z = self.x, self.y, self.z
...         return (UglyGen(x+1, y, z),
...                 UglyGen(x, y+1, z),
...                 UglyGen(x, y, z+1))
...     def __lt__(self, other):
...         if not isinstance(other, UglyGen):
...             return NotImplemented
...         return self.value < other.value
... 
>>> def gen_ugly():
...     gens = [UglyGen(0, 0, 0)]
...     last = 0
...     while gens:
...         while gens[0].value == last:
...             heapq.heappop(gens)
...         top = heapq.heappop(gens)
...         succ_items = top.incr()
...         for succ in succ_items:
...             heapq.heappush(gens, succ)
...         last = top.value
...         yield last
... 
>>> def find_nth_ugly(n):
...     for n0, u in itertools.izip(xrange(n), gen_ugly()):
...         pass
...     return u
... 
>>> find_nth_ugly(15)
24

Ответ 3

используйте список (n в следующем коде), чтобы сохранить все предыдущие уродливые числа, следующее уродливое число - это минимальное число (n * 2, n * 3, n * 5), которое больше, чем n [- 1]:

n = [1]
while len(n) < 1500:
    n.append(min([next(x*s for x in n if x*s>n[-1]) for s in (2,3,5)]))    
print n[-1]

Это решение O (n ^ 2), поэтому не пытайтесь использовать большое число.

Ответ 4

Я предлагаю вам решить эту проблему постепенно и сохранить все уродливые числа, которые вы найдете в списке.

При проверке номера для уродства тогда вам нужно только проверить, является ли это одним из ваших номеров раз 2, 3 или 5.

EDIT: я просто попытался реализовать это, как этот

ugly = [1]
candidate = 2
while len(ugly) < 1500:
    for u in ugly:
        if any([(u * x == candidate) for x in (2, 3 ,5)]):
            ugly.append(candidate)
            break
    candidate += 1

print ugly[-1]

но этот подход безнадежно застаивается. Слишком наивный.:) Используйте своеобразное сито Эратосфена.