уродливое число - PullRequest
       56

уродливое число

37 голосов
/ 05 января 2011

Числа, чьи единственные простые множители 2, 3 или 5, называются некрасивыми числами.

Пример:

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

1 можно рассматривать как 2 ^ 0.

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

Я написал тривиальную программу, которая вычисляет, является ли данное число безобразным или нет. Для n> 500 - это стало очень медленно. Я попытался использовать памятку - наблюдение: ugly_number * 2, ugly_number * 3, ugly_number * 5 все безобразно. Даже при том, что это медленно. Я попытался использовать некоторые свойства журнала - так как это уменьшит эту проблему от умножения до сложения - но пока не так много удачи. Мысль поделиться этим со всеми вами. Есть интересные идеи?

Использование концепции, аналогичной "Сите Эратосфена" (спасибо, Анон)

    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-е уродливое число.

Даже это довольно медленно. Я пытаюсь найти 1500-е уродливое число.

Ответы [ 12 ]

37 голосов
/ 05 января 2011

Простое быстрое решение на Java. Использует подход, описанный Anon. .
Здесь TreeSet - это просто контейнер, способный возвращать наименьший элемент в нем. (Дубликаты не сохраняются.)

    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);
    }

Поскольку 1000-е уродливое число - 51200000, сохранение их в bool[] на самом деле не вариант.

1012 * редактировать *
В качестве отдыха от работы (отладка тупого Hibernate), вот полностью линейное решение. Спасибо marcog за идею!

    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]);

Идея состоит в том, что для вычисления a[i] мы можем использовать a[j]*2 для некоторых j < i. Но мы также должны убедиться, что 1) a[j]*2 > a[i - 1] и 2) j наименьшее возможное.
Тогда a[i] = min(a[j]*2, a[k]*3, a[t]*5).

10 голосов
/ 05 января 2011

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

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

Это выглядит как неправильный подходдля задачи, которую вы пытаетесь решить - это что-то вроде алгоритма Шлемиеля.

Вы знакомы с алгоритмом Сито Эратосфена для поиска простых чисел?Нечто подобное (использование знания о том, что каждое уродливое число в 2, 3 или 5 раз больше другого уродливого числа), вероятно, будет работать лучше для решения этой проблемы.

По сравнению с Ситом я не имею в виду "сохранить массивbools и исключить возможности по мере подъема ".Я больше ссылаюсь на общий метод генерации решений, основанный на предыдущих результатах.Если сито получает число, а затем удаляет все его кратные из набора кандидатов, хороший алгоритм для этой задачи должен начинаться с пустого набора, а затем добавлять правильные значения, кратные каждому уродливому числу.

7 голосов
/ 25 января 2012

Мой ответ относится к правильному ответу, данному Никита Рыбак .Чтобы можно было увидеть переход от идеи первого подхода ко второму.

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()

Что отличается от первого подхода Никиты Рыбака, так это то, что вместо добавления следующих кандидатов в единую структуру данных, т.е.Древовидный набор, каждый из которых можно добавить отдельно в 3 списка FIFO.Таким образом, каждый список будет постоянно сортироваться, и следующий наименьший кандидат должен всегда находиться в head одного или нескольких из этих списков.

Если мы исключим использованиетри списка выше, мы приходим ко второй реализации в Никита Рыбак 'ответ.Это делается путем оценки этих кандидатов (которые должны содержаться в трех списках) только при необходимости, так что нет необходимости хранить их.

Проще говоря :

В первом подходе мы помещаем каждого нового кандидата в единую структуру данных, и это плохо, потому что слишком много вещей смешиваются неразумно.Эта плохая стратегия неизбежно влечет за собой сложность времени O (log (размер дерева)) каждый раз, когда мы делаем запрос к структуре.Однако, поместив их в отдельные очереди, вы увидите, что каждый запрос занимает только O (1), и поэтому общая производительность снижается до O (n) !!!Это связано с тем, что каждый из трех списков уже отсортирован.

6 голосов
/ 05 января 2011

Я полагаю, что вы можете решить эту проблему в сублинейное время, возможно O (n ^ {2/3}).

Чтобы дать вам представление, если вы упростите задачу, разрешив коэффициенты только 2 и 3, вы можете достичь O (n ^ {1/2}) времени, начиная с поиска наименьшей степени двух, по крайней мере до n-го уродливого числа, а затем генерирует список из O (n ^ {1/2}) кандидатов. Этот код должен дать вам представление, как это сделать. Он основан на том факте, что n-е число, содержащее только степени 2 и 3, имеет простую факторизацию, сумма показателей которой равна 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))]

Та же идея должна работать для трех допустимых факторов, но код становится более сложным. Сумма степеней факторизации падает до O (n ^ {1/3}), но вам нужно рассмотреть больше кандидатов, O (n ^ {2/3}), чтобы быть более точным.

4 голосов
/ 05 января 2011

В основном поиск может быть сделан O (n):

Учтите, что вы ведете частичную историю с некрасивыми числами. Теперь на каждом шаге вы должны найти следующий. Оно должно быть равно числу из истории, умноженному на 2, 3 или 5. Выберите наименьшее из них, добавьте его в историю и отбросьте из него некоторые числа, чтобы наименьшее из списка, умноженное на 5, было больше самый большой.

Это будет быстро, потому что поиск следующего номера будет простым:
мин (самый большой * 2, самый маленький * 5, один из середины * 3),
это больше, чем самое большое число в списке. Если их мало, список всегда будет содержать несколько чисел, поэтому поиск числа, которое нужно умножить на 3, будет быстрым.

