Чи існує алгоритм, який знаходить відсортовані підряди розміром три за час?


21

Я хочу довести або спростувати існування алгоритму, який, маючи масив цілих чисел, знаходить три індекси і такі, що і (або виявляє, що такої потрійності немає) у лінійному часі.i , j k i < j < k A [ i ] < A [ j ] < A [ k ]Ai,jki<j<kA[i]<A[j]<A[k]

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

Я хотів би це довести формально. Як ти це робиш? Я б в ідеалі хотів, щоб доказ був викладений поетапно, а потім, якщо ви настільки схильні, дещо пояснення, як слід доводити / спростовувати прості подібні питання в цілому. Якщо це допомагає, кілька прикладів:

[1,5,2,0,3] → (1,2,3)
[5,6,1,2,3] → (1,2,3)
[1,5,2,3] → (1,2,3)
[5,6,1,2,7] → (1,2,7)
[5,6,1,2,7,8] → (1,2,7)
[1,2,999,3] → (1,2,999)
[999,1,2,3] → (1,2,3)
[11,12,8,9,5,6,3,4,1,2,3] → (1,2,3)
[1,5,2,0,-5,-2,-1] → (-5,-2,-1)

Я припускав, що можна перейти через , і кожного разу, коли з'явиться (наш поточний , тобто), ми робимо нову трійку і натискаємо її на масив. Ми продовжуємо крокувати і порівнювати кожну трійку до тих пір, поки одна з наших трійки не буде завершена. Так як , ! Але я думаю, що це складніше, ніж просто оскільки кількість трійки на нашому потрійному масиві в гіршому випадку відповідатиме розміру вхідного списку.i < j j O ( n )Ai<jj[1,5,2,0,-5,-2,-1] → 1..2.. -5.. -2.. -1[1,5,2,0,-5,-2,3,-1] → 1..2.. -5.. -2.. 3O(n)



Зауважте, що в гіршому випадку (відсортований масив) у вас є навіть багато підходящих трійки. Будь ласка, розглянути можливість надання алгоритму, який ви пропонуєте, як псевдокод; Я думаю, що ваше пояснення не є повним. Θ(n3)
Рафаель

Відповіді:


14

Це варіація найдовшої проблеми, що збільшується в підпорядкуванні ; це рішення, представлене у Вікіпедії за допомогою двох допоміжних масивів і :ПMP

  • k A [ k ] j A [ k ] k i j k i j k 13 11 k iM[j] - зберігає положення найменшого значення таким чином, що в діапазоні збільшується послідовність довжини закінчується на (зауважимо, у нас тут, тому що представляє довжину зростаючої підпослідовності, і представляє положення його закінчення. Очевидно, що ми ніколи не можемо мати зростаючу підпослідовність довжиною , що закінчується в положенні . за визначенням).kA[k]jA[k]kijkijk1311ki
  • А [ к ] А [ к ]P[k] - зберігає положення попередника в найдовшій збільшувальній послідовності, що закінчується на .A[k]A[k]

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

Цей алгоритм працює в гіршому випадку . Ваша проблема - це особливий випадок, який дозволяє повернутися, коли який виштовхує час виконання вниз до тому що двійковий пошук працює лише на масивах довжиною не більше двох, що, таким чином, знаходиться в часі на відміну від у загальному випадку.L = 3 O ( n ) O ( 1 ) Θ ( log n )Θ(nlogn)L=3O(n)O(1)Θ(logn)

Розглянемо модифікований псевдо код:

 L = 0
 for i = 1, 2, ... n:
    binary search for the largest positive j ≤ L
      such that X[M[j]] < X[i] (or set j = 0 if no such value exists)
    P[i] = M[j]
    if j == L or X[i] < X[M[j+1]]:
       M[j+1] = i
       L = max(L, j+1)
   if L==3 : return true; // you can break here, and return true.
return false; // because L is smaller than 3.

