Алгоритм O (nlogn) - Знайдіть три рівномірно розташовані в дворядному рядку


173

У мене було це питання на тесті з алгоритмів вчора, і я не можу зрозуміти відповіді. Це зводить мене абсолютно з розуму, бо воно коштувало близько 40 балів. Я вважаю, що більшість класів не вирішили це правильно, тому що я не придумав рішення за останні 24 години.

Враховуючи довільну двійкову рядок довжиною n, знайдіть три рівномірно розташовані всередині рядка, якщо вони існують. Напишіть алгоритм, який вирішує це за час O (n * log (n)).

Отже, такі струни мають три, які "рівномірно розподілені": 11100000, 0100100100

редагувати: Це випадкове число, тому воно повинне мати можливість працювати для будь-якого числа. Я наводив приклади, щоб проілюструвати властивість "рівномірно розташованих". Отже 1001011 - дійсне число. З 1, 4 і 7 - це ті, що розташовані рівномірно.


4
Чи можливо таке: 10011010000? У нього три 1s (перша, друга, четверта), рівномірно розташовані, але є й додаткові 1s.
Анна

5
Роберте, вам потрібно змусити свого професора дати відповідь на це і опублікувати його тут. Ця проблема підводить мене до стіни. Я можу зрозуміти, як це зробити в n ^ 2, але не n * log (n).
Джеймс Макмахон

3
Гм, я багато часу намагався розібратися, але ще не натрапив на хорошу відповідь. Можливо, ви неправильно зрозуміли питання? Наприклад, якщо запитання задається, знайдіть алгоритм, який працює в O (n log n), який визначає положення рівномірно розташованої послідовності проміжків k, у значно більшій послідовності, це можна було б легко зробити за допомогою швидкого перетворення фур'є.
ldog

2
якщо ваш професор дає рішення, будь ласка, опублікуйте це як відповідь.
ldog

5
Враховуючи той факт, що Клаус Рот отримав 1958 р. Медаль Поля за (серед іншого), що підтверджує, що для кожної щільності d> 0 існує натуральне число N таке, що кожне підмножина {1, ..., N} принаймні d * N елементів містить арифметичну прогресію довжиною 3, я не дивуюсь, що до цих пір ніхто не знайшов переконливого алгоритму для проблеми. Дивіться також en.wikipedia.org/wiki/Szemer%C3%A9di%27s_theorem
jp

Відповіді:


128

Нарешті! Наступні результати у відповіді sdcvvc , ми маємо це: алгоритм O (n log n) для проблеми! Це теж просто, коли ви це зрозумієте. Ті, хто здогадався про FFT, мали рацію.

Проблема: нам дають двійковий рядок Sдовжиною n , і ми хочемо знайти в ній три рівномірно розташовані 1. Наприклад, Sможе бути 110110010, де n = 9. Він рівномірно розташований на 1s у позиціях 2, 5 та 8.

  1. Скануйте Sзліва направо та складіть список Lпозицій 1. Для S=110110010вищезазначеного маємо список L = [1, 2, 4, 5, 8]. Цей крок - O (n). Проблема в даний час , щоб знайти арифметичну прогресію довжини 3 в L, тобто знайти виразне A, B, C , в Lтакий , що ба = СВ , або , що еквівалентно А + С = 2b . На прикладі вище ми хочемо знайти прогресію (2, 5, 8).

  2. Складіть многочлен p із доданками x k для кожного k in L. Для наведеного вище прикладу робимо многочлен p (x) = (x + x 2 + x 4 + x 5 + x 8 ) . Цей крок - O (n).

  3. Знайдіть многочлен q= p 2 , використовуючи Швидке перетворення Фур'є . Для наведеного вище прикладу ми отримуємо многочлен q (x) = x 16 + 2x 13 + 2x 12 + 3x 10 + 4x 9 + x 8 + 2x 7 + 4x 6 + 2x 5 + x 4 + 2x 3 + x 2 . Цей крок - O (n log n).

  4. Ігноруйте всі терміни, крім тих, що відповідають x 2k для деякого k in L. Для наведеного вище прикладу ми отримуємо терміни x 16 , 3x 10 , x 8 , x 4 , x 2 . Цей крок - O (n), якщо ви вирішите це зробити взагалі.

Ось найважливіший момент: коефіцієнт будь-якого x 2b для b in L- це саме кількість пар (a, c) в Lтаких, що a + c = 2b . [CLRS, Вих. 30.1-7] Одна така пара є (b, b) завжди (тому коефіцієнт принаймні 1), але якщо існує будь-яка інша пара (a, c) , то коефіцієнт принаймні 3, з (a, c ) і (с, а) . Для наведеного вище прикладу маємо коефіцієнт x 10, який дорівнює 3, саме через AP (2,5,8). (Ці коефіцієнти x 2bзавжди будуть непарні числа з причин, зазначених вище. А всі інші коефіцієнти в q завжди будуть парними.)

Отже, алгоритм повинен подивитися на коефіцієнти цих доданків x 2b і побачити, чи будь-який з них більший за 1. Якщо його немає, то немає рівномірно розташованих 1с. Якщо це б в , Lдля яких коефіцієнт х більше 1, то ми знаємо , що є деяка пара (а, с) - крім (Ь, Ь) - для яких а + с = 2b . Щоб знайти дійсну пару, ми просто спробуємо кожну a в L(відповідна c була б 2b-a ) і побачимо, чи є 1 у положенні 2b-a in S. Цей крок - O (n).

Це все, шановні.


Можна запитати: чи потрібно використовувати FFT? Багато відповідей, такі як бета-версія , flybywire та rsp , припускають, що підхід, який перевіряє кожну пару 1s і бачить, чи є 1 на "третій" позиції, може працювати в O (n log n), виходячи з інтуїції що якщо занадто багато 1, ми знайдемо трійку легко, а якщо 1 занадто мало, перевірка всіх пар займає мало часу. На жаль, в той час як ця інтуїція є правильною і простим підходом є краще , ніж O (N 2 ), не набагато краще. Як і у відповіді sdcvvc , ми можемо взяти "канторовскій набір" рядків довжиною n = 3 k, з позиціями 1s на позиціях, потрійне представництво яких містить лише 0s та 2s (no 1s). Такий рядок має 2 k = n (log 2) / (log 3) ≈ n 0,63 в ньому і не має рівномірно розташованих 1s, тож перевірка всіх пар була б порядком квадрата числа 1s у ньому: це 4 k ≈ n 1,26, що, на жаль, асимптотично набагато більше, ніж (n log n). Насправді, найгірший випадок ще гірший: Лео Мозер у 1953 році сконструював (ефективно) такі рядки, які мають n 1-c / √ (log n) 1s, але не рівномірно розташовані 1s, що означає, що на таких рядках просте підхід зайняв би Θ (n 2-2c / √ (log n) )- тільки крихітний кращий за Θ (n 2 ) , на диво!


Про максимальну кількість 1s у рядку довжиною n з не 3 рівномірно розташованими (що ми бачили вище було щонайменше n 0,63 від легкої кантороподібної конструкції та принаймні n 1-c / √ (log n) з Конструкція Мозера) - це OEIS A003002 . Він також може бути обчислений безпосередньо з OEIS A065825 як k такий, що A065825 (k) ≤ n <A065825 (k + 1). Я написав програму, щоб знайти їх, і виявляється, що жадібний алгоритм не дає найдовшого такого рядка. Наприклад, при n = 9 ми можемо отримати 5 1s (110100011), але жадібний дає лише 4 (110110000), для n = 26 ми можемо отримати 11 1s (11001010001000010110001101), але жадібний дає лише 8 (11011000011011000000000000), а для n= 74 ми можемо отримати 22 1s (11000010110001000001011010001000000000000000010001011010000010001101000011), але жадібний дає лише 16 (1101100001101100000000000001101100001101100000000000000000000000000000000000000000000). Однак вони погоджуються в небагатьох місцях до 50 (наприклад, від 38 до 50). Як говориться в посиланнях OEIS, схоже, що Ярослав Врублевський зацікавлений у цьому питанні, і він підтримує веб-сайт щодо цих нестандартних наборів . Точні цифри відомі лише до 194.


27
Дуже хороша. Вражає. Здається, трохи очікувати, що хтось придумає це в тесті.
hughdbrown

4
Ну, крок 1, перекладаючи проблему на пошук точки доступу, є простим. Крок 3, що поліноми можна помножити за час O (n log n), є лише фактом. Справжня хитрість, і що ускладнює проблему, - це ідея мислити 11011 як поліном з коефіцієнтами [1,1,0,1,1] і т. Д. Це розумна і часто корисна ідея, яка відповідає всім назад до Ейлера. [Дивіться дивовижну книгу Вільфа "генеруюча функціоналогія" для сучасної експозиції: math.upenn.edu/~wilf/DownldGF.html ] Отже, це залежить від того, студенти були піддані генерації функцій в останній пам'яті чи ні. :-)
ShreevatsaR

2
Вибачте, мій розрахунок був абсолютно неправильним Це повинно бути 110110010 ^ 2 = 12124214302200100. Але ідея стоїть. Просто зауважте позицію 3-го
Гільєрмо Філіпс

11
Дуже вражає. Дуже здорово бачити, як ця тема / питання збираються разом і знаходять рішення. Я починав думати, що це неможливо. Також цей професор - злий.
KingNestor

1
@RexE: Якщо р є ступенем n-1 (має n термінів), q = p ^ 2 є ступенем 2n-2 (має щонайбільше 2n-1 доданків). Як у вас вийшло n ^ 2? (Також множення двох поліномів на ступінь n на O (n log n) часу за допомогою FFT - це досить стандартна операція; будь ласка, натисніть на посилання у відповіді або дивіться статтю у Вікіпедії .)
ShreevatsaR

35

У цій роботі (1999) ваша проблема називається AVERAGE :

Проблема 3SUM-жорстка, якщо є субдеквадратичне зменшення від задачі 3SUM: Враховуючи набір A з n цілих чисел, чи є елементи A, b, c в A такі, що a + b + c = 0? Невідомо, чи AVERAGE твердий 3SUM. Однак існує просте лінійне скорочення часу від СРЕДНОГО до 3SUM, опис якого ми опускаємо.

Вікіпедія :

Коли цілі числа знаходяться в діапазоні [−u ... u], 3SUM можна вирішити за час O (n + u lg u), представляючи S як бітовий вектор і виконуючи згортку за допомогою FFT.

Цього достатньо, щоб вирішити вашу проблему :).

Що дуже важливо, це те, що O (n log n) - це складність за кількістю нулів та одиниць, а не за кількістю одиниць (яку можна задати як масив, як [1,5,9,15]). Перевірити, чи має набір арифметична прогресія, кількість номерів 1, важко, і згідно з цим документом станом на 1999 рік не відомий швидший алгоритм, ніж O (n 2 ), і передбачається, що його не існує. Усі, хто цього не враховує, намагаються вирішити відкриту проблему.

Інша цікава інформація, в основному непосильна:

Нижня межа:

Легкою нижньою межею є набір, що нагадує Кантор (числа 1..3 ^ n-1, що не містять 1 у їх потрійному розширенні) - його щільність n ^ (log_3 2) (приблизно 0.631). Тож будь-яка перевірка, якщо набір не надто велика, а потім перевірка всіх пар недостатня для отримання O (n log n). Ви повинні вивчити послідовність розумнішою. Більш нижня межа вказана тут - це п 1-с / (журнал (п)) ^ (1/2) . Це означає, що набір Кантора не є оптимальним.

