Як знайти 5 повторених значень за O (n) час?


15

Припустимо, у вас є масив розміром що містить цілі числа від до , включно, рівно п’ять повторень. Мені потрібно запропонувати алгоритм, який може знайти повторні числа за час. Я не можу за все життя нічого думати. Я думаю, сортування в кращому випадку було б ? Тоді обхід масиву буде , в результаті чого . Однак я не дуже впевнений, чи потрібне буде сортування, оскільки я бачив складні речі із пов’язаним списком, чергами, стеками тощо.n61n5O(n)O(nlogn)O(n)O(n2logn)


16
O(nlogn)+O(n) не є . Це . Було б якби ви робили сортування n разів. O(n2logn)O(nlogn)O(n2logn)
Фонд позову Моніки


1
@leftaroundabout Ці алгоритми є де - розмір масиву, а - розмір вхідного набору. оскільки ці алгоритми працюють вn k k = n - c o n s t a n t O ( n 2 )O(kn)nkk=nconstantO(n2)
Роман Греф

4
@ RomanGräf видається, що фактична ситуація така: алгоритми працюють в , де - розмір домену. Отже, для такої проблеми, як ОП, зводиться до тієї самої, чи використовуєте ви такий алгоритм на розмірному домені, або традиційний алгоритм домену без обмеженого розміру. Має сенс теж. k n O ( n log n )O(logkn)knO(nlogn)
Проблизька

5
Для , єдине допустиме число - , за вашим описом. Але тоді доведеться повторити шість, а не п’ять разів. 1 1n=611
Алекс Рейнкінг

Відповіді:


22

Ви можете створити додатковий масив розміром . Спочатку встановіть усі елементи масиву на . Потім проведіть через вхідний масив і збільште на 1 для кожного . Після цього ви просто перевірте масив : цикл на і якщо то повторюється. Ви вирішуєте це в час за рахунок пам'яті, яка є і тому, що ваші цілі числа знаходяться між і .n 0 A B [ A [ i ] ] i B A B [ A [ i ] ] > 1 A [ i ] O ( n ) O ( n ) 1 n - 5Bn0AB[A[i]]iBAB[A[i]]>1A[i]O(n)O(n)1n5


26

Рішення у відповіді fade2black є стандартним, але воно використовує простір. Ви можете покращити це до простору наступним чином:O(n)O(1)

  1. Нехай масив буде . Для , обчисліть .d = 1 , , 5 σ d = n i = 1 A [ i ] dA[1],,A[n]d=1,,5σd=i=1nA[i]d
  2. Обчислити (можна використовувати відомі формули для обчислення останньої суми в ). Зауважте, що , де - повторювані числа. O ( 1 ) τ d = m d 1 + + m d 5 m 1 , , m 5τd=σdi=1n5idO(1)τd=m1d++m5dm1,,m5
  3. Обчисліть многочлен . Коефіцієнти цього многочлена є симетричними функціями які можна обчислити з в .m 1 , , m 5 τ 1 , , τ 5 O ( 1 )P(t)=(tm1)(tm5)m1,,m5τ1,,τ5O(1)
  4. Знайдіть усі корені многочлена , спробувавши всі можливостей.n - 5P(t)n5

Цей алгоритм передбачає машинну модель оперативної пам'яті, в якій основні арифметичні операції над -бітними словами приймають час .O ( 1 )O(logn)O(1)


