Найбільша сума, що ділиться на n


16

Я задав це питання на StackOverflow , але думаю, що тут є більш підходяще місце.

Це проблема від Введення в курс алгоритмів :

У вас є масив з позитивними цілими числами (масив не потрібно сортувати або елементи унікальні). Запропонуйте алгоритм , щоб знайти найбільшу суму елементів, що ділиться на .анО(н)н

Приклад: . Відповідь (з елементами )а=[6,1,13,4,9,8,25],н=7566,13,4,8,25

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

Крім того, якщо ми обмежимо увагу на суміжній послідовності елементів, легко знайти оптимальну таку послідовність за час, зберігаючи часткові суми за модулем : нехай , для кожного залишку запам'ятайте найбільший індекс такий, що , а потім для кожного ви вважаєте де - індекс, що відповідає .n S [ i ] = a [ 0 ] + a [ 1 ] + + a [ i ] r j S [ j ] rО(н)нS[i]=а[0]+а[1]++а[i]rji S [ j ] - S [ i ] j r = S [ i ] mod nS[j]r(модн)iS[j]S[i]jr=S[i]modn

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

Або можна це зробити за O(nlogn) час?


2
1. Ви точно опублікували те саме запитання на "Переповнення стека". Будь ласка , не ставте один і той же питання на кількох сайтах . Ми не хочемо, щоб кілька копій плавали навколо на декількох веб-сайтах SE. Якщо ви не отримали прийнятної відповіді, добре позначати своє запитання щодо міграції на інший сайт, але, будь ласка, не проставляйте те ж саме в іншому місці. 2. Чи можете ви дати посилання / цитування / посилання на підручник або курс, де це з’явилося? Наскільки ви впевнені, що існує рішення O(n) -time?
DW

5
Чи все ще є викликом у вашому університеті? Було б дуже корисно подивитися посилання на курс, точне запитання, і якщо це дійсно і люди, які його підготували, пояснять / опублікують свою відповідь, це було б дивним. O(n)
Зло

Порівняно легко знайти його в O (n2) O (n2), використовуючи динамічне програмування та зберігаючи найбільшу суму із залишками 0,1,2, ..., n − 10,1,2, ..., n − 1. Чи можете ви трохи розібратися в цьому? Я можу зрозуміти, як це було б n-квадратом, якщо ми розглянемо лише суміжні елементи, але і з непоміжними елементами, чи не було б це експоненціальним у порядку?
Nithish Inpursuit Ofhappiness

Відповіді:


4

Ось кілька випадкових ідей:

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

    Вартість буде якби ми обробили елементи. У цьому алгоритмі немає нижньої межі , оскільки нам не потрібно сортувати всі елементи. Для отримання найменших елементів потрібно лише час .O(nk)kΩ(nlogn)O(nlogk)k

  • Якщо ми піклувались про множину з великим розміром, замість множини з найбільшою сумою, ми можемо використати множення полінома на основі швидкого фур'є-перетворення для вирішення задачі в час. Аналогічно тому, що робиться в 3SUM, коли діапазон домену обмежений. (Примітка: використовуйте повторне квадратування для здійснення двійкового пошуку, інакше ви отримаєте де - кількість опущених елементів.)O(n(logn)2(loglogn))O(nk(logn)(loglogn))k

  • Коли є складовим і майже всі залишки є кратним одному з -х факторів, значне час може бути заощаджено, зосередившись на залишках, які не є кратними цьому фактору.nn

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

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

  • Алгоритм динамічного програмування дуже піддається виконанню паралельно. За допомогою процесора для кожного слота буфера ви можете перейти до . Альтернативно, використовуючи ширину та розділяючи та підкорюючи агрегацію замість ітеративного агрегації, ціна на глибину ланцюга може знизитись до .O ( n 2 ) O ( log 2 n )O(n)O(n2)O(log2n)

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


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

2
@EvilJS Підмножина з найбільшою сумою з залишком 0 дорівнює повній множині після вилучення підмножини з найменшою сумою з залишком, що відповідає сумі повного набору. Шукати найменшу суму, відповідну , зручніше, ніж шукати найбільшу суму, конгруентну до r 2, оскільки вона дозволяє припинити, як тільки ви знайдете рішення (при обробці елементів у порядку зростання), а не продовжувати. r1r2
Крейг Гідні

-1

Мій запропонований алгоритм виглядає так:

Сума ділиться на n, якщо ви додасте лише зведення, кратні n.

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

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

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

Після того, як ви закінчили циклічне перетворення масиву, ви обчислюєте вихід. Це ви робите, сортуючи кожен список у хешмапі відповідно до значення, на яке вказує індекс. Тепер ви розглядаєте кожну пару в хешмапі, підсумовуючи до n. Отже, якщо n = 7, ви шукаєте хешмап для 3 і 4. Якщо ви отримали запис в обох, ви берете два найбільші значення, видаліть їх зі своїх списків і додайте їх у свій список результатів.

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


2
Жадібний, лінійний, не працює. Ви розглядаєте лише елементи, які поділяються на n, і пари, що діляться на n, а як щодо трійки і більше? Це не гарантує максимальну суму підмножини у тривіальному випадку. [2, 1, 8] -> максимальна сума дорівнює 9, але ваш алгоритм повертає 3.
Зло

@EvilJS, що сталося з алгоритмом під- ? n2
дельта-термінатор

Дякую, що вказали на цю помилку. Моя ідея щодо вдосконалення полягала б у тому, щоб зробити хеш-карту стеків списків, яка впорядковується шляхом збільшення значення і починає накопичуватися лише після заповнення проходу через масив.
Tobias Würfl

Ви маєте на увазі масив масивів, який буде сортований, а "хешмап" -% n? Вам все одно потрібно сортувати їх, і якщо ви їх сортуєте, приймати мінімальне / максимальне значення нормально, але все ж є неминуча частина фактично вибору підмножини, що в гіршому випадку не приносить користі. У будь-якому випадку, якщо у вас є якісь вдосконалення, можливо, ви могли б редагувати публікацію?
Зло

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

-2

використовуйте цей метод DP від ​​( /programming/4487438/maximum-sum-of-non-consecutive-elements?rq=1 ):

Враховуючи масив A [0..n], нехай M (i) є оптимальним рішенням, використовуючи елементи з індексами 0..i. Тоді M (-1) = 0 (використовується в рецидиві), M (0) = A [0], і M (i) = max (M (i - 1), M (i - 2) + A [i ]) для i = 1, ..., n. M (n) - це рішення, яке ми хочемо. Це O (n) . Ви можете використовувати інший масив, щоб зберігати, який вибір робиться для кожної підпрограми, і таким чином відновити вибрані фактичні елементи.

Змініть рекурсію на M (i) = max (M (i - 1), M (i - 2) + A [i]) таким, що зберігається лише у тому випадку, якщо її ділиться на N


2
Це не працює - я дам вам зрозуміти, чому. (Підказка: Спробуйте запустити його на постійному масиві 1). Також у цій проблемі ми допускаємо послідовні елементи.
Yuval Filmus

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