Верхня межа - мій старий алгоритм:

Відомо, що для великих n підмножина {1,2, ..., n}, що не містить арифметичної прогресії, має максимум n / (log n) ^ (1/20) елементів. Стаття про трійки в арифметичній прогресії доводить більше: множина не може містити більше n * 2 28 * (журнал журналу n / log n) 1/2 елементів. Таким чином, ви можете перевірити, чи досягнуто це обмеження, а якщо ні, наївно перевірити пари. Це алгоритм O (n 2 * log log n / log n), швидший, ніж O (n 2 ). На жаль, "Про трійку ..." є на Спрингера - але перша сторінка доступна, а виклад Бен Гріна доступний тут , сторінка 28, теорема 24.

До речі, документи - з 1999 року - того ж року, що і перший, про який я згадав, тому, мабуть, тому перший не згадує цього результату.


2
Чудова відповідь, перша, яка говорить щось остаточне про цю проблему. Отже, набір, схожий на Кантора, має n ^ 0,63 1s, а це означає, що алгоритм «перевірити всі пари 1s» принаймні n ^ 1,26 (≫ n log n) у гіршому випадку. Нижня межа, цитується у статті Семереді (BTW, папір Мозера, яку він цитує, доступний тут: books.google.com/books?id=Cvtwu5vVZF4C&pg=PA245 ), здається, насправді має на увазі n ^ (2-o (1)), але ми повинні будьте обережні, тому що там у нас є числа, проведені з {1, ..., n}, але тут це сума чисел у послідовності, яка дорівнює n.
ShreevatsaR

Е-е, що саме є "двосторонньою" послідовністю "Кантор", яка містить n ^ (log_3 2) 1s в ній і немає трьох рівномірно розташованих 1s?
ShreevatsaR

Приклад: 101000101000000000101000101. Його довжина 3 ^ n, має 2 ^ n одиниць (так n ^ 0.63 щільність). Якщо записати місця 1 у двійковій, це буде {0,2,20,22,200,202,220,222}. Інший можливий спосіб подумати про це - взяти послідовність з них і постійно видаляти "середні", як у звичайній конструкції набору Cantor: 111111111 -> 111000111 -> 101000101. Причиною, чому вона не містить арифметичної прогресії, є: якщо x , y, z утворили одне, тоді y = (x + z) / 2 і x і z відрізняються на деякому місці розширення. Візьміть найзначніший. Скажіть, x має 0, а z має 2. Тоді y має бути 1. протиріччя.
sdcvvc

3
Знову ж таки, чудові дослідження! Я продовжив роботу над документом 3SUM 2008 року, і він посилався на вправу CLRS. 30.1-7, подивившись на яку я отримав відповідь - алгоритм O (n log n) насправді досить простий! (Просто промальовуючи поліном / генеруючу функцію.) Відповідь я розмістив нижче. (Тепер брикаю себе за те, що раніше не думав про це ... прості рішення завжди викликають таку реакцію: p)
ShreevatsaR

Отже, відповідь на його екзаменаційне запитання виглядав приблизно так: "Ця проблема зводиться до важкої проблеми 3-SUM, а 3-SUM hard не має підквадратичного рішення, тому цю проблему неможливо вирішити в O (n logn). " Так?
hughdbrown

8

Це не рішення, а подібний напрямок думки до того, що думав Олександр

Я бавився, створюючи послідовності з максимальною кількістю одиниць, і всі вони досить цікаві, я отримав до 125 цифр, і ось перші 3 номери, які було знайдено, намагаючись вставити якомога більше бітів "1":

  • 11011000011011000000000000001101100001101100000000000000000000000000000000000000000110110000110110000000000000011011000011011
  • 101101000101101000000000000101101000101101000000000000000000000000000000000000000000000101101000101101000000000000101101000101101
  • 100110010100110010000000000100110010100110010000000000000000000000000000000000000000010011001010011001000000000010011001010011001

Зауважте, що всі вони є фракталами (не надто дивно, враховуючи обмеження). Може бути щось у мисленні назад, можливо, якщо струна не є фракталом з характеристикою, то вона повинна мати повторюваний малюнок?

Завдяки бета-версії за кращий термін для опису цих цифр.

Оновлення: На жаль, схоже, що візерунок руйнується, починаючи з достатньо великої початкової рядки, наприклад: 10000000000001:

100000000000011
10000000000001101
100000000000011011
10000000000001101100001
100000000000011011000011
10000000000001101100001101
100000000000011011000011010000000001
100000000000011011000011010000000001001
1000000000000110110000110100000000010011
1000000000000110110000110100000000010011001
10000000000001101100001101000000000100110010000000001
10000000000001101100001101000000000100110010000000001000001
1000000000000110110000110100000000010011001000000000100000100000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011
1000000000000110110000110100000000010011001000000000100000100000000000001101
100000000000011011000011010000000001001100100000000010000010000000000000110100001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001000001
100000000000011011000011010000000001001100100000000010000010000000000000110100001001000001000000110001000000001000000000000000000000000000000000000000010000001000000000000001100000000110010000000010010000000000001000000001000010000010010001001000001000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000011000001000000000000000000000100100000000000000000000000000000000000011001000000000000000000000010010000010000001
1000000000000110110000110100000000010011001000000000100000100000000000001101000010010000010000001100010000000010000000000000000000000000000000000000000100000010000000000000011000000001100100000000100100000000000010000000010000100000100100010010000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000110000010000000000000000000001001000000000000000000000000000000000000110010000000000000000000000100100000100000011
10000000000001101100001101000000000100110010000000001000001000000000000011010000100100000100000011000100000000100000000000000000000000000000000000000001000000100000000000000110000000011001000000001001000000000000100000000100001000001001000100100000100000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001100000100000000000000000000010010000000000000000000000000000000000001100100000000000000000000001001000001000000110000000000001

2
Святий * @ !!, це ФРАКТАЛИ! Якщо це утримується, він ставить верхню межу на число 1, і це менше, ніж O (n).
Бета

фрактали, це набагато кращий термін для їх опису. Спасибі
z -

Цікаво, що ці зразки дуже нагадують потрійний набір Кантора ( en.wikipedia.org/wiki/Cantor_set ). Якщо це так, то частка їх повинна прагнути до нуля ...
flybywire

Чи очевидно, що послідовності з максимальною кількістю 1s без трійки мають безпосереднє відношення до найгіршого часу виконання алгоритму? Цілком можливо, що у вас можуть бути рядки з великою кількістю 1, але в яких ви знайдете трійки лише дуже пізно, оскільки ці 1-ті знаходяться в положеннях, які запізнаються за алгоритмом.
ShreevatsaR

3
Мій аналіз кількості рядків у рядках порівняно з їх загальним розміром, схоже, свідчить про наявність лінійної залежності між кількістю одиниць та розміром рядка, що приводить мене до думки, що немає щасливої ​​верхньої межі, яка дозволяє нам сказати, що кількість рядків у рядку буде максимум log (n) для даного рядка. Тож рішення, що стосуються лише позицій тих, а не всієї рядки, теж будуть O (n ^ 2). Або, точніше, O (n + m ^ 2), де m - кількість одиниць у рядку, а n - розмір рядка, а m - велика-тета (n).
Вельбог

6

Я підозрюю, що простий підхід, схожий на O (n ^ 2), насправді дасть щось краще, як O (n ln (n)). Послідовності, які займають найдовше тестування (для будь-якого n), - це ті, які не містять тріо, і це встановлює суворі обмеження на число 1, яке може бути в послідовності.

Я придумав кілька аргументів, що махають руками, але я не зміг знайти охайний доказ. Я збираюся взяти удар в темряві: відповідь - це дуже розумна ідея, яку професор знає так довго, що це здається очевидним, але студентам це занадто важко. (Або ти, або ти проспав лекцію, яка її висвітлювала.)


2
хаха, ні, я не спав жодної лекції. Я спілкувався з кількома іншими студентами, і ніхто не мав чіткого уявлення про те, як це вирішити. Більшість написали деякі БС про поділ і перемогу, щоб отримати частковий кредит.
Роберт Паркер

3

Редакція: 2009-10-17 23:00

Я запускаю це у великій кількості (наприклад, рядки в 20 мільйонів), і тепер я вважаю, що цей алгоритм не є O (n logn). Незважаючи на це, це досить класна реалізація і містить ряд оптимізацій, завдяки чому вона працює дуже швидко. Він оцінює всі розташування двійкових рядків 24 або менше цифр за 25 секунд.

Я оновив код, щоб включити 0 <= L < M < U <= X-1спостереження від більш раннього сьогодні.


Оригінал

Це в принципі схоже на інше питання, на яке я відповів . Цей код також розглядав три значення в серії і визначав, чи відповідає триплет умові. Ось код C #, адаптований до цього:

using System;
using System.Collections.Generic;

namespace StackOverflow1560523
{
    class Program
    {
        public struct Pair<T>
        {
            public T Low, High;
        }
        static bool FindCandidate(int candidate, 
            List<int> arr, 
            List<int> pool, 
            Pair<int> pair, 
            ref int iterations)
        {
            int lower = pair.Low, upper = pair.High;
            while ((lower >= 0) && (upper < pool.Count))
            {
                int lowRange = candidate - arr[pool[lower]];
                int highRange = arr[pool[upper]] - candidate;
                iterations++;
                if (lowRange < highRange)
                    lower -= 1;
                else if (lowRange > highRange)
                    upper += 1;
                else
                    return true;
            }
            return false;
        }
        static List<int> BuildOnesArray(string s)
        {
            List<int> arr = new List<int>();
            for (int i = 0; i < s.Length; i++)
                if (s[i] == '1')
                    arr.Add(i);
            return arr;
        }
        static void BuildIndexes(List<int> arr, 
            ref List<int> even, ref List<int> odd, 
            ref List<Pair<int>> evenIndex, ref List<Pair<int>> oddIndex)
        {
            for (int i = 0; i < arr.Count; i++)
            {
                bool isEven = (arr[i] & 1) == 0;
                if (isEven)
                {
                    evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count+1});
                    oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count});
                    even.Add(i);
                }
                else
                {
                    oddIndex.Add(new Pair<int> {Low=odd.Count-1, High=odd.Count+1});
                    evenIndex.Add(new Pair<int> {Low=even.Count-1, High=even.Count});
                    odd.Add(i);
                }
            }
        }

        static int FindSpacedOnes(string s)
        {
            // List of indexes of 1s in the string
            List<int> arr = BuildOnesArray(s);
            //if (s.Length < 3)
            //    return 0;

            //  List of indexes to odd indexes in arr
            List<int> odd = new List<int>(), even = new List<int>();

            //  evenIndex has indexes into arr to bracket even numbers
            //  oddIndex has indexes into arr to bracket odd numbers
            List<Pair<int>> evenIndex = new List<Pair<int>>(), 
                oddIndex = new List<Pair<int>>(); 
            BuildIndexes(arr, 
                ref even, ref odd, 
                ref evenIndex, ref oddIndex);

            int iterations = 0;
            for (int i = 1; i < arr.Count-1; i++)
            {
                int target = arr[i];
                bool found = FindCandidate(target, arr, odd, oddIndex[i], ref iterations) || 
                    FindCandidate(target, arr, even, evenIndex[i], ref iterations);
                if (found)
                    return iterations;
            }
            return iterations;
        }
        static IEnumerable<string> PowerSet(int n)
        {
            for (long i = (1L << (n-1)); i < (1L << n); i++)
            {
                yield return Convert.ToString(i, 2).PadLeft(n, '0');
            }
        }
        static void Main(string[] args)
        {
            for (int i = 5; i < 64; i++)
            {
                int c = 0;
                string hardest_string = "";
                foreach (string s in PowerSet(i))
                {
                    int cost = find_spaced_ones(s);
                    if (cost > c)
                    {
                        hardest_string = s;
                        c = cost;
                        Console.Write("{0} {1} {2}\r", i, c, hardest_string);
                    }
                }
                Console.WriteLine("{0} {1} {2}", i, c, hardest_string);
            }
        }
    }
}

