Как найти лексикографически наименьшую строку, изменив подстроку?
У меня есть строка S
, которая состоит из a
и b
. Выполните следующую операцию один раз. Цель состоит в том, чтобы получить лексикографически наименьшую строку.
Операция: Обратить ровно одну подстроку S
например.
- if
S = abab
, затем Output = aabb
(reverse ba
строки S
)
- if
S = abba
then Output = aabb
(reverse bba
строки S
)
Мой подход
Случай 1: Если все символы входной строки одинаковы, то вывод будет самой строкой.
Случай 2:, если S
имеет вид aaaaaaa....bbbbbb....
, тогда ответ будет S
сам.
иначе: Найти первое вхождение b
в S
сказать, что позиция i. Строка S
будет выглядеть как
aa...bbb...aaaa...bbbb....aaaa....bbbb....aaaaa...
|
i
Чтобы получить лексикографически наименьшую строку, подстрока, которая будет обращена вспять, начинается с индекса i. См. Ниже возможное окончание j.
aa...bbb...aaaa...bbbb....aaaa....bbbb....aaaaa...
| | | |
i j j j
Обратная подстрока S[i:j]
для каждого j и найдите наименьшую строку.
Сложность алгоритма будет O(|S|*|S|)
где |S|
- длина строки.
Есть ли лучший способ решить эту проблему? Вероятно, решение O(|S|)
.
То, что я думаю, если мы можем выбрать правильный j
в линейном времени, тогда мы закончим. Выберем, что j, где число a
является максимальным. Если есть один максимум, мы решили проблему, но что, если это не так? Я много пробовал. Пожалуйста, помогите.
Ответы
Ответ 1
Итак, я придумал алгоритм, который кажется более эффективным, чем O (| S | ^ 2), но я не совсем уверен в его сложности. Вот грубая схема:
- Полоса ведущего
a's
, сохраняющая в переменной start
.
- Сгруппируйте остальную часть строки в куски писем.
- Найдите индексы групп с самыми длинными последовательностями
a's
.
- Если осталось только один
index
, перейдите к 10.
- Отфильтруйте эти индексы так, чтобы длина [первой] группы
b's
после разворота была минимальной.
- Если осталось только один
index
, перейдите к 10.
- Отфильтруйте эти индексы так, чтобы длина [первой] группы
a's
(не включая начальную a's
) после разворота была минимальной.
- Если осталось только один
index
, перейдите к 10.
- Вернитесь к 5, за исключением проверки групп [second/third/...]
a's
и b's
на этот раз.
- Возвращает
start
, а также группы с отменой до index
, а также остальные группы.
Так как любая подстрока, которая обращается вспять, начинается с a b
и заканчивается на a
, никакие два гипотетических разворота не являются палиндромами, и, следовательно, два разворота не приведут к одному и тому же результату, гарантируя, что существует уникальное оптимальное решение и что алгоритм завершится.
Моя интуиция говорит о таком подходе, вероятно, о (log (| S |) * | S |), но я не слишком уверен. Ниже приведен пример реализации (не очень хороший, хотя и в Python).
from itertools import groupby
def get_next_bs(i, groups, off):
d = 1 + 2*off
before_bs = len(groups[i-d]) if i >= d else 0
after_bs = len(groups[i+d]) if i <= d and len(groups) > i + d else 0
return before_bs + after_bs
def get_next_as(i, groups, off):
d = 2*(off + 1)
return len(groups[d+1]) if i < d else len(groups[i-d])
def maximal_reversal(s):
# example input: 'aabaababbaababbaabbbaa'
first_b = s.find('b')
start, rest = s[:first_b], s[first_b:]
# 'aa', 'baababbaababbaabbbaa'
groups = [''.join(g) for _, g in groupby(rest)]
# ['b', 'aa', 'b', 'a', 'bb', 'aa', 'b', 'a', 'bb', 'aa', 'bbb', 'aa']
try:
max_length = max(len(g) for g in groups if g[0] == 'a')
except ValueError:
return s # no a after the start, no reversal needed
indices = [i for i, g in enumerate(groups) if g[0] == 'a' and len(g) == max_length]
# [1, 5, 9, 11]
off = 0
while len(indices) > 1:
min_bs = min(get_next_bs(i, groups, off) for i in indices)
indices = [i for i in indices if get_next_bs(i, groups, off) == min_bs]
# off 0: [1, 5, 9], off 1: [5, 9], off 2: [9]
if len(indices) == 1:
break
max_as = max(get_next_as(i, groups, off) for i in indices)
indices = [i for i in indices if get_next_as(i, groups, off) == max_as]
# off 0: [1, 5, 9], off 1: [5, 9]
off += 1
i = indices[0]
groups[:i+1] = groups[:i+1][::-1]
return start + ''.join(groups)
# 'aaaabbabaabbabaabbbbaa'
Ответ 2
TL; DR: Здесь алгоритм, который только итерации по строке один раз (с O (| S |) - сложностью для ограниченных строк). Пример, который я объясняю ниже, немного длинный, но алгоритм действительно довольно прост:
- Итерируйте по строке и обновите ее значение, интерпретируемое как двоичное число обратного (lsb-to-msb).
- Если вы найдете последний нуль последовательности нулей, длина которой превышает текущий максимум, сохраните текущую позицию и текущее обратное значение. С этого момента также обновите это значение, интерпретируя остальную часть строки в качестве двоичного числа вперёд (msb-to-lsb).
- Если вы найдете последний нуль последовательности нулей, которая до тех пор, пока текущий максимум, сравните текущее обратное значение с текущим значением сохраненной конечной точки; если он меньше, замените конечную точку на текущую позицию.
Итак, вы в основном сравниваете значение строки, если она была обращена вверх к текущей точке, со значением строки, если она была только обращена вверх к (так далеко) оптимальной точке и обновляла эту оптимальную точка на лету.
Вот пример быстрого кода; его можно было бы, несомненно, кодировать более элегантно:
function reverseSubsequence(str) {
var reverse = 0, max = 0, first, last, value, len = 0, unit = 1;
for (var pos = 0; pos < str.length; pos++) {
var digit = str.charCodeAt(pos) - 97; // read next digit
if (digit == 0) {
if (first == undefined) continue; // skip leading zeros
if (++len > max || len == max && reverse < value) { // better endpoint found
max = len;
last = pos;
value = reverse;
}
} else {
if (first == undefined) first = pos; // end of leading zeros
len = 0;
}
reverse += unit * digit; // update reverse value
unit <<= 1;
value = value * 2 + digit; // update endpoint value
}
return {from: first || 0, to: last || 0};
}
var result = reverseSubsequence("aaabbaabaaabbabaaabaaab");
document.write(result.from + "→" + result.to);