Найдовша рівновіддалена підпослідовність


76

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

1, 4, 5, 7, 8, 12

має підпослідовність

   4,       8, 12

Мій наївний метод жадібний і просто перевіряє, наскільки ви можете розширити послідовність з кожної точки. O(n²)Здається, це вимагає часу на очко.

Чи є швидший спосіб вирішити цю проблему?

Оновлення. Я протестую код, вказаний у відповідях, якомога швидше (дякую). Однак уже зрозуміло, що використання пам'яті n ^ 2 не буде працювати. Поки що немає коду, який закінчується введенням як [random.randint(0,100000) for r in xrange(200000)].

Терміни. Я тестував із наступними вхідними даними для своєї 32-розрядної системи.

a= [random.randint(0,10000) for r in xrange(20000)] 
a.sort()
  • Метод динамічного програмування ZelluX використовує 1,6 ГБ оперативної пам'яті і займає 2 хвилини 14 секунд. Для pypy це займає всього 9 секунд! Однак він аварійно завершує роботу з помилкою пам'яті на великих входах.
  • Метод O (nd) часу Armin зайняв 9 секунд з pypy, але лише 20 МБ оперативної пам'яті. Звичайно, це було б набагато гірше, якби діапазон був набагато більшим. Низьке використання пам'яті означало, що я також міг перевірити його за допомогою = [random.randint (0,100000) для r у xrange (200000)], але він не закінчився за кілька хвилин, коли я дав йому pypy.

Для того, щоб мати можливість протестувати метод Клюєва, з яким я погодився

a= [random.randint(0,40000) for r in xrange(28000)] 
a = list(set(a))
a.sort()

скласти список довжини приблизно 20000. Всі таймінги з pypy

  • ZelluX, 9 секунд
  • Клюєв, 20 секунд
  • Армін, 52 секунди

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


3
Як швидко ВАС думаєш це зробити?
Mitch Wheat

44
Я не розумію всіх голосів проти, і особливо не голосуючих (не по темі, краще для Суперкористувача ? Серйозно? ) Це цікава проблема, на яку я теж хотів би бачити відповіді. Отже, +1 від мене.
Тім Піцкер

7
@ user2179021: Ви можете покращити своє запитання, включивши вже наявний код. Здається, це заспокоює деяких найбільш критичних користувачів SO. Наразі не турбуйтеся про голоси проти.
Тім Пітцкер

3
@TimPietzcker Я з вами тут, я бачив набагато гірші запитання, я вважаю, що тут добре
Роман Пекар

7
У прикладі, що вирішує, що 4, 8, 12це правильний результат, над 1, 4, 7яким є однаково довга послідовність?
FThompson

Відповіді:


11

Оновлення: Перший описаний тут алгоритм застарілий другою відповіддю Арміна Ріго , яка набагато простіша та ефективніша. Але обидва ці методи мають один недолік. Їм потрібно багато годин, щоб знайти результат для одного мільйона цілих чисел. Тож я спробував ще два варіанти (див. Другу половину цієї відповіді), де діапазон вхідних цілих чисел вважається обмеженим. Таке обмеження дозволяє набагато швидші алгоритми. Також я намагався оптимізувати код Armin Rigo. Подивіться мої результати порівняльного аналізу в кінці.


Ось ідея алгоритму, що використовує O (N) пам’ять. Складність часу становить O (N 2 log N), але може бути зменшена до O (N 2 ).

Алгоритм використовує такі структури даних:

  1. prev: масив індексів, що вказують на попередній елемент (можливо, неповний) підпослідовності.
  2. hash: хеш-карта з ключем = різниця між послідовними парами в послідовності та значення = дві інші хеш-карти. Для цих інших хеш-карт: ключ = початковий / кінцевий індекс підпослідовності, значення = пара (довжина підпослідовності, кінцевий / початковий індекс підпослідовності).
  3. pq: черга пріоритетів для всіх можливих значень "різниці" для послідовностей, що зберігаються в prevта hash.