Основні відмінності:

  1. Вичерпний пошук рішень
    Цей код формує набір даних про потужність, щоб знайти найскладніший вхід для вирішення цього алгоритму.
  2. Усі рішення проти найскладнішого для вирішення
    Код попереднього питання генерував усі рішення за допомогою генератора пітонів. Цей код відображає найскладніші для кожної довжини шаблону.
  3. Алгоритм підрахунку балів
    Цей код перевіряє відстань від середнього елемента до його лівого та правого краю. Код пітона перевіряв, чи сума була вище 0 або нижче.
  4. Конвергенція щодо кандидата
    Поточний код працює від середини до краю, щоб знайти кандидата. Код у попередній проблемі працював від країв до середини. Остання зміна покращує ефективність роботи.
  5. Використання парних і непарних пулів
    На основі спостережень в кінці цього запису код шукає парні парні числа парних непарних чисел, щоб знайти L і U, зберігаючи M фіксованими. Це зменшує кількість пошукових запитів за допомогою попереднього обчислення інформації. Відповідно, код використовує два рівні непрямості в основному циклі FindCandidate і вимагає двох викликів FindCandidate для кожного середнього елемента: один раз для парних чисел і один раз для непарних.

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

Результати застаріли: видалено.


Редагувати: 16.10.2009 18.48

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

Я хотів би зазначити, що ці дані не є найскладнішим для мого алгоритму, тому я вважаю, що припущення про те, що фрактали yx найважче вирішити, є помилковим. Найгірший випадок для конкретного алгоритму, я думаю, буде залежати від самого алгоритму і, швидше за все, не буде узгодженим для різних алгоритмів.


Редагувати: 2009-10-17 13:30

Подальші спостереження з цього приводу.

Спочатку перетворіть рядок 0 і 1 у масив індексів для кожної позиції 1. Скажіть, довжина цього масиву A дорівнює X. Тоді мета - знайти

0 <= L < M < U <= X-1

такий, що

A[M] - A[L] = A[U] - A[M]

або

2*A[M] = A[L] + A[U]

Оскільки A [L] і A [U] складають парне число, вони не можуть бути (непарні, непарні) або (непарні, парні). Пошук відповідності може бути покращений шляхом поділу A [] на непарні і парні пули та пошуку матчів на A [M] в пулах непарних і навіть парних кандидатів.

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


Редагувати 2009-10-18 00:45

Але в мене відбувається ще одна оптимізація, подібно до поділу кандидатів на парні та непарні. Оскільки три індекси мають додавати кратне 3 (a, a + x, a + 2x - mod 3 дорівнює 0, незалежно від a і x), ви можете розділити L, M і U на їх значення 3 mod 3 :

M  L  U
0  0  0
   1  2
   2  1
1  0  2
   1  1
   2  0
2  0  1
   1  0
   2  2

Насправді, ви можете поєднати це з парним / непарним спостереженням і розділити їх на свої значення 6 mod:

M  L  U
0  0  0
   1  5
   2  4
   3  3
   4  2
   5  1

і так далі. Це забезпечить подальшу оптимізацію продуктивності, але не алгоритмічне прискорення.


2

Ще не змогли придумати рішення :(, але є кілька ідей.

Що робити, якщо ми почнемо із зворотної задачі: побудуємо послідовність із максимальною кількістю 1s та БЕЗ будь-яких рівномірно розташованих тріо. Якщо ви можете довести, що максимальна кількість 1s є o (n), ви можете покращити свою оцінку, повторивши лише через список 1s.


Ну, число 1-х, безумовно, обмежене вище O (n). Це не може бути O (n ** 2), правда - число 1 зростає швидше, ніж дані? Важливе питання - чи нижня верхня межа від цього.
hughdbrown

Я використав малий o, не великий
Olexiy

2

Це може допомогти ....

Ця проблема зводиться до наступного:

З огляду на послідовність натуральних чисел, знайдіть суміжну підрядність, розділену на префікс та суфікс, таким чином, що сума префікса піддослідності дорівнює сумі суфікса підрядності.

Наприклад, з огляду на послідовність [ 3, 5, 1, 3, 6, 5, 2, 2, 3, 5, 6, 4 ], ми знаходимо підпослідовність [ 3, 6, 5, 2, 2]з префіксом [ 3, 6 ]з префіксом суми 9і суфікса [ 5, 2, 2 ]з суфіксом суми 9.

Скорочення полягає в наступному:

Враховуючи послідовність нулів і одиниць, починаючи з крайньої лівої, продовжуйте рух вправо. Щоразу, коли зустрічається інша, запишіть кількість рухів з моменту попереднього і додайте це число до отриманої послідовності.

Наприклад, з огляду на послідовність [ 0, 1, 1, 0, 0, 1, 0, 0, 0, 1 0 ], ми знайдемо скорочення [ 1, 3, 4]. З цього скорочення ми обчислюємо суміжну підрядність [ 1, 3, 4], префікс [ 1, 3]з сумою 4та суфікс [ 4 ]з сумою 4.

Це зменшення може бути обчислено в O(n).

На жаль, я не впевнений, куди звідси піти.


1
Це більш компактне позначення, але це не допоможе складності у часі. Набір розділів "префікса" є ізоморфним для пошуку всіх пар у всіх випадках "1", що є O (n ^ 2).
p00ya

Очевидно, що там існують алгоритми, що стосуються суміжних сум сукупності. На жаль, всі вони, мабуть, мають справу з знаходженням суміжної підрядності з максимальною сумою в O (n).
yfeldblum

@ p00ya це невірно. Використовуючи цей алгоритм, тривалість часу залежить від верхньої межі числа помилкових, яка за допомогою Assupton на рядку, що генерується Cantor, є ((3/2) ^ (log (n) / log (3))), і складність простору стає такою, але складність у часі набуває цього множення на n. Перевірте мою другу відповідь. (не мінус): D
Лука Ране

@ralu: це, за вашим припущенням, що рядки, породжені Кантором, є найгіршим випадком, а це неправильно. Для запису кількість пар, безумовно, O (n ^ 2); але я думаю, я насправді мав на увазі, що це була велика Омега (n ^ 2), що неправильно, враховуючи ці результати (див. посилання NrootN, зокрема), пропонуючи нижню межу в парах великої Омеги (n ^ (2 / 1.52 )) доказом або великим Омега (n ^ (4/3)) здогадками.
p00ya

1

Для простого типу проблеми (тобто ви шукаєте три "1" з лише (тобто нулем або більше) "0" між ним), його досить просто: Ви можете просто розділити послідовність на кожен "1" і шукати два суміжні підрядки, що мають однакова довжина (звичайно, друга послідовність не є останньою). Очевидно, це можна зробити за O (n) час.

Для більш складної версії (тобто ви шукаєте індекс i і проміжок g > 0 такий, що s[i]==s[i+g]==s[i+2*g]=="1"), я не впевнений, чи існує рішення O (n log n) , оскільки, можливо, є триплети O (n²), які мають ця властивість (подумайте про рядок усіх, існує приблизно n² / 2 таких трійків). Звичайно, ви шукаєте лише одне з них, але наразі я не маю уявлення, як його знайти ...


Так, ми обговорюємо більш складну версію проблеми. Проте рішення n * log (n) може бути можливим.
Олексій

1
насправді n вибирають 3, що є O (n ^ 3) можливих потрій, я думаю, коли ти сказав приблизно n ^
2/2

@gmatt: n вибрати 2 достатньо; якщо ми зафіксуємо два 1s, то положення третього визначається, і це постійний час, щоб побачити, чи є 1 у цій позиції чи ні.
ShreevatsaR

@ShreevatsaR: так правильно, я думаю, я думав про необмежений випадок.
ldog

1
@ gmatt: насправді ми шукаємо Tuples (i, g), як визначено вище, з обмеженнями, що 0 <= i <(n-3) і 0 <g <(ni-1) / 2, отже, оцінка n ^
2/2

1

Забавне питання, але як тільки ви зрозумієте, що фактична картина між двома '1 не має значення, алгоритм стає:

  • шукати "1"
  • починаючи з наступного сканування позиції для іншого "1" (до кінця масиву мінус відстань від поточного першого "1", інакше 3-й "1" буде поза межами)
  • якщо в положенні 2-го '1' плюс відстань до першого 1 'знайдеться третій' 1 ', ми маємо рівномірно пробіли.

Що стосується коду, JTest fashion, (Зверніть увагу, цей код не написаний як найбільш ефективний, і я додав кілька println, щоб побачити, що відбувається.)

import java.util.Random;

import junit.framework.TestCase;

public class AlgorithmTest extends TestCase {

 /**
  * Constructor for GetNumberTest.
  *
  * @param name The test's name.
  */
 public AlgorithmTest(String name) {
  super(name);
 }

 /**
  * @see TestCase#setUp()
  */
 protected void setUp() throws Exception {
  super.setUp();
 }

 /**
  * @see TestCase#tearDown()
  */
 protected void tearDown() throws Exception {
  super.tearDown();
 }

 /**
  * Tests the algorithm.
  */
 public void testEvenlySpacedOnes() {

  assertFalse(isEvenlySpaced(1));
  assertFalse(isEvenlySpaced(0x058003));
  assertTrue(isEvenlySpaced(0x07001));
  assertTrue(isEvenlySpaced(0x01007));
  assertTrue(isEvenlySpaced(0x101010));

  // some fun tests
  Random random = new Random();

  isEvenlySpaced(random.nextLong());
  isEvenlySpaced(random.nextLong());
  isEvenlySpaced(random.nextLong());
 }

 /**
  * @param testBits
  */
 private boolean isEvenlySpaced(long testBits) {
  String testString = Long.toBinaryString(testBits);
  char[] ones = testString.toCharArray();
  final char ONE = '1';

  for (int n = 0; n < ones.length - 1; n++) {

   if (ONE == ones[n]) {
    for (int m = n + 1; m < ones.length - m + n; m++) {

     if (ONE == ones[m] && ONE == ones[m + m - n]) {
      System.out.println(" IS evenly spaced: " + testBits + '=' + testString);
      System.out.println("               at: " + n + ", " + m + ", " + (m + m - n));
      return true;
     }
    }
   }
  }

  System.out.println("NOT evenly spaced: " + testBits + '=' + testString);
  return false;
 }
}

4
Якщо я не помиляюся, це O (n²), оскільки зовнішня петля працює n разів, а внутрішня петля працює в середньому n / 2 рази.
StriplingWarrior

Зовнішній цикл працює n разів, а внутрішній цикл працює в середньому n / 4, але запускається лише з позицій, що відповідають «1». Для наближення до поведінки n ^ 2 кількість '1' повинно бути високим, що призводить до справжнього результату рано, тим самим зупиняючи обробку. Тому поведінка n ^ 2 ніколи не відбудеться. Як визначити О на основі відомих властивостей даних, уникнути мене на даний момент.
rsp

На жаль, йдеться не про середній час реального життя, а про теоретичне велике виконання. А ваш підхід - O (n²) (такий же, як і мій, тому що ваш підхід такий самий, як і мій)
DaClown

Я говорив не про середню поведінку, а про максимальну поведінку. Я не був би здивований, якщо можна довести, що максимальна ентропія, яка не відповідає тесту, містить log n '1 в рядку.
rsp

Що робити, якщо оновити індекс у зовнішньому циклі таким, як перший з 1, знайдений у внутрішньому циклі, тобто, якщо (вони [m] == ONE) {n = m}? Чи допомагає це великому О?
пароплав25

1

Я подумав про підхід, який може спрацювати.

По-перше, при попередній обробці потрібно вставити всі списки менше ніж половину введеного розміру ( n / 3) до списку.

Подано рядок: 0000010101000100(зауважте, що цей конкретний приклад дійсний)

Вставте в список всі прости (1) від 1 до (16/2): {1, 2, 3, 4, 5, 6, 7}

Потім розділіть його навпіл:

100000101 01000100

Продовжуйте робити це, поки не доберетеся до рядків розміром 1. Для всіх рядків розміру-один із значком 1 додайте індекс рядка до списку можливостей; в іншому випадку поверніть -1 за провал.

Вам також потрібно буде повернути список ще можливих відстаней, пов'язаних з кожним стартовим індексом. (Почніть зі списку, який ви склали вище, і видаліть номери під час переходу) Тут порожній список означає, що ви маєте справу лише з одним 1, і тому будь-який інтервал можливий у цей момент; інакше список включає проміжки, які необхідно виключити.

Отже, продовжуючи приклад вище:

1000 0101 0100 0100

10 00 01 01 01 00 01 00

1 0 0 0 0 1 0 1 0 1 0 0 0 1 0 0

На першому кроці комбайна зараз у нас вісім комплектів з двох. По-перше, у нас є можливість множини, але ми дізнаємось, що проміжок через 1 неможливий через те, що там знаходиться інший нуль. Тож повертаємо 0 (для індексу) і {2,3,4,5,7} за те, що проміжок через 1 неможливий. По-друге, у нас нічого немає і тому повертаємо -1. У третьому ми маємо матч без проміжків, усунутих в індексі 5, тому повертаємо 5, {1,2,3,4,5,7}. У четвертій парі повертаємо 7, {1,2,3,4,5,7}. По-п'яте, поверніть 9, {1,2,3,4,5,7}. У шостому поверніть -1. У сьомому повернути 13, {1,2,3,4,5,7}. У восьмій поверніться -1.

Поєднавши знову чотири набори з чотирьох, ми маємо:

1000: Повернення (0, {4,5,6,7}) 0101: Повернення (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6 , 7}) 0100: Повернення (9, {3,4,5,6,7}) 0100: Повернення (13, {3,4,5,6,7})