@SaeedAmiri Я побачив коментар, але ще не встиг його переглянути (я поставив це питання перед сном). З вашого посилання я підозрював, що наш особливий випадок L = 3 якось допоможе, але не мав можливості зрозуміти деталі. Зараз я на роботі та обмежений час. Будьте впевнені, що я ціную вашу відповідь. Мені було б поверхово дякувати за це, не розуміючи повністю кожного рядка в ньому.
Крістофер зробив

@SaeedAmiri: Я погоджуюся, що ви очікуєте більше «заповнення прогалин», але ви все одно повинні навести найважливіші аргументи доказу (хоч і ескізні). Щодо ОП, він, здається, базується в Італії, тому, ймовірно, швидко заснув між вашим коментарем та відповіддю (і, швидше за все, зараз він зайнятий східним).
Рафаель

@ChristopherDone, я не хочу вас засмучувати, вибачте, це моя помилка, ви точно маєте рацію.

+1: Це добре узагальнює, робить лише один прохід і є пробілом. O(1)
Ар'ябхата

Гаразд, це добре виглядає. Мені знадобилося певний час, щоб зрозуміти поведінку загального алгоритму послідовності, що зростає довше. Після цього нормальна довжина == 3 зміни в порядку. Спасибі!
Крістофер зробив

11

Примітка про методологію

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

Двоелементна версія

Почнемо з малого: замість того, щоб шукати три індекси, за якими елементи знаходяться в порядку, давайте шукатимемо два: такий, що .A [ i ] < A [ j ]i<jA[i]<A[j]

Якщо зменшується (тобто , або рівнозначно ), таких показників немає. В іншому випадку є індекс такий, що .Ai<j,A[i]A[j]i,A[i]A[i+1]iA[i]<A[i+1]

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

Складання алгоритму

Я буду використовувати термін підпослідовність означає виписку з масиву , що складається з індексів , які не можуть бути послідовними ( з ), і працювати , щоб середнє послідовні елементи ( ).A(A[i1],,A[im])i1<<imA(A[i],A[i+1],,A[i+m1])

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

4,3,2,1,0

