Як можна знайти всі незбалансовані паролі в рядку за лінійним часом з постійною пам'яттю?


11

Під час інтерв'ю мені дали наступну проблему:

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

Наприклад, у рядку ") (ab))" індекси 0 і 5 містять паролі, які не мають відповідних батьків.

Я висунув робоче рішення O (n), використовуючи O (n) пам'ять, використовуючи стек і переходячи через рядок, один раз додаючи парочки до стеку та видаляючи їх із стеку, коли я стикався з закриваючим паролем, а верхня частина стека містилася відкриття батьків.

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

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

Хтось може уточнити запропоноване нею рішення?


1
Можливо, спочатку вам знадобиться роз’яснення. Чи вважається перша парона або друга парона в "(()" неврівноваженою? Чи остання парона або друга за останніми паролями в "())" вважається незбалансованою? Або достатньо ідентифікувати будь-який набір паролів з найменшою кардинальністю, щоб вилучення їх залишило залишок паронів збалансованим? Або щось інше? Або це частина інтерв'ю, щоб відповідь могла просто викласти будь-яку виправдану специфікацію?
Джон Л.

Я б сказав, це не має значення, ви самі. Видаліть будь-який набір, який залишить залишок збалансованим.
тимчасове_користувач_ім'я

5
Потім видаліть їх усіх; P
Ведрак

@Veedrac, звичайно (як відомо) плакат забув слово "мінімальний" у "Видалити будь-який мінімальний набір ...".
LSpice

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

Відповіді:


17

Оскільки це відбувається з програм програмування, а не з теоретичних вправ з інформатики, я припускаю, що для збереження індексу в рядку потрібно пам'ять . У теоретичній інформатиці це означатиме використання моделі ОЗУ; з машинами Тьюрінга ви цього не могли зробити, і вам знадобиться пам'ять для зберігання індексу в рядок довжиною .O(1)Θ(log(n))n

Ви можете дотримуватися основного принципу використовуваного алгоритму. Ви пропустили можливість оптимізації пам’яті.

використовуючи стек і переходячи через рядок, один раз додаючи парочки до стеку та видаляючи їх із стеку, коли я стикався з закриваючим паролем, а верхня частина стека містила відкриваючий батьків

Отже, що містить цей стек? Він ніколи не буде містити ()(вхідні дужки, за якими слідує дужка, що закривається), оскільки кожного разу, коли )ви з'являєтеся, ви (натискаєте, а не натискаєте ). Таким чином, стек завжди має форму )…)(…(- купу закритих дужок, а потім купу вхідних дужок.

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

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

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

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

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

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

Вправа 2: переконайте себе, що це той самий алгоритм, що і Apass.Jack , просто пояснений по-іншому.


О, дуже хороший Жилле, дуже добре пояснений. Я зараз прекрасно розумію. Минуло вже досить багато років, як я отримав від вас відповідь на одне із моїх запитань.
тимчасове_користувач

"Якщо ви хочете повідомити про положення невідповідних дужок в кінці, вам потрібно буде запам'ятати положення кожної дужки." Не зовсім. Лінійний час не означає одиночний прохід. Ви можете зробити другий прохід, щоб знайти будь-які дужки у невідповідній стороні та позначити їх.
Mooing Duck

Для останнього кроку вам не потрібно запускати його у зворотному порядку, ви можете просто позначити останній N "(" як невідповідність.
Mooing Duck

1
@MooingDuck Це не працює. Напр (().
orlp

Хоча мені дуже подобається ця відповідь, щось мене постійно турбує. Щось таке "Мені якось потрібно запам’ятати позицію. Я думаю, що у мене з цим питання: як" вивести поточний індекс ", не споживаючи пам'яті (або цілком конкретний контекст, коли ваші результати споживаються таким чином, що порядок без ваших результатів не має значення)
Едуард

8

Оскільки ми можемо просто ігнорувати всі буквено-цифрові символи, ми будемо вважати, що рядок містить лише круглі дужки відтепер. Як і в питанні, існує лише один вид дужок, "()".

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

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


Нехай strбуде рядок як масив символів, розмір яких .n

Ініціалізуйте turning_point=0, maximum_count=0, count=0. Для кожного iз них 0потрібно n-1зробити наступне.

  1. Якщо str[i] = ')'додати 1 до count; в іншому випадку віднімаємо 1.
  2. Якщо count > maximum_count, встановіть turning_point=iі maximum_count=count.

Тепер turning_pointіндекс перелому.

Скидання maximum_count=0, count=0. Для кожного iз них 0потрібно turning_pointзробити наступне.

  1. Якщо str[i] = ')'додати 1 до count; в іншому випадку віднімаємо 1.
  2. Якщо count > maximum_count, встановіть maximum_count = count. Виведення iяк показник незбалансованої дужки, що закривається.

Скидання maximum_count=0, count=0. Для кожного iз n-1до turning_point+1вниз зробіть наступне.

  1. Якщо str[j] = '('додати 1 до count; в іншому випадку віднімаємо 1.
  2. Якщо count > maximum_count, встановіть maximum_count = count. Виведення iяк показник незбалансованої дужки, що відкривається.

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


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

Ось код у Python .

Просто натисніть "пробіг", щоб побачити кілька результатів тесту.


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

Проблема 1. Чи можемо ми узагальнити алгоритм до випадку, коли рядок містить два типи дужок, наприклад "() []"? Ми маємо визначити, як розпізнати та поводитися з новою ситуацією, випадком переплетення, "([)]".


Лол, вправа 1 і задача 1, мило. Логіку описаного вами алгоритму напрочуд складно уявити. Мені доведеться кодувати це завтра, щоб отримати його.
тимчасовий_користувач

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

Виявлення незбалансованих дужок, що відкриваються, невірно. Тобто, якщо ваша arr "())", p дорівнює 2, а p + 1 виходить за межі arr. Просто ідея - щоб знайти незбалансовані дужки, що відкриваються, ви могли б змінити arr і використовувати частину алгоритму для пошуку незбалансованих дужок, що закриваються (звичайно, із зворотно адаптованими індексами).
OzrenTkalcecKrznaric

@OzrenTkalcecKrznaric Саме тому, що виходить за межі, в "())" немає врівноважених дужок, що відкриваються. p+1
Джон Л.

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