Алгоритм:

  1. Ініціалізувати за prevдопомогою індексів i-1. Оновлення hashтаpq зареєструйте всі (неповні) підпослідовності, знайдені на цьому кроці, та їх "відмінності".
  2. Отримати (і видалити) найменшу "різницю" від pq. Отримайте відповідний запис hashта відскануйте одну з хеш-карт другого рівня. На цей час усі підпослідовності з заданою "різницею" завершені. Якщо хеш-карта другого рівня містить довжину підпослідовностей краще, ніж досі, оновіть найкращий результат.
  3. У масиві prev: для кожного елемента будь-якої послідовності, знайденої на кроці 2, індекс зменшення та оновлення hashта, можливо pq. Під час оновлення hashми могли виконати одну з наступних операцій: додати нову підпослідовність довжиною 1, або збільшити деяку існуючу підпослідовність на 1, або об’єднати дві існуючі підпослідовності.
  4. Видаліть запис хеш-карти, знайдений на кроці 2.
  5. Продовжуйте з кроку 2, поки pqне є порожнім.

Цей алгоритм оновлює O (N) елементів prevO (N) разів кожен. І для кожного з цих оновлень може знадобитися додати нову "різницю" pq. Все це означає часову складність O (N 2 log N), якщо ми використовуємо просту реалізацію купи для pq. Щоб зменшити його до O (N 2 ), ми могли б використовувати більш розширені реалізації черги пріоритетів. Деякі можливості перелічені на цій сторінці: Пріоритетні черги .

Дивіться відповідний код Python на Ideone . Цей код не допускає повторення елементів у списку. Це можна виправити, але в будь-якому випадку було б непоганою оптимізацією видалити дублікати (і окремо знайти найдовшу послідовність, крім дублікатів).

І той самий код після невеликої оптимізації . Тут пошук припиняється, як тільки довжина послідовності, помножена на можливу "різницю" підпослідовності, перевищує діапазон списку джерел.


Код Armin Rigo простий і досить ефективний. Але в деяких випадках він робить деякі додаткові обчислення, яких можна уникнути. Пошук може бути припинено, як тільки довжина послідовності, помножена на можливу "різницю" підпослідовності, перевищує діапазон списку джерел:

def findLESS(A):
  Aset = set(A)
  lmax = 2
  d = 1
  minStep = 0

  while (lmax - 1) * minStep <= A[-1] - A[0]:
    minStep = A[-1] - A[0] + 1
    for j, b in enumerate(A):
      if j+d < len(A):
        a = A[j+d]
        step = a - b
        minStep = min(minStep, step)
        if a + step in Aset and b - step not in Aset:
          c = a + step
          count = 3
          while c + step in Aset:
            c += step
            count += 1
          if count > lmax:
            lmax = count
    d += 1

  return lmax

print(findLESS([1, 4, 5, 7, 8, 12]))

Якщо діапазон цілих чисел у вихідних даних (M) невеликий, можливий простий алгоритм з O (M 2 ) часом та O (M) простором:

def findLESS(src):
  r = [False for i in range(src[-1]+1)]
  for x in src:
    r[x] = True

  d = 1
  best = 1

  while best * d < len(r):
    for s in range(d):
      l = 0

      for i in range(s, len(r), d):
        if r[i]:
          l += 1
          best = max(best, l)
        else:
          l = 0

    d += 1

  return best


print(findLESS([1, 4, 5, 7, 8, 12]))

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


Попередній алгоритм може бути вдосконалений, якщо замість масиву булевих символів ми використовуємо структуру даних бітового набору та побітові операції для паралельної обробки даних. Наведений нижче код реалізує бітсет як вбудоване ціле число Python. Він має однакові припущення: відсутність дублікатів, мінімальне вхідне значення невід’ємне та близьке до нуля. Складність часу - O (M 2 * log L), де L - довжина оптимальної підпослідовності, складність простору - O (M):

def findLESS(src):
  r = 0
  for x in src:
    r |= 1 << x

  d = 1
  best = 1

  while best * d < src[-1] + 1:
    c = best
    rr = r

    while c & (c-1):
      cc = c & -c
      rr &= rr >> (cc * d)
      c &= c-1

    while c != 1:
      c = c >> 1
      rr &= rr >> (c * d)

    rr &= rr >> d

    while rr:
      rr &= rr >> d
      best += 1

    d += 1

  return best

Тести:

Вхідні дані (близько 100000 цілих чисел) генеруються таким чином:

random.seed(42)
s = sorted(list(set([random.randint(0,200000) for r in xrange(140000)])))