За допомогою двох індексів ми могли б знайти послідовні індекси. Маючи три індекси, ми можемо не придумати і . Однак ми можемо розглянути той випадок, коли має бути вирішено пробіг трьох строго зростаючих елементів ( , оскільки легко розпізнати такі прогони, і подивіться, як ця умова не може бути виконана. Припустимо, що послідовність не має строго зростаючого пробігу довжиною 3.j=i+1k=j+1A[i]<A[i+1]<A[i+2]

4,3,2,1,2,3,2,1,0

Послідовність має лише строго зростаючі прогони довжиною 2 (які я називатиму впорядкованими парами для коротких), розділених зменшенням пробігу довжини щонайменше 2. Для того, щоб строго збільшувати пробіг щоб бути частиною зростаючої 3-елементної послідовності, повинен бути більш ранній елемент такий, що або пізніший елемент такий, що .A[j]<A[j+1]iA[i]<A[j]kA[j+1]<A[k]

4,3,2,2,5,1,5,0,5,1,0

Випадок, коли ні ні існує, коли кожна впорядкована пара повністю нижча за наступну. Це ще не все: коли пари переплітаються, нам потрібно порівняти їх більш тонко.ik

3,2,1,3,5,2,5,1,5,0,5, -0,5,1,25, -0,25 3,2,1,2,5,1,5,0,5,2,1,0

Крайній лівий елемент зростаючої послідовності повинен прийти рано і бути малим. Наступний елемент повинен бути більшим, але якомога меншим, щоб можна було знайти третій більший елемент . Перший елемент не завжди є найменшим елементом у послідовності, і він не завжди є першим, для якого є наступний більший елемент - іноді далі є нижчий 2-елементний подальший запас, а іноді і кращий підходить для вже знайденого мінімуму.ijki

2.1,3,2,1,2,5,1,5,0,5,2,1,0 1,2,0,2,5,1,5,0,5

Переходячи зліва направо, ми орієнтовно вибираємо найменший елемент як . Якщо ми знайдемо більший елемент далі праворуч, виберемо цю пару як орієнтовну . Якщо ми знайдемо ще більший , ми виграємо. Ключове, що слід зазначити, - це те, що наш вибір та вибір оновлюються незалежно: якщо у нас є кандидат і ми знаходимо таким, що , стає наступним кандидатом але залишається. Тільки якщо ми знайдемо таким, що будеi(i,j)ki(i,j)(i,j)i>jA[i]<A[i]ii(i,j)jA[j]<A[j](i,j) стати новою кандидатською парою.

Заява алгоритму

Дано в синтаксисі Python, але будьте уважні, що я його не перевіряв.

def subsequence3(A):
    """Return the indices of a subsequence of length 3, or None if there is none."""
    index1 = None; value1 = None
    index2 = None; value2 = None
    for i in range(0,len(A)):
        if index1 == None or A[i] < value1:
            index1 = i; value1 = A[i]
        else if A[i] == value1: pass
        else if index2 == None:
            index2 = (index1, i); value2 = (value1, A[i])
        else if A[i] < value2[1]:
            index2[1] = i; value2[1] = A[i]
        else if A[i] > value2[1]:
            return (index2[0], index2[1], i)
    return None

Ескіз доказу

index1- індекс мінімальної частини масиву, який вже пройшов (якщо він трапляється кілька разів, ми зберігаємо перше виникнення) або Noneперед обробкою першого елемента. index2зберігає індекси зростаючої підрядності довжини 2 у вже пройденій частині масиву, що має найнижчий найбільший елемент, або Noneякщо такої послідовності не існує.

Коли return (index2[0], index2[1], i)працює, ми маємо value2[0] < value[1](це інваріант value2) та value[1] < A[i](очевидно з контексту). Якщо цикл закінчується без виклику раннього повернення, або value1 == Noneв цьому випадку не збільшується послідовність довжини 2, не кажучи вже про 3, або value1містить збільшується послідовність довжини 2, яка має найменший найбільший елемент. В останньому випадку ми, крім того, маємо інваріант, що жодна збільшувана послідовність довжини 3 не закінчується раніше, ніж value1; тому останній елемент будь-якої такої підпорядкованості, доданий до value2, формував би зростаючу послідовність довжиною 3: оскільки ми також маємо інваріант, який value2не є частиною зростаючої послідовності довжини 3, що міститься у вже пройденій частині масиву, немає такої підпорядкованості у всьому масиві.

Доведення вищезгаданих інваріантів залишається вправою для читача.

Складність

Ми використовуємо додаткову пам'ять і прокладаємо масив як потік зліва направо. Ми виконуємо обробку для кожного елемента, що призводить до часу виконання .O ( 1 ) O ( n )O(1)O(1)O(n)

Офіційне підтвердження

Залишене як вправу для читача.


8

O(n)O(n)

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

1

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

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

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

1

O(n)

ki

Псевдокод першого проходу може виглядати приблизно так:

Stack <Pair<Elem, Index>> greats;
Elem auxArr[inputArr.Length];

for (Index i = 0; i < inputArr.Length; i++) {

    while (!greats.IsEmpty() && inputArr[i] > greats.PeekTop().Elem) {
        Pair top = greats.Pop();
        auxArr[top.Index] = i;
    }

    Pair p;
    p.Elem = inputArr[i];
    p.Index = i;

    greats.Push(p);
}

"Оскільки ви розглядаєте кожен елемент масиву лише постійну кількість разів, це час O (n)". Я якось виключав кілька постійних проходів, відкидаючи це як O (n). Дуже дурний. Я вдячний за ваше пояснення, і я ще раз спробую це вирішити.
Крістофер зробив
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.