Ще один спосіб сформулювати це рішення - наступні рядки:

  1. Обчисліть і виведіть використовуючи формулу .y 1 = m 1 + + m 5 y 1 = x 1 - n - 5 i = 1 ix1=i=1nA[i]y1=m1++m5y1=x1i=1n5i
  2. Обчисліть в використовуючи формулу O ( n ) x 2 = ( A [ 1 ] ) A [ 2 ] + ( A [ 1 ] + A [ 2 ] ) A [ 3 ] + ( A [ 1 ] + A [ 2x2=1i<jA[i]A[j]O(n)
    x2=(A[1])A[2]+(A[1]+A[2])A[3]+(A[1]+A[2]+A[3])A[4]++(A[1]++A[n1])A[n].
  3. Виведемо за допомогою формули y 2 = x 2 - 1 i < j n - 5 i j - ( n - 5 i = 1 i ) y 1 .y2=1i<j5mimj
    y2=x21i<jn5ij(i=1n5i)y1.
  4. Обчисліть і за аналогічними прямими.y 3 , y 4 , y 5x3,x4,x5y3,y4,y5
  5. Значення це (до знаку) коефіцієнти многочлена з попереднього рішення. P ( t )y1,,y5P(t)

Це рішення показує, що якщо замінити 5 на , то отримаємо (я вважаю) алгоритм , використовуючи простір , який виконує арифметичні операції на цілі числа бітової довжини , зберігаючи щонайбільше з них у будь-який момент часу. (Це вимагає ретельного аналізу множин, які ми виконуємо, більшість з яких включає лише один операнд довжиною . Можливо, що це можна вдосконалити до часу та простору використовуючи модульний арифметична.O ( d 2 n ) O ( d 2 ) O ( d n ) O ( d log n ) O ( d ) O ( log n ) O ( d n ) O ( d )dO(d2n)O(d2)O(dn)O(dlogn)O(d)O(logn)O(dn)O(d)


Будь-яке тлумачення і , , тощо? Чому ? τ d P ( t ) m i d { 1 , 2 , 3 , 4 , 5 }σdτdP(t)mid{1,2,3,4,5}
стиропор летить

3
Розуміння рішення - фокус підсумовування , який відображається у багатьох вправах (наприклад, як знайти відсутній елемент з масиву довжиною що містить усі, окрім числа ?). підсумок може бути використаний для обчислення для довільної функції , і питання полягає в тому, який вибрати, щоб можна було вивести . У моїй відповіді використовуються знайомі прийоми з елементарної теорії симетричних функцій. 1 , , n f ( m 1 ) + + f ( m 5 ) f f m 1 , , m 5n11,,nf(m1)++f(m5)ffm1,,m5
Yuval Filmus

1
@hoffmale Власне, . O(d2)
Yuval Filmus

1
@hoffmale Кожен з них приймає машинні слова. d
Yuval Filmus

1
@BurnsBA Проблема такого підходу полягає в тому, що набагато більше, ніж . Операції у великій кількості проходять повільніше. ( n - 4 ) ( n - 5 )(n5)#(n4)(n5)2
Юваль Фільм

8

Існує також алгоритм лінійного часу та постійного простору на основі розподілу, який може бути більш гнучким, якщо ви намагаєтесь застосувати це до варіантів проблеми, над якою не працює математичний підхід. Це вимагає мутування основного масиву і має гірші постійні фактори, ніж математичний підхід. Більш конкретно, я вважаю, що витрати з точки зору загальної кількості значень та кількості дублікатів є та відповідно, хоча це суворо доводить займати більше часу, ніж у мене на даний момент.d O ( n log d ) O ( d )ndO(nlogd)O(d)


Алгоритм

Почніть зі списку пар, де перша пара - це діапазон по всьому масиву, або якщо 1-індексований.[(1,n)]

Повторіть наступні дії, поки список не буде порожнім:

  1. Візьміть та видаліть зі списку будь-яку пару .(i,j)
  2. Знайдіть мінімальний та максимум та із позначеного підмасива.максminmax
  3. Якщо , підмножина складається лише з рівних елементів. Дайте його елементи, окрім одного, і пропустіть кроки 4 - 6.min=max
  4. Якщо , підмагістраль не містить дублікатів. Пропустіть кроки 5 і 6.maxmin=ji
  5. Розділіть підмасив навколо , таким чином, щоб елементи до деякого індексу були меншими за роздільник, а елементи над цим індексом не є. кmin+max2k
  6. Додайте та до списку.( k + 1 , j )(i,k)(k+1,j)

Короткий аналіз складності часу.

На кроках від 1 до 6 беруть час, оскільки пошук мінімуму та максимуму та розподіл можна зробити за лінійним часом.O(ji)

Кожна пара у списку є або першою парою , або дочірньою частиною пари, для якої відповідний підрядок містить повторюваний елемент. є таких батьків, оскільки кожен прохід діапазон, у якому може бути дублікат, тож є максимум якщо включати пари більше підматриці без дублікатів. У будь-який час розмір списку не більше .( 1 , n ) d log 2 n + 1 2 d log 2 n + 1 2 d(i,j)(1,n)dlog2n+12dlog2n+12d

Розгляньте роботу, щоб знайти якийсь один дублікат. Складається з послідовності пар за експоненціально зменшуваним діапазоном, тому загальна робота є сумою геометричної послідовності або . Це створює очевидний слід, що загальна робота для дублікатів повинна бути , лінійна в .d O ( n d ) nO(n)dO(nd)n

Щоб знайти більш чітку межу, розгляньте найгірший сценарій максимально розповсюдження дублікатів. Інтуїтивно, пошук займає дві фази - на одній, де кожен раз проходить повний масив, в прогресивно менших частинах, і на тій, де частини менші, ніж тому проходять лише частини масиву. Перша фаза може бути глибокою, тому вартість , а друга фаза коштує оскільки загальна площа, яку шукають, знову експоненціально зменшується . logdO(nlogd)O(n)ndlogdO(nlogd)O(n)


Дякую за пояснення. Тепер я розумію. Дуже гарний алгоритм!
DW

5

Залишаючи це як відповідь, оскільки йому потрібно більше місця, ніж дає коментар.

Ви помиляєтесь в ОП, коли пропонуєте метод. Сортування списку та перенесення його через час , а не час . Якщо ви робите дві речі (які приймають і відповідно) послідовно, то результуюча часова складність становить (за більшості обставин).O(nlogn)O(n2logn)O(f)O(g)O(f+g)=O(maxf,g)

Щоб помножити часові складності, вам потрібно використовувати цикл for. Якщо у вас є цикл довжиною і для кожного значення в циклі ви виконуєте функцію, яка займає , то ви отримаєте час .fO(g)O(fg)

Отже, у вашому випадку ви сортуєте а потім поперечно результаті чого . Якщо для кожного порівняння алгоритму сортування вам довелося зробити обчислення, яке займає , то воно займе але це не так.O(nlogn)O(n)O(nlogn+n)=O(nlogn)O(n)O(n2logn)


Якщо ви цікавитесь моїм твердженням, що , важливо зазначити, що це не завжди так. Але якщо або (що стосується цілого ряду загальних функцій), воно буде дотримуватися. Найпоширеніший час, який він не проводить, це коли втягуються додаткові параметри і ви отримуєте вирази на зразок .O(f+g)=O(maxf,g)fO(g)gO(f)O(2cn+nlogn)


3

Існує очевидний місцевий варіант техніки булевого масиву, що використовує порядок елементів як сховища (де arr[x] == xдля "знайдених" елементів). На відміну від варіанту розділів, який може бути виправданий тим, що він є більш загальним, я не впевнений, що вам насправді потрібно щось подібне, але це просто.

for idx from n-4 to n
    while arr[arr[idx]] != arr[idx]
        swap(arr[arr[idx]], arr[idx])

Це просто неодноразово розміщується arr[idx]в місці, arr[idx]поки ви не знайдете це місце вже прийняте, і тоді він повинен бути дублікатом. Зверніть увагу, що загальна кількість свопів обмежена оскільки кожен своп робить свою умову виходу правильним.n


Вам доведеться навести якийсь аргумент, що внутрішній whileцикл в середньому працює в постійний час. В іншому випадку це не алгоритм лінійного часу.
Девід Річербі

@DavidRicherby У середньому не працює постійний час, але зовнішній цикл працює лише 5 разів, тому це добре. Зауважте, що загальна кількість свопів обмежена оскільки кожен своп робить свою умову виходу правильним, тому навіть якщо кількість повторюваних значень збільшується, загальний час все ще лінійний (він же робить кроків, а не ). nnnd
Ведрак

На жаль, я якось не помітив, що зовнішня петля працює незмінна кількість разів! (Відредаговано так, щоб включити вашу замітку про кількість свопів, а також, щоб я міг змінити свою суботу.)
Девід Річербі

1

Відніміть значення, які ви маєте, від суми .i=1ni=(n1)n2

Отже, після часу (якщо арифметика є O (1), що насправді не є, але давайте робити вигляд, у вас є сума з 5 цілих чисел між 1 і n:Θ(n)σ1

x1+x2+x3+x4+x5=σ1

Нібито, це не корисно, правда? Ви не можете зрозуміти, як розбити це на 5 різних чисел.

Ах, але це тут стає весело! Тепер зробіть те саме, що і раніше, але відніміть квадрати значень із . Тепер у вас є:i=1ni2

x12+x22+x32+x42+x52=σ2

Бачите, куди я з цим їду? Зробіть те ж саме для степенів 3, 4 і 5, і у вас є 5 незалежних рівнянь у 5 змінних. Я впевнений, що ви можете вирішити для .x

Застереження: Арифметика НЕ на насправді O (1). Крім того, вам потрібно трохи місця, щоб представити свої суми; але не настільки, як ви могли б собі уявити - ви можете робити більшість всього модульно, доки у вас є, о, біти; що має це зробити.log(5n6)


Чи не пропонує @YuvalFilmus те саме рішення?
fade2black

@ fade2black: О, так, це, вибачте, я щойно побачив перший рядок його рішення.
einpoklum

0

Найпростіший спосіб вирішити проблему - створити масив, в якому ми будемо рахувати видимість для кожного числа в початковому масиві, а потім проходити все число від до і перевіряти, чи з’являється число більше одного разу, складність для цього рішення як в пам'яті, так і в часі лінійне, або1n5O(N)


1
Це та сама відповідь @ fade2black (хоча трохи легше на очі)
LangeHaare

0

Зіставте масив на, 1 << A[i]а потім XOR все разом. Вашими дублікатами будуть цифри, де відповідний біт вимкнено.


Існує п’ять дублікатів, тому трюк xor у деяких випадках не порушиться.
Зло

1
Час роботи цього - . Кожен бітвектор є бітами довгим, тому кожна операція бітвектора займає час, і ви робите одну бітну операцію вектора на елемент вихідного масиву, загалом . O(n2)nO(n)O(n2)
DW

@DW Але якщо врахувати, що машини, якими ми зазвичай користуємося, є фіксованими або 32, або 64-бітними, і вони не змінюються під час роботи (тобто вони постійні), чому їх не слід розглядати як такі і припускати, що бітові операції є в замість ? O(1)O(n)
code_dredd

1
@ray, я думаю, ти відповів на власне запитання. Зважаючи на те, що машини, якими ми зазвичай користуємося, є фіксованими на 64 бітах, час виконання операції на бітовому векторі дорівнює , а не . Він приймає що - щось на зразок інструкції , щоб зробити деякі операції на все біти бітового вектора і є , а НЕ . nO(n)O(1)n/64nnn/64O(n)O(1)
DW

@DW Що я вийшов з prev. коментарів було те, що бітовий вектор посилався на один елемент у розмірному масиві, при цьому бітовий вектор був 64-бітним, що було б константою, про яку я маю на увазі. Очевидно, що обробка масиву розміром займе час , якщо припустити, що в елементі є біт на елемент і кількість елементів. Але , тому операція для елемента масиву w / постійний розряд бітів повинна бути замість а для масиву замість . Ви тримаєтеn O ( k n ) k n k = 64 O ( 1 ) O ( k ) O ( n ) O ( k n ) knnO(kn)knk=64O(1)O(k)O(n)O(kn)kзаради повноти / коректності чи я пропускаю щось інше?
code_dredd

-2
DATA=[1,2,2,2,2,2]

from collections import defaultdict

collated=defaultdict(list):
for item in DATA:
    collated[item].append(item)
    if len(collated) == 5:
        return item.

# n time

4
Ласкаво просимо на сайт. Ми - сайт з інформатики , тому ми шукаємо алгоритми та пояснення, а не скидки коду, які потребують розуміння певної мови та її бібліотек. Зокрема, ваше твердження, що цей код працює в лінійному часі, передбачає, що він collated[item].append(item)працює в постійному часі. Це справді правда?
Девід Річербі

3
Крім того, ви шукаєте значення, яке повторюється п'ять разів. На відміну від цього, ОП шукає п'ять значень, які повторюються двічі.
Yuval Filmus
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.