А для найшвидших алгоритмів я також використовував такі дані (близько 1000000 цілих чисел):

s = sorted(list(set([random.randint(0,2000000) for r in xrange(1400000)])))

Усі результати показують час у секундах:

Size:                         100000   1000000
Second answer by Armin Rigo:     634         ?
By Armin Rigo, optimized:         64     >5000
O(M^2) algorithm:                 53      2940
O(M^2*L) algorithm:                7       711

2
Я повинен визнати, що я не розумію цього рішення. Чи можете ви надати код?
eleanora

@ user2179021: Не впевнений, що це допоможе. Це має бути досить багато коду. Нелегко писати. Надто складно зрозуміти. Я все ще сподіваюся, що можливо отримати ідею з цього короткого опису. Можливо, у вас є запитання щодо якогось конкретного кроку?
Євген Клюєв

Навіть крок 1 був би корисним. Що ми встановлюємо prev, next, hash і pq на першому кроці, використовуючи приклади 1, 4, 5, 7, 8, 12 у питанні?
елеанора

@ user2179021: prevє [Nil, 0,1,2,3,4]. nextстановить [1,2,3,4,5, Ніл]. pqміститиме всі сусідні різниці: 1,2,3,4. Кожна підпослідовність має довжину 1. Це значення разом із початковою / кінцевою позиціями зберігаються у hash: {1: ({1: 1,3: 1}, {2: 1,4: 1}), 2: ({2: 1 }, {3: 1}), 3: ({0: 1}, {1: 1}), 4: ({4: 1}, {5: 1})}.
Євген Клюєв

1
@EvgenyKluev Ваш підхід тут кращий щонайменше трьома способами :) 1. Ваше рішення паралельно розроблено, щоб зробити це дійсно швидким. 2. Складність часу зрозуміла і не залежить від розміру чисел. Я не на 100 відсотків впевнений, наскільки складним є час пропонованого рішення пов'язаного питання. 3. Якщо ви маєте якесь уявлення про ймовірний діапазон "d", ваші рішення ще швидші.
eleanora

19

Ми можемо отримати O(n*m)своєчасне рішення з дуже невеликими потребами пам’яті, адаптувавши ваше. Ось nкількість елементів у заданій вхідній послідовності чисел і mдіапазон, тобто найбільше число мінус найменше.

Викличте послідовність усіх вхідних чисел (і використовуйте заздалегідь обчислене, set()щоб відповісти у постійний час на запитання "це число в А?"). Назвіть d кроком підпослідовності, яку ми шукаємо (різниця між двома числами цієї підпослідовності). Для кожного можливого значення d виконайте наступне лінійне сканування всіх вхідних чисел: для кожного числа n від A у зростаючому порядку, якщо число ще не було видно, шукайте в A довжину послідовності, починаючи з n з a крок d. Потім позначте всі предмети в цій послідовності, як уже бачено, щоб ми не уникали повторного пошуку за ними, для того самого d. Через це складність якраз O(n)для кожного значення d.

A = [1, 4, 5, 7, 8, 12]    # in sorted order
Aset = set(A)

for d in range(1, 12):
    already_seen = set()
    for a in A:
        if a not in already_seen:
            b = a
            count = 1
            while b + d in Aset:
                b += d
                count += 1
                already_seen.add(b)
            print "found %d items in %d .. %d" % (count, a, b)
            # collect here the largest 'count'

Оновлення:

  • Це рішення може бути досить хорошим, якщо вас цікавлять лише значення d, які є відносно малими; наприклад, якщо отримання найкращого результату d <= 1000було б досить хорошим. Потім складність знижується до O(n*1000). Це робить алгоритм приблизним, але насправді придатним для роботи n=1000000. (Вимірюється 400-500 секунд за допомогою CPython, 80-90 секунд за допомогою PyPy, із випадковим підмножиною чисел від 0 до 10 000 000).

  • Якщо ви все-таки хочете шукати весь діапазон, і якщо загальний випадок - існування довгих послідовностей, помітним поліпшенням є зупинка, як тільки d занадто велике, щоб знайти ще довшу послідовність.


Це рішення може бути досить хорошим, якщо вас цікавлять лише значення d, які є відносно малими; наприклад, якщо отримання найкращого результату для d <= 1000 було б досить хорошим. Потім складність знижується до O(n*1000), що може тривати менше ніж за хвилину при n = 1000000 (спробуйте також PyPy).
Армін Ріго

