Это из GeeksForGeeks:
«По заданной строке найдите самый длинный палиндром, который можно построить, удалив или перетасовав символы из строки. Вернуть только один палиндром, если существует несколько строк палиндрома наибольшей длины».
Учитывая их примеры ввода и вывода, я собираюсь предположить, что вводит только строчные буквы. Если вы хотите предположить обратное, следующая запись в блоге все равно будет релевантной на 99%.
Вот мой псевдокод для наивной реализации:
# remember: palindromes must have the same letter at [0] and [-1], [1] and [-2], etc. # scan the string for duplicate letters # return a single letter if no duplicate letters exist # when a pair of letters is found, add them to beginning and end of new string, and remove them both from the original string # if, when no pairs are left, there are still characters remaining in the string, add one of them to the string at index [string.length/2]
Итак, с этим хакерским планом я пришел к следующему:
def longest_pal(str) longest = [] loop do alpha = str.scan(/([a-z])/)[0][0] all_of_alpha = str.scan(/#{alpha}/) letter = all_of_alpha[0] if all_of_alpha.length >= 2 remaining = all_of_alpha.length - 2 longest.insert(0, letter) longest.insert(-1, letter) str.delete!(letter) remaining.times do str << letter end end break unless str.each_char.find { |c| str.count(c) > 1 } end letter = str.scan(/([a-z])/)[0][0] longest.insert(longest.length/2, letter) unless str.empty? longest.join end
Что тут происходит? Что ж, мы #сканируем любую букву в строке. Затем мы сопоставляем все экземпляры этой буквы. Если есть два или более экземпляра, мы добавляем эту букву в начало и конец массива, #удаляем все экземпляры этой буквы из строки, затем добавляем ее обратно, чтобы в итоге мы удалили только 2. Мы прерываем работу, когда есть пар больше не осталось, затем добавьте последнюю букву в середину строки, если какие-либо буквы остались.
Ой. Но эй, это работает. Теперь давайте посмотрим, что говорят люди GeeksForGeeks.
«Для палиндромной строки нечетной длины, скажем, 2n + 1, 'beg' состоит из первых n символов строки, 'mid' будет состоять только из 1 символа, т.е. (n + 1)-й символ, а 'end' будет состоять из из последних n символов строки-палиндрома.
Похоже, мы были на правильном пути. GeeksForGeeks дает нам реализацию на C++, которую я преобразовал в Ruby ниже:
def longest_pal(str) beg = "" mid = "" count = {} i = 0 while i < str.length count[str[i]] ||= 0 count[str[i]] += 1 i += 1 end count.keys.each do |letter| if count[letter].odd? mid = letter count[letter] -= 1 else i = 0 while i < count[letter]/2 beg << letter i += 1 end end end last = beg.reverse "#{beg}#{mid}#{last}" end
Какая разница? Первое, что бросается в глаза, это использование ими хеша. Они проходят через строку один раз, увеличивая количество для этой буквы. Затем они проверяют, добавляют ли буквы в начало строки, если количество букв четное. Если нечетное, они устанавливают среднюю букву на эту букву. В конце они переворачивают начальную строку и объединяют все три части в один палиндром. Решение — O(n), потому что задействована только одна итерация, а обращение массива, дающее нам «последний» компонент, происходит только один раз. Сравните это с моей первой попыткой, которая включала 2 сканирования каждой пары букв в строке. Мне кажется, что это как минимум O(2n), и при этом выглядит намного уродливее. Держитесь подальше от регулярных выражений, дети. Ты не хочешь закончить, как я.