Пары строк в обратном порядке в списке из более чем миллиона строк?

#string #reverse

#строка #обратный

Вопрос:

Недавно во время какого-то интервью спросили: «Как найти обратное всех строк, если существует в списке из более чем миллиона строк?

Например, для str[1] = «abc» мне нужно точно проверить наличие «cba», никаких анаграмм.

Способ 1. Сохраните все строки в hashset, начните обход с первой строки и проверьте, существует ли в Hashset перевернутая форма. если да, то пара else переходит к следующему элементу.

Можете ли вы предложить какой-либо метод, если ограничением является память?

Комментарии:

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

2. Хотя я согласен с Дэниелом в этом, рассматривая ПАМЯТЬ как ограничение, это не имело бы никакого значения.

3. @DanielRHicks Я отредактировал свой вопрос…. он имел в виду, что для всех строк в списке найдите, существует ли обратная ему…

Ответ №1:

Если разрешено, вы можете отсортировать строки на месте, чтобы при поиске обратной строки вы могли выполнить двоичный поиск.

Ответ №2:

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

Ровно 1 000 000 бит == 125 КБ

Комментарии:

1. 1.) это займет больше памяти. 2) вам не нужна длинная строка, чтобы получить много строк одинаковой длины.

Ответ №3:

Вы можете выбрать хэш-таблицу и использовать сегменты, чтобы уменьшить конфликт хэшей. Что нам теперь нужно сделать для конкретной строки запроса, это просто перевернуть ее, хэшировать и найти в хэш-таблице вместо того, чтобы проходить от начала до конца.

Комментарии:

1. Да, это по сути то же самое, что и моя схема, только с вдвое большим количеством хэшей.

Ответ №4:

Это просто мое мнение:

Я бы создал хэш с

ключ = символ

значение = Список строк, которые начинаются с этого символа

  • Теперь запустите цикл, внутри которого вам нужно начать с первой строки.
  • переверните его
  • Возьмите первый символ и найдите этот ключ в хэше
  • затем в значении этого он содержит список строк и находит строку в этом списке

Ответ №5:

С «памятью как ограничением» я бы даже не стал использовать HashSet (который, afaik, также удалит дублированные строки в исходном списке), потому что вы будете использовать дополнительную структуру HashSet, которая занимает некоторое количество памяти.

Сортировка также не улучшит использование памяти.

Я бы использовал исходный список (который уже есть, поэтому дополнительная память не будет использоваться) 3-байтовую целочисленную переменную для итерации списка. 3 байта могут выполнять итерацию по списку из 2 ^ 24 = 16777216 строк

С «памятью как ограничением» я бы выбрал 2 цикла for. Я думаю, что псевдокод, подобный C, будет легче понять, что мой простой английский.

Примечания:

  1. Из примера, приведенного в вопросе, на самом деле это не список, а массив, поэтому я буду работать со структурой, как если бы это был массив
  2. Вопрос не ясен, как связать эти «abc», «def»,»cba»,»abc». Я буду соединять первый «abc» с «cba», а также этот «cba» со «вторым «abc» (намерение неясно в вопросе)
  3. Я предполагаю, что мы не можем изменить исходный список

Вот код с наименьшим потреблением памяти, который я могу придумать:

 // "list" holds the original list (array)
for (int i = 0; i < length(list) - 1; i  ) {
    for (int j = i   1; j < length(list); j  ) {
        if (list[i] == reverse(list[j])) {
            print(list[i]   " reversed is " list[j])
        }
    }
}
  

Что касается использования памяти, это решение будет принимать 2 целочисленные переменные (обычно по 4 байта каждая) исходный список, от которого, я полагаю, мы не можем избавиться.

Что касается использования процессора (на самом деле, не имеет значения, исходя из вопроса), количество раз, когда строки будут отменены, будет: (N * (N 1)) / 2, где N — длина списка

Комментарии:

1. 1,000,000,000,000 итераций, больше или меньше. (Не считая фактического цикла сравнения.)

2. Хм, нет. Всего 1 итерация по списку. Порядок этого решения — N. Но, как я уже сказал, и человек, который спросил, четко указал, нет необходимости делать это быстро, но с наименьшим объемом памяти. Список уже есть, я просто добавляю 3 байта. Сколько дополнительных байтов занимает ваше решение?

3. Поэтому, пожалуйста, объясните, как за один проход по списку вы идентифицируете все обратные дубликаты в списке.

4. Дэниел, твой последний комментарий заставил меня понять первоначальный вопрос. Я подумал, что, как и в примере в вопросе, у вас уже была строка, и идея заключалась в том, чтобы проверить, содержится ли в списке обратная форма этой (единственной) строки. Абсолютно моя ошибка. Удаление моего ответа.

Ответ №6:

Сначала я бы хэшировал строки, используя хэш, который не зависит от направления. Это может быть простая сумма символов, хотя, безусловно, существуют лучшие схемы, которые будут хэшировать с обоих концов. И чтобы «подсластить сделку», можно добавить длину строки к хэш-значению или иным образом включить ее в хэш.

Затем, когда строки будут разбиты на идентичные хэш-группы, выполните сравнение «длинной руки».

Обратите внимание, что, используя эту схему или ту, где вы просто используете хэш, зависящий от направления, вперед или назад, нужно не сразу вставлять строку в набор хэшей, а сначала проверить ее (с обратным хэшем, если необходимо), и если вы получите совпадение (ипоследующее длинное сравнение истинно) удалите уже хэшированную строку и соедините две. Вторая строка никогда не попадает в набор, и, если все строки имеют совпадения максимум, у вас будет только 500 000 записей в хэш-наборе, и, если строки были случайными, вероятно, ближе к 250 000 (я не садился, чтобы вычислить вероятности).

Таким образом, вам потребуется только один проход через набор строк, чтобы сделать все это.

Комментарии:

1. использование хэш-значения, не зависящего от направления, не дает вам никаких реальных преимуществ, но, безусловно, увеличит коэффициент коллизий.

2. Независимый от направления хэш хэширует «abc» и «cba» в одну корзину. Это значительно сокращает количество комбинаций, которые вам нужно попробовать.

3. Я не понимаю. Почему это что-то уменьшает? О каких комбинациях вы говорите?

4. С помощью этой схемы нужно только сравнивать строки с одинаковым хэшем. Я бы предположил, что вы могли бы организовать получение не менее 5000 различных хэшей, чтобы среднее количество строк, которые вам нужно было бы сравнить с другими, было порядка 200 против 1 000 000. И небольшое усилие может привести к созданию алгоритма хеширования, который будет работать намного лучше.

5. Вы имеете в виду, что хотите генерировать много коллизий хэшей? Какой в этом смысл? Вам нужен связанный список для хранения результатов, поэтому вы не будете использовать меньше памяти, но из-за коллизий определенно используете больше процессора. В этом случае я предпочитаю решение OP, поскольку оно превосходит.