Хороша ідея, якщо радіус дії дуже обмежений
RiaD

Але якщо d <= 1000ви можете просто видалити дублікати, у вас буде щонайбільше 1000 елементів і вирішити це в O (1000 ^ 2), що працюватиме максимум кілька секунд, я вірю.
RiaD

Ні, якщо А має один мільйон чисел, які знаходяться між 0 і 10'000'000, то m = 10'000'000. Але якщо ми обмежимося, d <= 1000ми шукаємо послідовності в цілому А, що мають не більше 1000 кроків.
Armin Rigo

Ах, я розумію, що ти маєш на увазі
RiaD

12

ОНОВЛЕННЯ: Я знайшов статтю з цієї проблеми, ви можете завантажити її тут .

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

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

lmax = 2
l = [[2 for i in xrange(n)] for j in xrange(n)]
for mid in xrange(n - 1):
    prev = mid - 1
    succ = mid + 1
    while (prev >= 0 and succ < n):
        if a[prev] + a[succ] < a[mid] * 2:
            succ += 1
        elif a[prev] + a[succ] > a[mid] * 2:
            prev -= 1
        else:
            l[mid][succ] = l[prev][mid] + 1
            lmax = max(lmax, l[mid][succ])
            prev -= 1
            succ += 1

print lmax

Це приємно і чисто. Чи можна змусити його працювати в лінійному просторі?
eleanora

@ user2179021 Рядок lmax = max (lmax, l [mid] [succ]) оновлює lmax, і якщо ви хочете отримати оптимальну послідовність, ви можете зберегти цю послідовність і тут.
ZelluX

Як ви думаєте, чи можна зробити лінійний простір?
eleanora

@ user2179021 Я не можу зрозуміти. Але я знайшов інше рішення, яке в деяких випадках працює швидше. Будь ласка, перегляньте посилання в оновленій відповіді.
ZelluX

Дякую. Однак я не впевнений, що метод у статті швидший на практиці. Я думаю, що простір - головне питання насправді (а також можливість використовувати знання про ймовірні розміри розриву, щоб пришвидшити процес).
eleanora

3

Алгоритм

  • Основний цикл, що обходить список
  • Якщо номер знайдений у списку попереднього обчислення, то він належить всім послідовностям, які є в цьому списку, перерахуйте всі послідовності з count + 1
  • Видаліть усі попередньо розраховані поточні елементи
  • Перерахувати нові послідовності, де перший елемент знаходиться в діапазоні від 0 до поточного, а другий - поточний елемент обходу (насправді, а не від 0 до поточного, ми можемо використовувати той факт, що новий елемент не повинен бути більшим за max (a) та new список повинен мати можливість стати довшим, ніж уже знайдений)

Тож для [1, 2, 4, 5, 7]виводу списку буде (це трохи брудно, спробуйте кодувати самі і дивіться)

  • індекс 0 , елемент 1 :
    • якщо 1в precalc? Ні - нічого не робити
    • Нічого не робити
  • індекс 1 , елемент 2 :
    • якщо 2в precalc? Ні - нічого не робити
    • перевірити, якщо 3 = 1+ ( 2- 1) * 2 у нашому наборі? Ні - нічого не робити
  • індекс 2 , елемент 4 :
    • якщо 4в precalc? Ні - нічого не робити
      • перевірити, чи 6 = 2+ ( 4- 2) * 2 у нашому наборі? Ні
      • перевірити, чи 7 = 1+ ( 4- 1) * 2 у нашому наборі? Так - додати новий елемент {7: {3: {'count': 2, 'start': 1}}} 7 - елемент списку, 3 - крок.
  • індекс 3 , елемент5 :
    • якщо 5в precalc? Ні - нічого не робити
      • не перевіряйте, 4оскільки 6 = 4 + ( 5- 4) * 2 менше, ніж розрахований елемент 7
      • перевірити, чи 8 = 2+ ( 5- 2) * 2 у нашому наборі? Ні
      • перевірити 10 = 2+ ( 5- 1) * 2 - більше, ніж max (a) == 7
  • індекс 4 , елемент 7:
    • якщо 7 в precalc? Так - покладіть результат
      • не перевіряйте, 5оскільки 9 = 5 + ( 7- 5) * 2 більше, ніж max (a) == 7