2 голосов
/ 11 февраля 2019

Здесь много хороших ответов, но мне было трудно понять их, в частности, как любой из этих ответов, включая принятый, поддерживал аксиому 2 в оригинальной статье Дейкстры :

Аксиома 2. Если x находится в последовательности, то есть 2 * x, 3 * x и 5 * x.

После некоторой доски стало ясно, что аксиома 2 не инвариант на каждой итерации алгоритма, а фактически цель самого алгоритма.На каждой итерации мы пытаемся восстановить условие в аксиоме 2. Если last является последним значением в последовательности результатов S, аксиома 2 может быть просто перефразирована как:

Для некоторых x в S, следующее значение в S - это минимум 2x, 3x и 5x, который больше last.Давайте назовем эту аксиому 2 '.

Таким образом, если мы можем найти x, мы можем вычислить минимум 2x, 3x и 5x в постоянном времени и добавитьэто к S.

Но как нам найти x?Один подход, мы не делаем;вместо этого, всякий раз, когда мы добавляем новый элемент e в S, мы вычисляем 2e, 3e и 5e и добавляем их в очередь с минимальным приоритетом.Поскольку эта операция гарантирует, что e находится в S, простое извлечение верхнего элемента PQ удовлетворяет аксиоме 2 '.

Этот подход работает, но проблема в том, что мы генерируем группу чисел, которые мы не можемв конечном итоге с помощью.См. этот ответ для примера;если пользователь хочет 5-й элемент в S (5), PQ в этот момент удерживает 6 6 8 9 10 10 12 15 15 20 25.Разве мы не можем тратить это пространство впустую?

Оказывается, мы можем добиться большего.Вместо того, чтобы хранить все эти числа, мы просто поддерживаем три счетчика для каждого из кратных, а именно, 2i, 3j и 5k.Это кандидаты на следующий номер в S.Когда мы выбираем один из них, мы увеличиваем только соответствующий счетчик, а не два других.Поступая так, мы не с готовностью генерируем все кратные числа, таким образом решая проблему с пространством при первом подходе.

Давайте посмотрим пробный прогон для n = 8, то есть для числа 9.Мы начинаем с 1, как указано в аксиоме 1 в статье Дейкстры.

+---------+---+---+---+----+----+----+-------------------+
| #       | 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} |
+---------+---+---+---+----+----+----+-------------------+

Обратите внимание, что S не вырос на 6-й итерации, поскольку минимальный кандидат 6 уже был добавлен ранее.,Чтобы избежать этой проблемы запоминания всех предыдущих элементов, мы изменяем наш алгоритм, чтобы увеличивать все счетчики всякий раз, когда соответствующие мультипликаторы равны минимальному кандидату.Это подводит нас к следующей реализации Scala.

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)))
}
2 голосов
/ 31 августа 2015

Чтобы найти n-ое уродливое число в O (n ^ (2/3)), алгоритм jonderry будет работать просто отлично.Обратите внимание, что задействованные числа огромные , поэтому любой алгоритм, пытающийся проверить, является ли число некрасивым, не имеет шансов.

Поиск всех n наименьших уродливых чисел в порядке возрастания выполняется легко с использованием очереди приоритетов за время O (n log n) и пространство O (n): создайте очередь с приоритетами чисел с наименьшими числамиВо-первых, сначала включите только номер 1. Затем повторите n раз: Удалите наименьшее число x из очереди приоритетов.Если x не было удалено ранее, то x - это следующее большее уродливое число, и мы добавляем 2x, 3x и 5x в очередь с приоритетами.(Если кто-то не знает термин «очередь приоритетов», это похоже на кучу в алгоритме heapsort).Вот начало алгоритма:

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

Подтверждение времени выполнения: мы извлекаем уродливое число из очереди n раз.Изначально у нас есть один элемент в очереди, и после извлечения некрасивого числа мы добавляем три элемента, увеличивая их на 2. Таким образом, после того, как найдено не очень страшное число, у нас в очереди не более 2n + 1 элементов.Извлечение элемента может быть сделано в логарифмическом времени.Мы извлекаем больше чисел, чем только уродливые числа, но не более чем уродливые числа плюс 2n - 1 других чисел (тех, которые могли быть в сите после n-1 шагов).Таким образом, общее время составляет менее 3n удалений элементов в логарифмическом времени = O (n log n), а общее пространство составляет не более 2n + 1 элементов = O (n).

2 голосов
/ 05 января 2011

Вот правильное решение в ОД.Функция ugly () вернет поток (ленивый список) чисел Хэмминга.В этом потоке можно использовать функцию nth.

При этом используется метод Sieve, следующие элементы рассчитываются только при необходимости.

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())));

Это работа CS первого года: -)

1 голос
/ 10 сентября 2014

Полагаю, мы можем использовать Динамическое программирование (DP) и вычислять n-е Гадкое число . Полное объяснение можно найти на 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;
}

Мы видим, что это довольно быстро, просто измените значение MAX , чтобы вычислить большее Ugly Number

0 голосов
/ 11 августа 2017

вот мой код, идея состоит в том, чтобы разделить число на 2 (пока оно не даст остаток 0), затем на 3 и 5. Если наконец число становится единым, это уродливое число. Вы можете считать и даже печатать все уродливые числа до 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++;
    }

}
...