Об'єднання у вісім:

10000101: Повернення (0, {5,7}), (5, {2,3,4,5,6,7}), (7, {1,2,3,4,5,6,7}) 01000100: Повернення (9, {4,7}), (13, {3,4,5,6,7})

Об’єднання в набір з шістнадцяти:

10000101 01000100

По мірі просування ми продовжуємо перевіряти всі можливості досі. До цього кроку ми залишили речі, які вийшли за межі кінця рядка, але тепер ми можемо перевірити всі можливості.

В основному ми перевіряємо перший 1 з проміжками 5 і 7 і виявляємо, що вони не відповідають рівню 1. (Зверніть увагу, що кожна перевірка КОНСТАНТНА, а не лінійний час) Потім ми перевіряємо другий (індекс 5) з проміжками 2, 3, 4, 5, 6 і 7 - або ми б, але ми можемо зупинитися на 2, оскільки що насправді відповідає.

Фу! Це досить довгий алгоритм.

Я не знаю на 100%, чи це O (n log n) через останній крок, але все, що там, безумовно, є O (n log n) , наскільки я можу сказати. Я повернусь до цього пізніше і спробую вдосконалити останній крок.

EDIT: Я змінив свою відповідь, щоб відобразити коментар Welbog. Вибачте за помилку. Я також напишу трохи псевдокоду пізніше, коли отримаю трохи більше часу, щоб розшифрувати те, що я написав ще раз. ;-)


Я не дотримуюся вашого алгоритму, але +1 для спроби алгоритму, який насправді намагається бути O (n log n)
ldog

Дякую. Я спробую це пояснити краще, коли отримаю більше часу (можливо, напишу якийсь псевдокод чи щось таке).
Platinum Azure

Чому ви дивитесь лише на розрив можливостей простих? Як би ви запропонували відповідати рядку типу 100010001? Якщо я правильно розумію ваш підхід, він не зможе відповідати, оскільки правильну відповідь (0,{4})неможливо обчислити. Зважаючи на те, що у вашому списку потрібні непростої форми, легко придумати патологічні рядки, які завищують переліки можливостей, які вам потрібно перевірити, щоб перевищити O (n log (n)), я думаю.
Вельбог

Присягається Ну, я спочатку збирався робити кратні, але я свого роду змінив свою відповідь на середині і не зміг все змінити. Вибачте. Виправить невдовзі
Platinum Azure

3
Я не думаю, що це O (n log n). На першому етапі комбінування ви обробляєте (n / 2) набори, кожен з яких, можливо, повертає набір O (n) можливих проміжків. На жаль, це лише O (n ^ 2), на жаль.
MartinStettner

1

Я дам тут свою грубу здогадку і дозвольте тим, хто краще розраховує складність, допомогти мені про те, як мій алгоритм працює в O-нотації

  1. задано двійковий рядок 0000010101000100 (як приклад)
  2. обрізання голови та хвоста нулів -> 00000 101010001 00
  3. ми отримуємо 101010001 з попереднього розрахунку
  4. перевірте, чи середній біт є "один", якщо це правда, чи виявлено дійсні три рівномірно розташованих "одиниці" (лише якщо число біт нечетно пронумеровано)
  5. співвідносно, якщо залишилася обрізана кількість бітів навіть пронумерована, голова та хвіст "один" не можуть бути частиною рівномірно розташованих "один",
  6. ми використовуємо в якості прикладу 1010100001 (з додатковим нулем, щоб стати рівномірним урожаєм); в цьому випадку нам потрібно знову обрізати, потім стає -> 10101 00001
  7. ми отримуємо 10101 з попереднього обчислення і перевіряємо середній біт, і ми знову знайшли рівномірно розташований біт

Я поняття не маю, як скласти для цього складність, хтось може допомогти?

редагувати: додати код, щоб проілюструвати мою ідею

edit2: спробував скласти мій код і виявив основні помилки, виправлені

char *binaryStr = "0000010101000100";

int main() {
   int head, tail, pos;
   head = 0;
   tail = strlen(binaryStr)-1;
   if( (pos = find3even(head, tail)) >=0 )
      printf("found it at position %d\n", pos);
   return 0;
}

int find3even(int head, int tail) {
   int pos = 0;
   if(head >= tail) return -1;
   while(binaryStr[head] == '0') 
      if(head<tail) head++;
   while(binaryStr[tail] == '0') 
      if(head<tail) tail--;
   if(head >= tail) return -1;
   if( (tail-head)%2 == 0 && //true if odd numbered
       (binaryStr[head + (tail-head)/2] == '1') ) { 
         return head;
   }else {
      if( (pos = find3even(head, tail-1)) >=0 )
         return pos;
      if( (pos = find3even(head+1, tail)) >=0 )
         return pos;
   }
   return -1;
}

@recursive Я думаю, що це спрацює, коли він дійшов до виклику find3even (голова + 1, хвіст), який потім обріже його, щоб стати 111 в голові = 4, чи можете ви ще раз перевірити мене?
andycjw

@recursive, будь ласка, перевірте код, який я додав, щоб краще пояснити псевдо-код, який я склав раніше, який не дуже суворий і стислий
andycjw

Це nlogn - для n біт ми очікуємо приблизно ітерацій реєстрації, перевіряючи n * c бітів, де C - константа.
Ron Warholic

Так, це, здається, не вдається на 111001 і 100111, як найпростіші випадки. Рівномірно розташовані позначки 1 не повинні зосереджуватись на середньому шматочку.
Дін J

Він обробляє ці випадки правильно, 111001 має парну кількість бітів, тому він негайно розбивається на 111 і 001. Оскільки 111 має непарну кількість бітів, а середній біт - один, він повертається успішно.
Рон Уорхолік

1

Я придумав щось подібне:

def IsSymetric(number):
    number = number.strip('0')

    if len(number) < 3:
        return False
    if len(number) % 2 == 0:
        return IsSymetric(number[1:]) or IsSymetric(number[0:len(number)-2])
    else:
        if number[len(number)//2] == '1':
            return True
        return IsSymetric(number[:(len(number)//2)]) or IsSymetric(number[len(number)//2+1:])
    return False

На це надихає andycjw.

  1. Обрізати нулі.
  2. Якщо навіть тоді тестуйте дві підрядки 0 - (len-2) (пропустити останній символ) та від 1 - (len-1) (пропустити першу діаграму)
  3. Якщо навіть не, якщо середній знак - один, ніж ми маємо успіх. Ще розділіть рядок в середині без елемента серединки і перевірте обидві частини.

Щодо складності це може бути O (nlogn), так як у кожній рекурсії ми ділимось на два.

Сподіваюся, це допомагає.


Схоже, ви перетворюєте проблему з N елементами у 2 проблеми з N-1 елементами. Поділ його навпіл означало б перетворення його на 2 проблеми з N / 2 елементами.
RHSeeger

Це стосується лише рівних довжин. Отже, якщо len дорівнює 8, алгоритм створює рядки довжини: 7, 7, 3, 3, 3, 3. Висота дерева рекурсії дорівнює 3 і дорівнює lg (8).
Беку

1

Гаразд, я зроблю ще один удар при проблемі. Я думаю, що я можу довести алгоритм O (n log (n)), подібний до тих, що вже обговорювались, використовуючи збалансоване двійкове дерево для зберігання відстаней між 1. Такий підхід був надихнутий спостереженнями Справедливості щодо зведення проблеми до переліку відстаней між 1-ю.

Чи могли б ми просканувати вхідний рядок, щоб побудувати збалансоване бінарне дерево навколо позиції 1, таким чином, щоб кожен вузол зберігав положення 1, а кожен край був позначений відстані до сусіднього 1 для кожного дочірнього вузла. Наприклад:

10010001 gives the following tree

      3
     / \
  2 /   \ 3
   /     \
  0       7

Це можна зробити в O (n log (n)), оскільки для рядка розміром n кожна вставка приймає O (log (n)) в гіршому випадку.

Тоді проблема полягає в пошуку дерева, щоб виявити, чи є в будь-якого вузла шлях від цього вузла через ліворуч, який має ту саму відстань, що і шлях до правої дитини. Це можна зробити рекурсивно на кожному піддереві. При злитті двох підрядів у пошуку ми повинні порівнювати відстані від шляхів у лівому піддереві з відстанями від шляхів у правому. Оскільки кількість шляхів у піддереві буде пропорційним log (n), а кількість вузлів - n, я вважаю, що це можна зробити за O (n log (n)).

Я щось пропустив?


"Оскільки кількість шляхів у піддереві буде пропорційним log (n)" Чому б не n? Як правило, це перспективний підхід.
sdcvvc

@sdcwc: пропорційно log (n), а не n, оскільки у збалансованому дереві кожне піддерево має половину вузлів, а кількість шляхів до кореня піддерева така ж, як кількість вузлів у піддереві (виключаючи корінь).
Джеремі Бурк

0

Це здавалося сподобалося веселою проблемою, тому я вирішив спробувати свої сили.

Я роблю припущення, що 111000001 знайде перші 3 та матиме успіх. По суті, кількість нулів, що слідують за 1, є важливою справою, оскільки 0111000 за вашим визначенням така ж, як і 111000. Як тільки ви знайдете два випадки з 1, наступний 1 знайдений завершує трилогію.

Ось він у Python:

def find_three(bstring):
    print bstring
    dict = {}
    lastone = -1
    zerocount = 0
    for i in range(len(bstring)):
        if bstring[i] == '1':
            print i, ': 1'
            if lastone != -1:
                if(zerocount in dict):
                    dict[zerocount].append(lastone)
                    if len(dict[zerocount]) == 2:
                        dict[zerocount].append(i)
                        return True, dict
                else:
                    dict[zerocount] = [lastone]
            lastone = i
            zerocount = 0
        else:
            zerocount = zerocount + 1
    #this is really just book keeping, as we have failed at this point
    if lastone != -1:
        if(zerocount in dict):
            dict[zerocount].append(lastone)
        else:
            dict[zerocount] = [lastone]
    return False, dict

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


@рекурсивні, вони не розташовані рівномірно.
Джеймс Макмахон

Що ви маєте на увазі під рівномірно розташованими? Подивіться на індекси 0, 3 і 6. Усі, і з двома роздільними.
рекурсивний

О, я бачу, як я зрозумів, нулі були включені лише в інтервал.
Джеймс Макмахон

У запитанні йдеться про "1001011", на якому це не працює. Була раніше (зараз видалена) відповідь, опублікована одразу після того, як було задано питання, що вирішило ту саму (іншу) проблему, як і ця. :-)
ShreevatsaR

Я сьогодні дивився на це на роботі і не розумів, що Роб має на увазі під його редакцією. Я відредагував питання для наочності. Я мав би знати, що мені щось не вистачало, коли мені було легко з цим.
Джеймс Макмахон

0

Я припускаю, що причина цього nlog (n) пов'язана з наступним:

  • Щоб знайти 1, що є початком триплету, вам потрібно перевірити (n-2) символів. Якщо ви не знайшли його до цього моменту, ви цього не зробите (символи n-1 і n не можуть запустити трійку) (O (n))
  • Щоб знайти другий 1, який є частиною триплета (розпочатим першим), потрібно перевірити m / 2 (m = nx, де x - зміщення перших 1) символів. Це тому, що якщо ви не знайшли другий 1 до того моменту, коли ви перебуваєте на півдорозі від першого до кінця, ви не будете ... оскільки третій 1 повинен бути приблизно однаковий відстань повз другий. (O (журнал (n)))
  • Це O (1), щоб знайти останній 1, оскільки ви знаєте індекс, він повинен бути на момент знаходження першого і другого.

Отже, у вас n, log (n) і 1 ... O (nlogn)

Редагувати: На жаль, погано. У моєму мозку було встановлено, що n / 2 було входом у систему, що, очевидно, не є (подвоєння числа на елементах все ще подвоює кількість ітерацій внутрішньої петлі). Це все ще знаходиться на n ^ 2, не вирішуючи задачі. Ну, принаймні, я повинен написати якийсь код :)


Впровадження в Tcl

proc get-triplet {input} {
    for {set first 0} {$first < [string length $input]-2} {incr first} {
        if {[string index $input $first] != 1} {
            continue
        }
        set start [expr {$first + 1}]
        set end [expr {1+ $first + (([string length $input] - $first) /2)}]
        for {set second $start} {$second < $end} {incr second} {
            if {[string index $input $second] != 1} {
                continue
            }
            set last [expr {($second - $first) + $second}]
            if {[string index $input $last] == 1} {
                return [list $first $second $last]
            }
        }
    }
    return {}
}

get-triplet 10101      ;# 0 2 4
get-triplet 10111      ;# 0 2 4
get-triplet 11100000   ;# 0 1 2
get-triplet 0100100100 ;# 1 4 7

0

Я думаю, що я знайшов спосіб вирішення проблеми, але не можу побудувати офіційного доказу. Я прийняв рішення, написане на Java, і воно використовує лічильник 'n' для підрахунку кількості доступу до списку / масиву. Отже, n повинен бути меншим або рівним stringLength * log (stringLength), якщо він правильний. Я спробував це для цифр 0 до 2 ^ 22, і це працює.

Він починається з ітерації над вхідним рядком та складання списку всіх індексів, які містять один. Це просто O (n).

Тоді зі списку індексів він вибирає першийIndex, а secondIndex, який більший за перший. Ці два індекси повинні містити їх, оскільки вони є у списку індексів. Звідти можна обчислити третійIndex. Якщо inputString [thirdIndex] дорівнює 1, то він зупиняється.

public static int testString(String input){
//n is the number of array/list accesses in the algorithm
int n=0;

//Put the indices of all the ones into a list, O(n)
ArrayList<Integer> ones = new ArrayList<Integer>();
for(int i=0;i<input.length();i++){
    if(input.charAt(i)=='1'){
        ones.add(i);
    }
}

//If less than three ones in list, just stop
if(ones.size()<3){
    return n;
}

int firstIndex, secondIndex, thirdIndex;
for(int x=0;x<ones.size()-2;x++){
    n++;
    firstIndex = ones.get(x);

    for(int y=x+1; y<ones.size()-1; y++){
        n++;
        secondIndex = ones.get(y);
        thirdIndex = secondIndex*2 - firstIndex;

        if(thirdIndex >= input.length()){
            break;
        }

        n++;
        if(input.charAt(thirdIndex) == '1'){
            //This case is satisfied if it has found three evenly spaced ones
            //System.out.println("This one => " + input);
            return n;
        }
    }
}

return n;

}

додаткова примітка: лічильник n не збільшується, коли він переходить на рядок введення для побудови списку індексів. Ця операція є O (n), тому вона ніяк не вплине на складність алгоритму.


У вас все ще є дві петлі O (n), вкладені, що робить його O (n ^ 2)
RHSeeger

Масив індексів не такого розміру, як вхідний рядок. Через це мені важко написати реальний доказ або довести, що це неправильно. Я підозрюю, що є якась основна математична ідея, яка змушує цю роботу.
Роберт Паркер

1
Я думаю, що фокус цієї проблеми полягає в тому, що, незважаючи на те, що ваш алгоритм є O (n ^ 2), найгірший можливий випадок рядка, який ви можете отримати, призведе лише до ітерацій O (nlogn), інакше ви знайдете рішення за допомогою свого алгоритму.
z -

2
тестування його до 2 ^ 22 насправді не перевіряє його складність. 2 ^ 22 має лише 22 біти, це означає, що ваш N дорівнює 22. Спробуйте це для кількох значень, де N - кілька мільйонів.
Пітер Рекор

1
Спробуйте цей алгоритм з однією з максимальних «поганих» рядків, наведених у відповіді yx, і ви побачите, що це O(n^2)алгоритм.
Вельбог

0

Один із завдань проблеми - думати про фактори та зміни.

Зі зміщенням ви порівнюєте рядок одиниць і нулів зі зміщеною версією себе. Потім ви берете відповідні. Візьмемо цей приклад, зміщений на два:

1010101010
  1010101010
------------
001010101000

Отриманий 1 (розрядний ANDed) повинен представляти всі 1, які рівномірно розташовані двома. Цей же приклад зміщений на три:

1010101010
   1010101010
-------------
0000000000000

У цьому випадку немає 1-х, які рівномірно розташовані три один від одного.

То що це вам каже? Добре, що вам потрібно лише перевірити зміни, які є простими числами. Наприклад, скажіть, у вас є два 1, які розташовані на шість. Вам доведеться лише перевірити "дві" зміни та "три" зміни (оскільки ці розділи шість). Наприклад:

10000010 
  10000010 (Shift by two)
    10000010
      10000010 (We have a match)

10000010
   10000010 (Shift by three)
      10000010 (We have a match)

Таким чином, єдині зміни, які вам коли-небудь потрібно перевірити, - це 2,3,5,7,11,13 і т. Д. До найпростішого, найближчого до квадратного кореня за розміром рядка цифр.

Майже вирішений?

Я думаю, що я ближче до рішення. В основному:

  1. Скануйте рядок на 1. Для кожної 1 ноти це залишок після прийняття модуля своєї позиції. Модуль коливається від 1 до половини розміру рядка. Це тому, що найбільший можливий розмір розділення - це половина рядка. Це робиться в O (n ^ 2). АЛЕ Потрібно перевірити лише основні модулі, щоб O (n ^ 2 / log (n))
  2. Сортуйте список модулів / залишків, щоб спочатку був найбільший модуль, це можна зробити за час O (n * log (n)).
  3. Шукайте три послідовні модулі / залишки, які однакові.
  4. Якось знайдіть позицію тих!

Я думаю, що найбільша підказка у відповіді полягає в тому, що алгоритми швидкого сортування - це O (n * log (n)).

НЕПРАВИЛЬНО

Крок 1 є неправильним, як вказував колега. Якщо у нас є позиції 1 у позиціях 2,12 та 102. Тоді, приймаючи модуль 10, усі вони мали б однакові залишки, і все ж не однаково розташовані один від одного! Вибачте.


Це цікавий підхід, повідомте нас, якщо ви знайдете повне рішення.
Джеймс МакМахон

зсув на число k O (n) разів, а потім O (n) перевірка за зміну дає алгоритм O (n ^ 2), навіть якщо ви переходили на одне число. Ваш алгоритм повинен був би зміститися на більш ніж одне число.
ldog

0

Ось кілька думок, які, незважаючи на мої найкращі зусилля, здаються, не загортаються в лук. Проте вони можуть стати корисною відправною точкою для чийогось аналізу.

Розгляньте запропоноване рішення наступним чином - такий підхід, який запропонували кілька людей, включаючи мене в попередній версії цієї відповіді. :)

  1. Обрізання провідних і кінцевих нулів.
  2. Скануйте рядок, шукаючи 1.
  3. Коли знайдено 1:
    1. Припустимо, що це середина 1 рішення.
    2. Для кожного попереднього 1 використовуйте його збережене положення для обчислення очікуваного положення підсумкового 1.
    3. Якщо обчислена позиція закінчується після закінчення рядка, вона не може бути частиною рішення, тому відмініть позицію зі списку кандидатів.
    4. Перевірте рішення.
  4. Якщо рішення не знайдено, додайте поточний 1 до списку кандидатів.
  5. Повторюйте, поки більше 1 не знайдені.

Тепер розглянемо рядки вхідних рядків на зразок наступних, які не матимуть рішення:

101
101001
1010010001
101001000100001
101001000100001000001

Загалом, це з'єднання k рядків форми j 0, а потім 1 для j від нуля до k-1.

k=2  101
k=3  101001
k=4  1010010001
k=5  101001000100001
k=6  101001000100001000001

Зауважте, що довжини підрядів дорівнюють 1, 2, 3 і т. Д. Отже, розмір задачі n має підрядки довжиною від 1 до k такі, що n = k (k + 1) / 2.

k=2  n= 3  101
k=3  n= 6  101001
k=4  n=10  1010010001
k=5  n=15  101001000100001
k=6  n=21  101001000100001000001

Зауважимо, що k також відстежує число 1, яке ми маємо врахувати. Пам’ятайте, що кожного разу, коли ми бачимо 1, нам потрібно враховувати всі побачені досі. Отже, коли ми бачимо другий 1, ми розглядаємо лише перший, коли бачимо третій 1, ми переглядаємо перші два, коли бачимо четвертий 1, нам потрібно переглянути перші три тощо. На кінець алгоритму ми розглянули k (k-1) / 2 пари 1-х. Зателефонуйте на с.

k=2  n= 3  p= 1  101
k=3  n= 6  p= 3  101001
k=4  n=10  p= 6  1010010001
k=5  n=15  p=10  101001000100001
k=6  n=21  p=15  101001000100001000001

Зв'язок між n і p полягає в тому, що n = p + k.

Процес проходження рядка займає O (n) час. Щоразу, коли виникає 1, проводиться максимум (k-1) порівнянь. Оскільки n = k (k + 1) / 2, n> k ** 2, тому sqrt (n)> k. Це дає нам O (n sqrt (n)) або O (n ** 3/2). Однак зауважте, що це може бути не дуже жорсткою межею, оскільки кількість порівнянь переходить від 1 до максимуму k, це не k весь час. Але я не впевнений, як це пояснити в математиці.

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

Оскільки хтось може все-таки вважати його корисним, ось мій код цього рішення в Perl:

#!/usr/bin/perl

# read input as first argument
my $s = $ARGV[0];

# validate the input
$s =~ /^[01]+$/ or die "invalid input string\n";

# strip leading and trailing 0's
$s =~ s/^0+//;
$s =~ s/0+$//;

# prime the position list with the first '1' at position 0
my @p = (0);

# start at position 1, which is the second character
my $i = 1;

print "the string is $s\n\n";

while ($i < length($s)) {
   if (substr($s, $i, 1) eq '1') {
      print "found '1' at position $i\n";
      my @t = ();
      # assuming this is the middle '1', go through the positions
      # of all the prior '1's and check whether there's another '1'
      # in the correct position after this '1' to make a solution
      while (scalar @p) {
         # $p is the position of the prior '1'
         my $p = shift @p;
         # $j is the corresponding position for the following '1'
         my $j = 2 * $i - $p;
         # if $j is off the end of the string then we don't need to
         # check $p anymore
         next if ($j >= length($s));
         print "checking positions $p, $i, $j\n";
         if (substr($s, $j, 1) eq '1') {
            print "\nsolution found at positions $p, $i, $j\n";
            exit 0;
         }
         # if $j isn't off the end of the string, keep $p for next time
         push @t, $p;
      }
      @p = @t;
      # add this '1' to the list of '1' positions
      push @p, $i;
   }
   $i++;
}

print "\nno solution found\n";

Ваша послідовність «нерозв’язання» неправильна; індекс кожного 1 - це послідовність трикутних чисел 1, 3, 6, 10, 15 ... тощо. Він включає числа 6, 36 і 66, які утворюють арифметичну прогресію.
Джейсон S

0

Під час сканування 1s додайте їхні позиції до списку. Додаючи другий та наступний 1, порівняйте їх із кожною позицією у списку до цих пір. Пробіл дорівнює currentOne (центр) - попереднійOne (зліва). Правий біт - це currentOne + інтервал. Якщо це 1, кінець.

Список тих зростає обернено з проміжком між ними. Простіше кажучи, якщо у вас багато 0 між 1-ю (як у гіршому випадку), ваш список відомих 1-х буде рости досить повільно.

using System;
using System.Collections.Generic;

namespace spacedOnes
{
    class Program
    {
        static int[] _bits = new int[8] {128, 64, 32, 16, 8, 4, 2, 1};

        static void Main(string[] args)
        {
            var bytes = new byte[4];
            var r = new Random();
            r.NextBytes(bytes);
            foreach (var b in bytes) {
                Console.Write(getByteString(b));
            }
            Console.WriteLine();
            var bitCount = bytes.Length * 8;
            var done = false;
            var onePositions = new List<int>();
            for (var i = 0; i < bitCount; i++)
            {
                if (isOne(bytes, i)) {
                    if (onePositions.Count > 0) {
                        foreach (var knownOne in onePositions) {
                            var spacing = i - knownOne;
                            var k = i + spacing;
                            if (k < bitCount && isOne(bytes, k)) {
                                Console.WriteLine("^".PadLeft(knownOne + 1) + "^".PadLeft(spacing) + "^".PadLeft(spacing));
                                done = true;
                                break;
                            }
                        }
                    }
                    if (done) {
                        break;
                    }
                    onePositions.Add(i);
                }
            }
            Console.ReadKey();
        }

        static String getByteString(byte b) {
            var s = new char[8];
            for (var i=0; i<s.Length; i++) {
                s[i] = ((b & _bits[i]) > 0 ? '1' : '0');
            }
            return new String(s);
        }

        static bool isOne(byte[] bytes, int i)
        {
            var byteIndex = i / 8;
            var bitIndex = i % 8;
            return (bytes[byteIndex] & _bits[bitIndex]) > 0;
        }
    }
}

0

Я подумав, що додам ще один коментар, перш ніж розміщувати 22-е наївне рішення проблеми. Для наївного рішення нам не потрібно показувати, що число 1 у рядку не більше O (log (n)), а скоріше, щоб воно було не більше O (sqrt (n * log (n)).

Розв’язувач:

def solve(Str):
    indexes=[]
    #O(n) setup
    for i in range(len(Str)):
        if Str[i]=='1':
            indexes.append(i)

    #O((number of 1's)^2) processing
    for i in range(len(indexes)):
        for j in range(i+1, len(indexes)):
                            indexDiff = indexes[j] - indexes[i]
            k=indexes[j] + indexDiff
            if k<len(Str) and Str[k]=='1':
                return True
    return False

Це в основному трохи схоже на ідею та реалізацію flybywire, хоча дивиться вперед, а не назад.

Жадібний String Builder:

#assumes final char hasn't been added, and would be a 1 
def lastCharMakesSolvable(Str):
    endIndex=len(Str)
    j=endIndex-1
    while j-(endIndex-j) >= 0:
        k=j-(endIndex-j)
        if k >= 0 and Str[k]=='1' and Str[j]=='1':
            return True
        j=j-1
    return False



def expandString(StartString=''):
    if lastCharMakesSolvable(StartString):
        return StartString + '0'
    return StartString + '1'

n=1
BaseStr=""
lastCount=0
while n<1000000:
    BaseStr=expandString(BaseStr)
    count=BaseStr.count('1')
    if count != lastCount:
        print(len(BaseStr), count)
    lastCount=count
    n=n+1

(На мій захист, я все ще перебуваю на етапі розуміння "вивчити пітона")

Крім того, потенційно корисний вихід із жадібної будівлі струн, є досить послідовний стрибок після удару сили 2 у кількості 1-х… чого я не хотів чекати, щоб стати свідком удару 2096 року.

strlength   # of 1's
    1    1
    2    2
    4    3
    5    4
   10    5
   14    8
   28    9
   41    16
   82    17
  122    32
  244    33
  365    64
  730    65
 1094    128
 2188    129
 3281    256
 6562    257
 9842    512
19684    513
29525    1024

0

Спробую представити математичний підхід. Це скоріше початок, ніж кінець, тому будь-яка допомога, коментар чи навіть протиріччя - будуть глибоко вдячні. Однак якщо такий підхід доведений - алгоритм - це прямий пошук у рядку.

  1. З огляду на фіксовану кількість пробілів kі рядка S, пошук k-інтервалу-триплета займає O(n)- Ми просто перевіряємо для кожного 0<=i<=(n-2k)if S[i]==S[i+k]==S[i+2k]. Тест бере, O(1)і ми робимо це в тих n-kвипадках, коли kце константа, тому воно займає O(n-k)=O(n).

  2. Припустимо, що існує кількість зворотної пропорції між кількістю 1s та максимальним пробілом, який нам потрібно шукати. Тобто, якщо їх багато 1, має бути трійчатка, і вона повинна бути досить щільною; Якщо їх лише декілька 1, то триплет (якщо такий є) може бути досить рідким. Іншими словами, я можу довести, що якщо мене вистачає 1, такий триплет повинен існувати - і чим більше 1я маю, тим більш щільним є трійник. Це можна пояснити принципом « Голуб» - сподіваюся детальніше розглянути це пізніше.

  3. Скажіть, маєте верхню межу kможливої ​​кількості пробілів, які мені потрібно шукати. Тепер для кожного 1розташованого в S[i]нам потрібно зареєструватися 1в S[i-1]і S[i+1], S[i-2]і S[i+2], ... S[i-k]і S[i+k]. Це потрібно O((k^2-k)/2)=O(k^2)для кожної 1з S- за формулою підведення підсумків серії Гаусса . Зауважте, що це відрізняється від розділу 1 - я маю kяк верхню межу для кількості пробілів, а не як постійний пробіл.

Нам потрібно довести O(n*log(n)). Тобто нам потрібно показати, що k*(number of 1's)пропорційно log(n).

Якщо ми можемо це зробити, алгоритм тривіальний - для кожного, 1у Sякого є індекс i, просто шукаємо 1's з кожної сторони на відстань k. Якщо двох знайшли на одній відстані, поверніться iі k. Знову ж таки, хитра частина - це пошук kта доведення правильності.

Я дуже вдячний вашим коментарям тут - я досі намагався знайти співвідношення між kкількістю та кількістю 1моїх дошок без успіху.


0

Припущення:

Просто неправильно, якщо говорити про log (n) кількість верхньої межі одиниць

Редагувати:

Тепер я виявив, що, використовуючи номери Кантора (якщо правильно), щільність на множині становить (2/3) ^ Log_3 (n) (яка дивна функція), і я згоден, щільність log (n) / n є сильною.

Якщо це верхня межа, існує алгоритм, який вирішує цю проблему принаймні O (n * (3/2) ^ (log (n) / log (3))) часової складності та O ((3/2) ^ ( складність простору log (n) / log (3))). (перевірити відповідь Справедливості на алгоритм)

Це все ще набагато краще, ніж O (n ^ 2)

Ця функція ((3/2) ^ (log (n) / log (3))) насправді схожа на n * log (n) з першого погляду.

Як я отримав цю формулу?

Застосування номера канторів у рядку.
Припустимо, що довжина струни становить 3 ^ p == n
На кожному кроці в поколінні струни Cantor ви зберігаєте 2/3 великої кількості одиниць. Застосовуйте цей р разів.

Це означає (n * ((2/3) ^ p)) -> (((3 ^ p)) * ((2/3) ^ p)) ті, що залишилися, і після спрощення 2 ^ p. Це означає 2 ^ р у 3 ^ p рядку -> (3/2) ^ p. Замініть p = log (n) / log (3) і отримайте
((3/2) ^ (log (n) / log (3)))


Неправильно: Набір кантора має щільність n ^ log_3 (2).
sdcvvc

0

Як щодо простого рішення O (n) з простором O (n ^ 2)? (Використовується припущення, що всі побітові оператори працюють в O (1).)

Алгоритм в основному працює в чотири етапи:

Етап 1: Для кожного шматочка в початковому номері з’ясуйте, наскільки далеко вони, але врахуйте лише один напрямок. (Я розглядав усі біти в напрямку найменш значущого біта.)

Етап 2: Зворотний порядок бітів на вході;

Етап 3: Повторіть крок 1 на зворотному вході.

Етап 4: Порівняйте результати етапу 1 та етапу 3. Якщо будь-які біти однаково розташовані вище І нижче, ми повинні мати хіт.

Майте на увазі, що жоден крок у наведеному вище алгоритмі не займає більше часу, ніж O (n). ^ _ ^

В якості додаткової переваги, цей алгоритм знайде ВСІ однаково розташовані з кожного значення. Так, наприклад, якщо ви отримуєте результат "0x0005", то в БОТИ 1 та 3 одиниці є однакові відстані

Я не намагався оптимізувати код нижче, але це компільований код C #, який, здається, працює.

using System;

namespace ThreeNumbers
{
    class Program
    {
        const int uint32Length = 32;

        static void Main(string[] args)
        {
            Console.Write("Please enter your integer: ");
            uint input = UInt32.Parse(Console.ReadLine());

            uint[] distancesLower = Distances(input);
            uint[] distancesHigher = Distances(Reverse(input));

            PrintHits(input, distancesLower, distancesHigher);
        }

        /// <summary>
        /// Returns an array showing how far the ones away from each bit in the input.  Only 
        /// considers ones at lower signifcant bits.  Index 0 represents the least significant bit 
        /// in the input.  Index 1 represents the second least significant bit in the input and so 
        /// on.  If a one is 3 away from the bit in question, then the third least significant bit 
        /// of the value will be sit.
        /// 
        /// As programed this algorithm needs: O(n) time, and O(n*log(n)) space.  
        /// (Where n is the number of bits in the input.)
        /// </summary>
        public static uint[] Distances(uint input)
        {
            uint[] distanceToOnes = new uint[uint32Length];
            uint result = 0;

            //Sets how far each bit is from other ones. Going in the direction of LSB to MSB
            for (uint bitIndex = 1, arrayIndex = 0; bitIndex != 0; bitIndex <<= 1, ++arrayIndex)
            {
                distanceToOnes[arrayIndex] = result;
                result <<= 1;

                if ((input & bitIndex) != 0)
                {
                    result |= 1;
                }
            }

            return distanceToOnes;
        }

        /// <summary>
        /// Reverses the bits in the input.
        /// 
        /// As programmed this algorithm needs O(n) time and O(n) space.  
        /// (Where n is the number of bits in the input.)
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static uint Reverse(uint input)
        {
            uint reversedInput = 0;
            for (uint bitIndex = 1; bitIndex != 0; bitIndex <<= 1)
            {
                reversedInput <<= 1;
                reversedInput |= (uint)((input & bitIndex) != 0 ? 1 : 0);
            }

            return reversedInput;
        }

        /// <summary>
        /// Goes through each bit in the input, to check if there are any bits equally far away in 
        /// the distancesLower and distancesHigher
        /// </summary>
        public static void PrintHits(uint input, uint[] distancesLower, uint[] distancesHigher)
        {
            const int offset = uint32Length - 1;

            for (uint bitIndex = 1, arrayIndex = 0; bitIndex != 0; bitIndex <<= 1, ++arrayIndex)
            {
                //hits checks if any bits are equally spaced away from our current value
                bool isBitSet = (input & bitIndex) != 0;
                uint hits = distancesLower[arrayIndex] & distancesHigher[offset - arrayIndex];

                if (isBitSet && (hits != 0))
                {
                    Console.WriteLine(String.Format("The {0}-th LSB has hits 0x{1:x4} away", arrayIndex + 1, hits));
                }
            }
        }
    }
}

Хтось, ймовірно, прокоментує, що для будь-якої достатньо великої кількості побітових операцій в O (1) не можна робити. Ви б мали рацію. Однак я б припустив, що кожне рішення, що використовує додавання, віднімання, множення або ділення (що неможливо зробити шляхом зсуву), також матиме цю проблему.


0

Нижче - рішення. Тут і там могли бути невеликі помилки, але ідея є здоровою.

Редагувати: це не n * журнал (n)

КОД PSEUDO:

foreach character in the string
  if the character equals 1 {         
     if length cache > 0 { //we can skip the first one
        foreach location in the cache { //last in first out kind of order
           if ((currentlocation + (currentlocation - location)) < length string)
              if (string[(currentlocation + (currentlocation - location))] equals 1)
                 return found evenly spaced string
           else
              break;
        }
     }
     remember the location of this character in a some sort of cache.
  }

return didn't find evenly spaced string

C # код:

public static Boolean FindThreeEvenlySpacedOnes(String str) {
    List<int> cache = new List<int>();

    for (var x = 0; x < str.Length; x++) {
        if (str[x] == '1') {
            if (cache.Count > 0) {
                for (var i = cache.Count - 1; i > 0; i--) {
                    if ((x + (x - cache[i])) >= str.Length)
                        break;

                    if (str[(x + (x - cache[i]))] == '1')
                        return true;                            
                }
            }
            cache.Add(x);                    
        }
    }

    return false;
}

Як це працює:

iteration 1:
x
|
101101001
// the location of this 1 is stored in the cache

iteration 2:
 x
 | 
101101001

iteration 3:
a x b 
| | | 
101101001
//we retrieve location a out of the cache and then based on a 
//we calculate b and check if te string contains a 1 on location b

//and of course we store x in the cache because it's a 1

iteration 4:
  axb  
  |||  
101101001

a  x  b  
|  |  |  
101101001


iteration 5:
    x  
    |  
101101001

iteration 6:
   a x b 
   | | | 
101101001

  a  x  b 
  |  |  | 
101101001
//return found evenly spaced string

1
Ваш алгоритм працює, але вам потрібно довести, що він менший за O (n ^ 2). Тривіальний аналіз приводить вас до O (n ^ 2). Для кожного 1 ви переходите всі 1, які були до цього. Зробити функцією складності 1 + 2 + 3 + ... + (k / 2-1) = O (k ^ 2) [де k - число 1s].
Анна

Я перевірив, і справді найгірший сценарій без рішення є більшим, ніж O (n * log (n))
Niek H.

0

Очевидно, що нам потрібно принаймні перевірити пучки трійок одночасно, тому нам потрібно якось стиснути чеки. У мене є алгоритм кандидата, але аналіз часової складності виходить за рамки моїх можливостей * часовий поріг.

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

-

Почніть з кореня. Якщо квадрат загальної кількості 1 нижче, ніж його дозволена вартість, застосуйте наївний алгоритм. В іншому випадку рецидивуйте на своїх дітей.

Тепер ми або повернулися в межах бюджету, або знаємо, що не існує дійсних трійок, які повністю містяться в одному з дітей. Тому ми повинні перевірити міжвузлові трійники.

Тепер речі стають неймовірно безладними. Ми, по суті, хочемо повторити потенційні набори дітей, обмежуючи дальність дії. Як тільки діапазон буде достатньо обмежений, щоб алгоритм наївності працював за бюджетом, ви це зробите. Насолоджуйтесь реалізацією цього, бо гарантую, що це буде нудно. Там як десяток випадків.

-

Я вважаю, що алгоритм спрацює через те, що послідовності без дійсних трійок, схоже, йдуть поперемінно між пучками 1 і партією 0. Він ефективно розбиває сусідній простір пошуку, і дерево імітує це розщеплення.

Час виконання алгоритму зовсім не очевидно. Він спирається на нетривіальні властивості послідовності. Якщо 1-і є дуже рідкісними, тоді наївний алгоритм працюватиме під бюджет. Якщо цифри 1 щільні, то відповідність слід знайти відразу. Але якщо щільність "просто потрібна" (наприклад, біля ~ n ^ 0,63, чого ви можете досягти, встановивши всі біти в позиціях без цифри '2' в базі 3), я не знаю, чи буде вона працювати. Вам доведеться довести, що ефект розщеплення досить сильний.


0

Тут немає теоретичної відповіді, але я написав швидку програму Java для вивчення поведінки часу виконання як функції k і n, де n - загальна довжина бітів, а k - число 1. Я з кількома відповідями, які говорять про те, що "регулярний" алгоритм, який перевіряє всі пари бітових позицій і шукає 3-й біт, хоча в найгіршому випадку він вимагатиме O (k ^ 2), у реальність, тому що найгірший випадок потребує розріджених штрихів, це O (n ln n).

У будь-якому випадку ось програма нижче. Це програма в стилі Монте-Карло, яка проводить велику кількість випробувань NTRIALS для постійного n і випадковим чином генерує біт-набори для діапазону k-значень за допомогою процесів Бернуллі з щільністю, обмеженою межею, яку можна задати, і записує час роботи знаходження або невдало знайти трійку рівномірно розташованих, час, виміряний кроками НЕ за процесором. Я провів це за n = 64, 256, 1024, 4096, 16384 * (все ще працює), спочатку пробний запуск з 500000 випробувань, щоб побачити, які k-значення займають найдовший час роботи, потім інший тест з 5000000 випробувань із звуженими- фокус щільності, щоб побачити, як виглядають ці значення. Найдовші часи роботи трапляються з дуже рідкою щільністю (наприклад, для n = 4096 піки часу роботи знаходяться в діапазоні k = 16-64, з м'яким піком середнього часу виконання при 4212 кроках @ k = 31, Максимальна тривалість виконання досягла 5101 кроку @ k = 58). Схоже, знадобиться надзвичайно великі значення N, щоб крок O (k ^ 2) з найгіршого випадку став більшим, ніж крок O (n), де ви скануєте біткорд, щоб знайти показники позиції 1.

package com.example.math;

import java.io.PrintStream;
import java.util.BitSet;
import java.util.Random;

public class EvenlySpacedOnesTest {
    static public class StatisticalSummary
    {
        private int n=0;
        private double min=Double.POSITIVE_INFINITY;
        private double max=Double.NEGATIVE_INFINITY;
        private double mean=0;
        private double S=0;

        public StatisticalSummary() {}
        public void add(double x) {
            min = Math.min(min, x);
            max = Math.max(max, x);
            ++n;
            double newMean = mean + (x-mean)/n;
            S += (x-newMean)*(x-mean);
            // this algorithm for mean,std dev based on Knuth TAOCP vol 2
            mean = newMean;
        }
        public double getMax() { return (n>0)?max:Double.NaN; }
        public double getMin() { return (n>0)?min:Double.NaN; }
        public int getCount() { return n; }
        public double getMean() { return (n>0)?mean:Double.NaN; }
        public double getStdDev() { return (n>0)?Math.sqrt(S/n):Double.NaN; } 
        // some may quibble and use n-1 for sample std dev vs population std dev    
        public static void printOut(PrintStream ps, StatisticalSummary[] statistics) {
            for (int i = 0; i < statistics.length; ++i)
            {
                StatisticalSummary summary = statistics[i];
                ps.printf("%d\t%d\t%.0f\t%.0f\t%.5f\t%.5f\n",
                        i,
                        summary.getCount(),
                        summary.getMin(),
                        summary.getMax(),
                        summary.getMean(),
                        summary.getStdDev());
            }
        }
    }

    public interface RandomBernoulliProcess // see http://en.wikipedia.org/wiki/Bernoulli_process
    {
        public void setProbability(double d);
        public boolean getNextBoolean();
    }

    static public class Bernoulli implements RandomBernoulliProcess
    {
        final private Random r = new Random();
        private double p = 0.5;
        public boolean getNextBoolean() { return r.nextDouble() < p; }
        public void setProbability(double d) { p = d; }
    }   
    static public class TestResult {
        final public int k;
        final public int nsteps;
        public TestResult(int k, int nsteps) { this.k=k; this.nsteps=nsteps; } 
    }

    ////////////
    final private int n;
    final private int ntrials;
    final private double pmin;
    final private double pmax;
    final private Random random = new Random();
    final private Bernoulli bernoulli = new Bernoulli();
    final private BitSet bits;
    public EvenlySpacedOnesTest(int n, int ntrials, double pmin, double pmax) {
        this.n=n; this.ntrials=ntrials; this.pmin=pmin; this.pmax=pmax;
        this.bits = new BitSet(n);
    }

    /*
     * generate random bit string
     */
    private int generateBits()
    {
        int k = 0; // # of 1's
        for (int i = 0; i < n; ++i)
        {
            boolean b = bernoulli.getNextBoolean();
            this.bits.set(i, b);
            if (b) ++k;
        }
        return k;
    }

    private int findEvenlySpacedOnes(int k, int[] pos) 
    {
        int[] bitPosition = new int[k];
        for (int i = 0, j = 0; i < n; ++i)
        {
            if (this.bits.get(i))
            {
                bitPosition[j++] = i;
            }
        }
        int nsteps = n; // first, it takes N operations to find the bit positions.
        boolean found = false;
        if (k >= 3) // don't bother doing anything if there are less than 3 ones. :(
        {       
            int lastBitSetPosition = bitPosition[k-1];
            for (int j1 = 0; !found && j1 < k; ++j1)
            {
                pos[0] = bitPosition[j1];
                for (int j2 = j1+1; !found && j2 < k; ++j2)
                {
                    pos[1] = bitPosition[j2];

                    ++nsteps;
                    pos[2] = 2*pos[1]-pos[0];
                    // calculate 3rd bit index that might be set;
                    // the other two indices point to bits that are set
                    if (pos[2] > lastBitSetPosition)
                        break;
                    // loop inner loop until we go out of bounds

                    found = this.bits.get(pos[2]);
                    // we're done if we find a third 1!
                }
            }
        }
        if (!found)
            pos[0]=-1;
        return nsteps;
    }

    /*
     * run an algorithm that finds evenly spaced ones and returns # of steps.
     */
    public TestResult run()
    {
        bernoulli.setProbability(pmin + (pmax-pmin)*random.nextDouble());
        // probability of bernoulli process is randomly distributed between pmin and pmax

        // generate bit string.
        int k = generateBits();
        int[] pos = new int[3];
        int nsteps = findEvenlySpacedOnes(k, pos);
        return new TestResult(k, nsteps); 
    }

    public static void main(String[] args)
    {
        int n;
        int ntrials;
        double pmin = 0, pmax = 1;
        try {
            n = Integer.parseInt(args[0]);
            ntrials = Integer.parseInt(args[1]);
            if (args.length >= 3)
                pmin = Double.parseDouble(args[2]);
            if (args.length >= 4)
                pmax = Double.parseDouble(args[3]);
        }
        catch (Exception e)
        {
            System.out.println("usage: EvenlySpacedOnesTest N NTRIALS [pmin [pmax]]");
            System.exit(0);
            return; // make the compiler happy
        }

        final StatisticalSummary[] statistics;
        statistics=new StatisticalSummary[n+1];
        for (int i = 0; i <= n; ++i)
        {
            statistics[i] = new StatisticalSummary();
        }

        EvenlySpacedOnesTest test = new EvenlySpacedOnesTest(n, ntrials, pmin, pmax);
        int printInterval=100000;
        int nextPrint = printInterval;
        for (int i = 0; i < ntrials; ++i)
        {
            TestResult result = test.run();
            statistics[result.k].add(result.nsteps);
            if (i == nextPrint)
            {
                System.err.println(i);
                nextPrint += printInterval;
            }
        }
        StatisticalSummary.printOut(System.out, statistics);
    }
}

0
# <algorithm>
def contains_evenly_spaced?(input)
  return false if input.size < 3
  one_indices = []
  input.each_with_index do |digit, index|
    next if digit == 0
    one_indices << index
  end
  return false if one_indices.size < 3
  previous_indexes = []
  one_indices.each do |index|
    if !previous_indexes.empty?
      previous_indexes.each do |previous_index|
        multiple = index - previous_index
        success_index = index + multiple
        return true if input[success_index] == 1
      end
    end
    previous_indexes << index
  end
  return false
end
# </algorithm>

def parse_input(input)
  input.chars.map { |c| c.to_i }
end

У мене виникають проблеми з найгіршими сценаріями з мільйонами цифр. Розпалювання від /dev/urandomфактично дає тобі O (n), але я знаю, що найгірший випадок гірший за це. Я просто не можу сказати, наскільки гірше. Для маленьких nнеприємно знайти вхідні дані навколо 3*n*log(n), але на диво важко відрізнити ці показники від іншого порядку зростання для цієї конкретної проблеми.

Чи може хтось, хто працював над найгіршими входами, створити рядок довжиною більше, ніж скажімо, сто тисяч?


Як я зазначив у своїй відповіді, легко генерувати погані (хоча і не гірші) рядки будь-якої кількості цифр: поставити 1s точно в ті позиції p, які не містять жодного "1" s у їхньому потрійному поданні (тобто в позиції 2, 6, 8, 18, 20, 24, 26, 54, 56, 60 ...: див. формули на research.att.com/~njas/sequences/…). Для 3 ^ 13 ≈ 1 мільйон, це має 2 ^ 13 ≈ 8000 1s. Час виконання таких рядків буде ≈ n ^ (1,26) - що може бути важко відрізнити від O (n log n) для таких малих n. Спробуйте і подивіться.
ShreevatsaR

-2

Адаптація алгоритму Рабіна-Карпа могла б бути можливою для вас. Його складність становить 0 (n), тому вона може вам допомогти.

Погляньте http://en.wikipedia.org/wiki/Rabin-Karp_string_search_algorithm


3
Для пошуку точних підрядів Рабін-Карп використовує хешування рядків, тому це не може допомогти у вирішенні проблеми.
Роберт Паркер

-3

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

Є місце для вдосконалення, другий знайдений 1 може бути наступним першим 1. Також немає перевірки помилок.

#include <iostream>

#include <string>

int findIt(std::string toCheck) {
    for (int i=0; i<toCheck.length(); i++) {
        if (toCheck[i]=='1') {
            std::cout << i << ": " << toCheck[i];
            for (int j = i+1; j<toCheck.length(); j++) {
                if (toCheck[j]=='1' && toCheck[(i+2*(j-i))] == '1') {
                    std::cout << ", " << j << ":" << toCheck[j] << ", " << (i+2*(j-i)) << ":" << toCheck[(i+2*(j-i))] << "    found" << std::endl;
                    return 0;
                }
            }
        }
    }
    return -1;
}

int main (int agrc, char* args[]) {
    std::string toCheck("1001011");
    findIt(toCheck);
    std::cin.get();
    return 0;
}

1
Технічно це O (n ^ 2). В середньому внутрішня петля буде ітератувати більше половини n кожного разу, коли вона виконується. Таким чином, це може бути записано як O (n * (n / 2)), і це можна спростити до O (n ^ 2)
Роберт Паркер

Гм, схоже, ти маєш рацію. Це не проста проблема, просто знайти всі 1 займає O (n), не так багато місця для подальшого пошуку / порівняння зі складністю O (logn).
DaClown

-3

Я думаю, що цей алгоритм має складність O (n log n) (C ++, DevStudio 2k5). Тепер я не знаю деталей, як аналізувати алгоритм для визначення його складності, тому я додав до коду деяку метрику, що збирає інформацію. Код підраховує кількість тестів, виконаних у послідовності 1 і 0 для будь-якого даного введення (сподіваюся, я не склав кулі алгоритму). Ми можемо порівняти фактичну кількість тестів зі значенням O і побачити, чи існує кореляція.

#include <iostream>
using namespace std;

bool HasEvenBits (string &sequence, int &num_compares)
{
  bool
    has_even_bits = false;

  num_compares = 0;

  for (unsigned i = 1 ; i <= (sequence.length () - 1) / 2 ; ++i)
  {
    for (unsigned j = 0 ; j < sequence.length () - 2 * i ; ++j)
    {
      ++num_compares;
      if (sequence [j] == '1' && sequence [j + i] == '1' && sequence [j + i * 2] == '1')
      {
        has_even_bits = true;
        // we could 'break' here, but I want to know the worst case scenario so keep going to the end
      }
    }
  }

  return has_even_bits;
}

int main ()
{
  int
    count;

  string
    input = "111";

  for (int i = 3 ; i < 32 ; ++i)
  {
    HasEvenBits (input, count);
    cout << i << ", " << count << endl;
    input += "0";
  }
}

Ця програма виводить кількість тестів на кожну довжину рядка до 32 символів. Ось результати:

 n  Tests  n log (n)
=====================
 3     1     1.43
 4     2     2.41
 5     4     3.49
 6     6     4.67
 7     9     5.92
 8    12     7.22
 9    16     8.59
10    20    10.00
11    25    11.46
12    30    12.95
13    36    14.48
14    42    16.05
15    49    17.64
16    56    19.27
17    64    20.92
18    72    22.59
19    81    24.30
20    90    26.02
21   100    27.77
22   110    29.53
23   121    31.32
24   132    33.13
25   144    34.95
26   156    36.79
27   169    38.65
28   182    40.52
29   196    42.41
30   210    44.31
31   225    46.23

Я також додав значення 'n log n'. Накресліть їх за допомогою обраного вами інструменту для графічного зображення, щоб побачити співвідношення двох результатів. Чи поширюється цей аналіз на всі значення n? Не знаю.


Я не погоджуюся. Однак крива ближче до n log n, ніж до n ^ 2.
Скізз

3
Спробуйте перекачати розмір введення на мільйон або більше. На малих входах крива часто схожа на криві алгоритмів, які, очевидно, кращі при накачуванні розміру вхідних даних.
Нік Ларсен

Подвійне для петлі з внутрішньою, обмеженою зовнішньою, створює трикутну форму, яка все ще є O (n ^ 2) за складністю. Подумайте про все (i, j) таким, що i в [0, n] і j в [0, n-2 * i] у вас є трикутник, а площа трикутника має квадратичну тенденцію.
Матьє М.

Якщо бути точним, Тести = (n ^ 2-2n) / 4 для рівних n; очевидно квадратичний.
Дідусь
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.