result = (3, {'count': 3, 'start': 1}) # крок 3, рахунок 3, початок 1, перетворіть його в послідовність

Складність

Він не повинен бути більше O (N ^ 2), і я думаю, що це менше через попереднє припинення пошуку нових послідовностей, я спробую надати детальний аналіз пізніше

Код

def add_precalc(precalc, start, step, count, res, N):
    if step == 0: return True
    if start + step * res[1]["count"] > N: return False

    x = start + step * count
    if x > N or x < 0: return False

    if precalc[x] is None: return True

    if step not in precalc[x]:
        precalc[x][step] = {"start":start, "count":count}

    return True

def work(a):
    precalc = [None] * (max(a) + 1)
    for x in a: precalc[x] = {}
    N, m = max(a), 0
    ind = {x:i for i, x in enumerate(a)}

    res = (0, {"start":0, "count":0})
    for i, x in enumerate(a):
        for el in precalc[x].iteritems():
            el[1]["count"] += 1
            if el[1]["count"] > res[1]["count"]: res = el
            add_precalc(precalc, el[1]["start"], el[0], el[1]["count"], res, N)
            t = el[1]["start"] + el[0] * el[1]["count"]
            if t in ind and ind[t] > m:
                m = ind[t]
        precalc[x] = None

        for y in a[i - m - 1::-1]:
            if not add_precalc(precalc, y, x - y, 2, res, N): break

    return [x * res[0] + res[1]["start"] for x in range(res[1]["count"])]

1
це пошук різниці, яка з'являлася більшість разів, так? він знайде багато одиниць у "1 2 5 6 100 101 1000 1001 1e5 1e5 + 2 1e5 + 4", тоді як найкраща відповідь - з diff = 2?
RiaD

насправді моє рішення не є ефективним для пам'яті, все одно змінивши його :)
Роман Пекар

Ти щось змінив? Я не помітив різниці
RiaD

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

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

3

Ось ще одна відповідь, яка працює вчасно O(n^2)і без будь-яких помітних вимог до пам’яті, окрім перетворення списку в набір.

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

Фокус у тому, щоб переконати себе, що цієї простої оптимізації достатньо, щоб знизити складність до O(n^2)початкової O(n^3). Це залишається як вправа для читача :-) Час тут конкурує з іншими O(n^2)рішеннями.

A = [1, 4, 5, 7, 8, 12]    # in sorted order
Aset = set(A)

lmax = 2
for j, b in enumerate(A):
    for i in range(j):
        a = A[i]
        step = b - a
        if b + step in Aset and a - step not in Aset:
            c = b + step
            count = 3
            while c + step in Aset:
                c += step
                count += 1
            #print "found %d items in %d .. %d" % (count, a, c)
            if count > lmax:
                lmax = count

print lmax

2

Ваше рішення O(N^3)зараз (ви сказали O(N^2) per index). Тут O(N^2)час і O(N^2)рішення рішення.

Ідея

Якщо ми знаємо , підпослідовності , яка проходить через індекси i[0], i[1], i[2], i[3]ми не повинні намагатися підпослідовність, починається з i[1]і i[2]або i[2]іi[3]

Примітка. Я відредагував цей код, щоб полегшити його використання, aале він не буде працювати для рівних елементів. Ви можете O(N)легко перевірити максимальну кількість рівних елементів

Псевдокод

Я шукаю лише максимальну довжину, але це нічого не змінює

whereInA = {}
for i in range(n):
   whereInA[a[i]] = i; // It doesn't matter which of same elements it points to

boolean usedPairs[n][n];

for i in range(n):
    for j in range(i + 1, n):
       if usedPair[i][j]:
          continue; // do not do anything. It was in one of prev sequences.

    usedPair[i][j] = true;

    //here quite stupid solution:
    diff = a[j] - a[i];
    if diff == 0:
       continue; // we can't work with that
    lastIndex = j
    currentLen = 2
    while whereInA contains index a[lastIndex] + diff :
        nextIndex = whereInA[a[lastIndex] + diff]
        usedPair[lastIndex][nextIndex] = true
        ++currentLen
        lastIndex = nextIndex

    // you may store all indicies here
    maxLen = max(maxLen, currentLen)

