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 + = [следующий]