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

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

Пример:

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

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

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

Я написал тривиальную программу, которая вычисляет, является ли заданное число уродливым или нет. При n > 500 - он стал супер медленным. Я попытался использовать memoization - наблюдение: 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 - n-е уродливое число.

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

Ответ 1

Простое быстрое решение в Java. Использует подход, описанный Аноном..
Здесь 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[] на самом деле не является вариантом.

изменить
Как отдых от работы (отладка глупых спящих), здесь полностью линейное решение. Благодаря 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).

Ответ 2

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

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

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

Вы знакомы с алгоритмом Seive of Eratosthenes для поиска простых чисел? Что-то подобное (эксплуатация знаний о том, что каждое уродливое число в 2, 3 или 5 раз больше уродливого числа), вероятно, будет лучше работать для решения этой проблемы.

Ответ 3

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

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. Таким образом, каждый список будет сортироваться все время, а следующий наименьший кандидат должен всегда возглавлять один или несколько из этих списков.

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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 select(candidates, n - (t - candidates.length())) # linear time select

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

Ответ 6

Вот правильное решение в ML. Функция 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: -)

Ответ 7

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

Ответ 8

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

Ответ 9

вот мой код, идея состоит в том, чтобы разделить число на 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++;
    }

}

Ответ 10

Я пытаюсь решить это по-своему.

public class Ugly_numbers {
public static void main(String args[]){
    int count=1;
    Scanner in=new Scanner(System.in);
    System.out.println("Enter the turn whose ugly number you want to see");
    int n=in.nextInt();

    for(int i=2;i<=n;i++){
        if(i%2==0||i%3==0||i%5==0){
            count++;
            if((count)==n){
                System.out.println(i);
            }
        }

    }


}
}

Если n = 6, это хорошо. но если n = 7. это терпит неудачу. Кто-нибудь может предложить что-нибудь?

Ответ 11

Эта проблема может быть выполнена в O (1).

Если мы удалим 1 и посмотрим на цифры между 2 и 30, мы заметим, что есть 22 числа.

Теперь, для любого числа x в 22 числах выше, будет число x + 30 между 31 и 60, что также является уродливым. Таким образом, мы можем найти не менее 22 чисел между 31 и 60. Теперь для каждого уродливого числа между 31 и 60 мы можем записать его как s + 30. Таким образом, s также будет уродливым, так как s + 30 делится на 2, 3, или 5. Таким образом, будет ровно 22 числа между 31 и 60. Эта логика может быть повторена для каждого блока из 30 чисел после этого.

Таким образом, в первых 30 числах будет 23 номера и 22 за каждые 30 после этого. То есть, первые 23 уджли будут встречаться между 1 и 30, 45 ужимов будут возникать между 1 и 60, 67 уджлей будут происходить между 1 и 30 и т.д.

Теперь, если мне дают n, скажем 137, я вижу, что 137/22 = 6.22. Ответ будет лежать между 6 * 30 и 7 * 30 или между 180 и 210. К 180 году у меня будет 6 * 22 + 1 = 133-е уродливое число на 180. У меня будет 154-й уродливый номер в 210. Так что я ищу 4-е уродливое число (с 137 = 133 + 4) в интервале [2, 30], что равно 5. 137-е уродливое число тогда 180 + 5 = 185.

Другой пример: если я хочу 1500-е уродливое число, я считаю 1500/22 = 68 блоков. Таким образом, у меня будет 22 * ​​68 + 1 = 1497-й уродливый при 30 * 68 = 2040. Следующие три угара в блоке [2, 30] равны 2, 3 и 4. Таким образом, наш требуемый уродливый составляет 2040 + 4 = 2044.

То, что я могу просто построить список уродливых чисел между [2, 30] и просто найти ответ, выполнив поиск в O (1).

Ответ 12

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

  • список 1:1 * 2, 2 * 2, 3 * 2, 4 * 2, 5 * 2...;
  • список 2: 1 * 3, 2 * 3, 3 * 3, 4 * 3, 5 * 3...;
  • список 3: 1 * 5, 2 * 5, 3 * 5, 4 * 5, 5 * 5....

Итак, n-е уродливое число - это n-й номер списка, объединенного из трех перечисленных выше списков:

1, 1 * 2, 1 * 3, 2 * 2, 1 * 5, 2 * 3...

def nthuglynumber(n):
    p2, p3, p5=0,0,0
    uglynumber=[1]
    while len(uglynumber)<n:
        ugly2, ugly3, ugly5= uglynumber[p2]*2, uglynumber[p3]*3, uglynumber[p5]*5
        next=min(ugly2, ugly3, ugly5)
        if next==ugly2: p2+=1
        if next==ugly3: p3+=1
        if next==ugly5: p5+=1
        uglynumber+=[next]
    return uglynumber[-1]
  • ШАГ I: вычисление текущих уродливых номеров из трех списков
    • ugly2, ugly3, ugly5 = uglynumber [p2] * 2, uglynumber [p3] * 3, uglynumber [p5] * 5
  • ШАГ II, найдите следующее-большее уродливое число:
    • next = min (ugly2, ugly3, ugly5)
  • ШАГ III: перемещение указателя вперед, если его уродливое число является следующим большим числом
    • if next == ugly2: p2 + = 1
    • if next == ugly3: p3 + = 1
    • if next == ugly5: p5 + = 1
    • примечание здесь: не использовать if, elif и else
  • ШАГ IV: добавление следующего большего уродливого числа в объединенный список unglynumber
    • uglynumber + = [следующий]