Думки про використання пам'яті

O(n^2)час дуже повільний для 1000000 елементів. Але якщо ви збираєтеся запускати цей код на такій кількості елементів, найбільшою проблемою буде використання пам'яті.
Що можна зробити для його зменшення?

  • Змініть булеві масиви на бітові поля, щоб зберігати більше логічних типів на біт.
  • Зробіть кожний наступний логічний масив коротшим, оскільки ми використовуємо лише usedPairs[i][j]ifi < j

Мало евристики:

  • Зберігайте лише пари використаних індикаторів. (Конфлікти з першою ідеєю)
  • Видаліть usedPair, які ніколи більше не використовуватимуться (тобто для таких i, jякі вже були обрані в циклі)

nстановить мільйон, тому boolean usedPairs[n][n]знадобиться принаймні один терабіт пам’яті.
Michael Butscher

О, не помітив точних обмежень і не думав про пам'ять. (до речі це може бути поділено на 2, тому що ми використовуємо лише usedPairs[i][j]для i <j)
RiaD

@MichaelButscher, будь-який спосіб O(n^2)для номера 1000000 дуже повільний.
RiaD

Так, і я відчуваю, що це не можна зробити швидше (але не можу це довести).
Michael Butscher

2
@MichaelButscher, можливо, ми якось можемо використати, що це відсортовано.
RiaD

1

Це мої 2 центи.

Якщо у вас є список із назвою введення:

input = [1, 4, 5, 7, 8, 12]

Ви можете побудувати структуру даних, яка для кожної з цих точок (крім першої) покаже вам, наскільки віддалена ця точка від будь-кого з її попередників:

[1, 4, 5, 7, 8, 12]
 x  3  4  6  7  11   # distance from point i to point 0
 x  x  1  3  4   8   # distance from point i to point 1
 x  x  x  2  3   7   # distance from point i to point 2
 x  x  x  x  1   5   # distance from point i to point 3
 x  x  x  x  x   4   # distance from point i to point 4

Тепер, коли у вас є стовпці, ви можете розглянути i-thелемент введення (який є input[i]) та кожне число nв його стовпці.

Числа, що належать до ряду рівновіддалених чисел, які включають input[i], - це ті, що містяться n * jвi-th позиції стовпця, де j- кількість знайдених збігів при переміщенні стовпців зліва направо, плюс k-thпопередник input[i], де k- індекс nу колонціinput[i] .

Приклад: якщо ми вважаємо i = 1, input[i] = 4, n = 3, то ми можемо визначити послідовність осягаючи 4( input[i]),7 (тому що він має 3в положенні 1своєї колонки) і 1, так як kце 0, тому ми беремо перший попередник i.

Можлива реалізація (вибачте, якщо код не використовує ті самі позначення, що і пояснення):

def build_columns(l):
    columns = {}
    for x in l[1:]:
        col = []
        for y in l[:l.index(x)]:
            col.append(x - y)
        columns[x] = col
    return columns

def algo(input, columns):
    seqs = []
    for index1, number in enumerate(input[1:]):
        index1 += 1 #first item was sliced
        for index2, distance in enumerate(columns[number]):
            seq = []
            seq.append(input[index2]) # k-th pred
            seq.append(number)
            matches = 1
            for successor in input[index1 + 1 :]:
                column = columns[successor]
                if column[index1] == distance * matches:
                    matches += 1
                    seq.append(successor)
            if (len(seq) > 2):
                seqs.append(seq)
    return seqs

Найдовший:

print max(sequences, key=len)

Ваш алгоритм працював 2 секунди на 300 балів, мій алгоритм працював 0,03 секунди. Я пробував твій на 5000, але це було занадто довго :( я пропрацював 18 секунд на 5000, тож ми все ще не могли зробити це за 1000000 швидко
Роман Пекар

так, але, можливо, ми пропускаємо якийсь момент і це можливо зробити лінійно :) або принаймні NlogN ....
Роман Пекар

0

Перейти масив, ведучи запис оптимального результату / результатів та таблицю з

(1) індексом - різницею елементів у послідовності,
(2) count - кількістю елементів у послідовності на даний момент, і
(3) останнім записаним елемент.

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

Скануючи назад, ми можемо зупинити сканування, коли d більше середини діапазону масиву; або коли поточний max більший за довжину можливої ​​послідовності, для d більший за найбільшу індексовану різницю. Послідовності деs[j] більше, ніж останній елемент у послідовності, видаляються.

Я перетворив свій код з JavaScript на Python (мій перший код python):

import random
import timeit
import sys

#s = [1,4,5,7,8,12]
#s = [2, 6, 7, 10, 13, 14, 17, 18, 21, 22, 23, 25, 28, 32, 39, 40, 41, 44, 45, 46, 49, 50, 51, 52, 53, 63, 66, 67, 68, 69, 71, 72, 74, 75, 76, 79, 80, 82, 86, 95, 97, 101, 110, 111, 112, 114, 115, 120, 124, 125, 129, 131, 132, 136, 137, 138, 139, 140, 144, 145, 147, 151, 153, 157, 159, 161, 163, 165, 169, 172, 173, 175, 178, 179, 182, 185, 186, 188, 195]
#s = [0, 6, 7, 10, 11, 12, 16, 18, 19]

m = [random.randint(1,40000) for r in xrange(20000)]
s = list(set(m))
s.sort()

lenS = len(s)
halfRange = (s[lenS-1] - s[0]) // 2

while s[lenS-1] - s[lenS-2] > halfRange:
    s.pop()
    lenS -= 1
    halfRange = (s[lenS-1] - s[0]) // 2

while s[1] - s[0] > halfRange:
    s.pop(0)
    lenS -=1
    halfRange = (s[lenS-1] - s[0]) // 2

n = lenS

largest = (s[n-1] - s[0]) // 2
#largest = 1000 #set the maximum size of d searched

maxS = s[n-1]
maxD = 0
maxSeq = 0
hCount = [None]*(largest + 1)
hLast = [None]*(largest + 1)
best = {}

start = timeit.default_timer()

for i in range(1,n):

    sys.stdout.write(repr(i)+"\r")

    for j in range(i-1,-1,-1):
        d = s[i] - s[j]
        numLeft = n - i
        if d != 0:
            maxPossible = (maxS - s[i]) // d + 2
        else:
            maxPossible = numLeft + 2
        ok = numLeft + 2 > maxSeq and maxPossible > maxSeq

        if d > largest or (d > maxD and not ok):
            break

        if hLast[d] != None:
            found = False
            for k in range (len(hLast[d])-1,-1,-1):
                tmpLast = hLast[d][k]
                if tmpLast == j:
                    found = True
                    hLast[d][k] = i
                    hCount[d][k] += 1
                    tmpCount = hCount[d][k]
                    if tmpCount > maxSeq:
                        maxSeq = tmpCount
                        best = {'len': tmpCount, 'd': d, 'last': i}
                elif s[tmpLast] < s[j]:
                    del hLast[d][k]
                    del hCount[d][k]
            if not found and ok:
                hLast[d].append(i)
                hCount[d].append(2)
        elif ok:
            if d > maxD: 
                maxD = d
            hLast[d] = [i]
            hCount[d] = [2]


end = timeit.default_timer()
seconds = (end - start)

#print (hCount)
#print (hLast)
print(best)
print(seconds)

тож вам все ще потрібна пам’ять O (N ^ 2)
Роман Пекар

@RomanPekar Дякую за ваш коментар. А як щодо додаткової оптимізації, яка достроково завершує сканування назад?
גלעד ברקן

@RomanPeckar ні, просто думаючи про алгоритм; також я не знаю Python.
גלעד ברקן

@RomanPeckar додав приклад jsfiddle до моєї відповіді. jsfiddle.net/groovy/b6zkR
גלעד ברקן

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

0

Це окремий випадок для більш загальної проблеми, описаної тут: Відкрийте довгі закономірності, де K = 1 і фіксовано. Там демонструється, що це можна вирішити за допомогою O (N ^ 2). Запустивши мою реалізацію запропонованого там алгоритму C, потрібно 3 секунди, щоб знайти рішення для N = 20000 та M = 28000 у моїй 32-бітовій машині.


0

Жадібний метод
1. Генерується лише одна послідовність рішення.
2. Утворюється велика кількість рішень. Динамічне програмування 1. Це не гарантує надання оптимального рішення завжди.
2. Це, безумовно, дає оптимальне рішення.

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