Decode String
Закодированная строка записывает повторяющийся текст в виде k[text], что означает, что text записан k раз подряд. Группы могут находиться внутри других групп, поэтому 2[a3[b]] означает abbbabbb. Напишите функцию, которая получает закодированную строку s и возвращает декодированную строку.
Буквы вне всех скобок остаются без изменений. Каждое число повторений — положительное целое число, записанное непосредственно перед [, и цифры больше нигде не встречаются.
Функция
- sstring
- закодированная строка
- Возвращаетstring
- декодированная строка
Ограничения
1 ≤ s.length ≤ 104sсодержит только строчные английские буквы, цифры,[и].s— допустимое кодирование: за каждым[следует количество, и ему соответствует], при этом пустых скобок нет.- Каждое число
kудовлетворяет условию1 ≤ k ≤ 300и не содержит ведущих нулей. - Скобки могут быть вложены максимум на 100 уровней.
- Декодированная строка содержит не более
5 × 104символов.
Примеры
- Ввод
- s = "2[ab]3[c]x"
- Вывод
- "ababcccx"
- Пояснение
2[ab]даётabab, а3[c]даётccc.xнаходится за пределами всех скобок, поэтому копируется без изменений, в результате получаетсяababcccx.
- Ввод
- s = "2[x3[yz]]"
- Вывод
- "xyzyzyzxyzyzyz"
- Пояснение
- Сначала декодируй внутреннюю часть:
3[yz]— этоyzyzyz, поэтому тело внешней группы —xyzyzyz. Записанное дважды, этоxyzyzyzxyzyzyz.
- Ввод
- s = "q10[w]e"
- Вывод
- "qwwwwwwwwwwe"
- Пояснение
- Количество равно
10, оно считывается как две цифры, поэтомуwпоявляется десять раз междуqиe. Код, который считывает только цифру рядом с[, повторил бы её 0 раз.
+22 скрытых тестов при отправке
Дополнительный вопрос
Декодированная строка может быть намного длиннее входных данных. Как вернуть только символ на позиции i в декодированной строке, не создавая её целиком, если её длина может достигать 10^18?
Подсказки
Открывайте по одной. Каждая подсказывает чуть больше.
Ты не можешь записать
3[...], пока не знаешь, что находится внутри скобок, а внутри может быть ещё несколько групп. Какой тип группы ты всегда можешь сразу расшифровать?Группу, внутри которой нет других групп, можно раскрыть сразу, поэтому работай изнутри наружу. Когда появляется
], группа, которую она закрывает, завершена, и тебе нужны текст и количество, которые ждали перед её[.Выполните один проход, сохраняя уже собранный текст и считываемое число. При встрече
[поместите их оба в стек и начните заново. При встрече]извлеките их из стека и добавьте текущий текст, повторённый нужное число раз, к извлечённому тексту. Собирайте каждое число по одной цифре, чтобы работали10и300.
Решение
Количество указывается перед скобками, но нельзя записать копии, пока не знаешь, что находится внутри них, а внутри могут быть другие группы. Поэтому группу можно раскрыть, только когда завершена каждая группа внутри неё. Каждый из описанных ниже подходов позволяет сначала завершить самые внутренние группы: переписать строку изнутри наружу, позволить рекурсивному вызову завершить внутреннюю группу до внешней или хранить незавершённые внешние группы в стеке. Ниже n — длина входных данных, m — длина декодированной строки, а d — максимальная глубина вложенности.
Раскройте самую внутреннюю группу, затем повторите
Идея
Расшифруйте строку так, как сделали бы это на бумаге. Найдите группу, внутри которой нет других групп, разверните её копии на месте и проверьте снова. В 2[x3[yz]] в группе 3[yz] внутри ничего нет, поэтому строка становится такой: 2[xyzyzyz], а ещё одно раскрытие даст ответ.
Первая ] в строке всегда закрывает такую группу. До неё никакая другая группа не закрылась, поэтому между ней и её [ не может быть скобок. Эта [ — ближайшая слева от неё, а счётчик — это последовательность цифр непосредственно перед ней. Замените счётчик, скобки и содержимое группы на содержимое, записанное k раз, и повторяйте, пока в строке не останется ни одной ].
Это верно, но при каждом раскрытии заново строится вся строка. При b группах и строке, длина которой растёт примерно до m символов, это до b × m копирований символов. Скрытый тест примерно с 1,300 группами подряд требует около 25 миллионов копирований, чтобы получить 27,688 символов, хотя для этого хватило бы одного прохода по входным данным.
Алгоритм
- Найдите первую
]в строке. Если её нет, строка декодирована: верните её. - Двигайтесь влево от неё до ближайшей
[. Текст между ними — тело группы. - Продолжайте двигаться влево по цифрам перед этой
[и прочитайте их как количествоk. - Замените всё от первой цифры до
]телом, записаннымkраз. - Вернитесь к шагу 1.
def decodeString(s):
# Expand one innermost group at a time until no bracket is left.
while True:
close = s.find("]")
if close == -1:
return s
# The first ']' closes a group with no group inside it,
# and the nearest '[' to its left opens that group.
open_ = s.rfind("[", 0, close)
start = open_
while start > 0 and s[start - 1].isdigit():
start -= 1
times = int(s[start:open_])
s = s[:start] + s[open_ + 1:close] * times + s[close + 1:]Рекурсивный спуск
Идея
Формат рекурсивный: закодированная строка — это последовательность букв и групп, а тело группы — это снова закодированная строка. Поэтому напиши одну функцию decode, которая читает данные с общей позиции, пока не встретит ], завершающую текущий уровень, или конец входных данных, и возвращает прочитанное в декодированном виде.
Когда decode встречает цифру, она считывает всё число, пропускает [ и вызывает себя, чтобы декодировать тело группы. Этот вызов останавливается на соответствующей ], потому что любые более глубокие ] уже были обработаны более глубоким вызовом. Вызывающая функция пропускает ], добавляет тело k раз и продолжает чтение. Для 2[x3[yz]] внешний вызов считывает 2; следующий вызов считывает x и 3; третий вызов возвращает yz; средний вызов возвращает xyzyzyz; внешний вызов записывает результат дважды.
Каждый символ входных данных считывается один раз. Основные затраты связаны с копированием: символ результата копируется по одному разу для каждой окружающей его группы, поэтому время работы составляет O(n + m·d) при глубине вложенности d. Рекурсия также достигает глубины в d вызовов. Для 100 уровней это нормально, но очень глубоко вложенные данные могут переполнить стек вызовов: например, Python по умолчанию останавливается на 1 000 вложенных вызовах.
Алгоритм
- Сохраняйте одну позицию
pos, общую для всех вызовов, начиная с первого символа. decode()выполняет цикл, покаposнаходится внутри строки и не указывает на].- Если встречается буква, добавьте её и перейдите дальше.
- Если встречается цифра, прочитайте целое число
k, пропустите[, вызовитеdecode()для содержимого, пропустите]и добавьте содержимоеkраз. - Верните собранный результат. Первый вызов возвращает декодированную строку.
def decodeString(s):
pos = 0
def decode():
# Read from pos up to the ']' that closes this level, or the end.
nonlocal pos
parts = []
while pos < len(s) and s[pos] != "]":
if s[pos].isdigit():
times = 0
while s[pos].isdigit():
times = times * 10 + int(s[pos])
pos += 1
pos += 1 # skip '['
inner = decode() # the group's body, fully decoded
pos += 1 # skip ']'
parts.append(inner * times)
else:
parts.append(s[pos])
pos += 1
return "".join(parts)
return decode()Один проход со стеком
Идея
Рекурсия хранит по одному незавершённому фрагменту текста для каждой открытой группы в кадрах вызовов. Вместо этого можно хранить эти фрагменты в собственном стеке и прочитать строку за один проход цикла.
Отслеживай две величины для текущего уровня: current — текст, декодированный на данный момент, и count — считываемое число. Цифра дополняет count по формуле count × 10 + digit, поэтому значения 10 и 300 обрабатываются правильно. Символ [ открывает уровень: помести current и count в стек, затем начни оба значения заново. Буква добавляется к current. Символ ] закрывает уровень: извлеки из стека сохранённые текст и число, а затем присвой current значение, состоящее из сохранённого текста, за которым следует count копий current.
Проследи за выполнением для 2[x3[yz]]. При первом символе [ ты помещаешь в стек (пустая строка, 2). Символ x задаёт значение x для current. При втором символе [ ты помещаешь в стек (x, 3), а yz записывается в новый current. Первый символ ] извлекает из стека (x, 3), поэтому значением current становится xyzyzyz. Последний символ ] извлекает из стека (пустая строка, 2), и значением current становится xyzyzyzxyzyzyz.
Группы закрываются в порядке, обратном порядку их открытия, поэтому вершина стека всегда соответствует уровню, к которому возвращает символ ]. Объём работы такой же, как у рекурсии, O(n + m·d), но при глубокой вложенности растёт только список, а не стек вызовов.
Алгоритм
- Начните с пустого стека, пустой строки
currentиcount = 0. - При встрече цифры установите
count = count × 10 + digit. - При встрече
[поместите пару (current,count) в стек, затем сбросьтеcurrentдо пустой строки, аcount— до 0. - При встрече буквы добавьте её в конец
current. - При встрече
]извлеките из стека (before,k) и установитеcurrentравнымbefore, за которым следуютkкопийcurrent. - После последнего символа верните
current.
def decodeString(s):
stack = [] # one entry per open '[': (the text before it, its count)
current = [] # pieces of the text at the current level
count = 0
for ch in s:
if ch.isdigit():
count = count * 10 + int(ch) # counts can have several digits
elif ch == "[":
stack.append((current, count))
current, count = [], 0
elif ch == "]":
before, times = stack.pop()
before.append("".join(current) * times)
current = before
else:
current.append(ch)
return "".join(current)
Ловушки и крайние случаи
Большинство неправильных ответов возникает из-за того, что неверно считывается счётчик или неправильно определяется, куда добавляется сохранённый текст.
- Чтение одной цифры как всего счётчика. В
q10[w]eсчётчик равен 10. Код, который берёт только цифру перед[, повторяетw0 раз. - Забыли сбросить
countдо 0 после добавления в стек. Тогда цифры следующей группы прибавляются к старому числу, поэтому внутренний счётчик в2[a3[b]]считывается как 23. - Добавление копий перед сохранённым текстом. При встрече с
]результатом становится текст перед группой, за которым следуют копии, поэтомуab2[c]— этоabcc, а неccab. - Потеря букв на верхнем уровне. Символ
xв2[ab]3[c]xнаходится вне всех скобок и всё равно должен войти в ответ. - Добавление по одному символу в длинную неизменяемую строку. При каждом добавлении может копироваться вся строка, из-за чего ответ длиной 50,000 символов превращается в миллиарды копирований. Собирайте фрагменты в список или в строковый буфер.
Частые вопросы4
Какова временная сложность Decode String?
Чтение входных данных имеет сложность O(n). При построении результата каждый символ копируется один раз для каждой группы, в которую он входит, поэтому общая сложность составляет O(n + m·d), где m — длина декодированной строки, а d — глубина вложенности. Если каждый счётчик не меньше 2, каждая группа не длиннее половины окружающей её группы, поэтому количество копирований не превышает 2m. Ни один подход не может иметь сложность лучше O(m), поскольку сам ответ содержит m символов.
Стоит ли решать задачу Decode String с помощью рекурсии или стека?
Оба варианта выполняют одну и ту же работу. Рекурсия напрямую следует формату, поскольку тело группы само является закодированной строкой, и часто это быстрее всего написать на собеседовании. Вариант со стеком делает то же самое в одном цикле и хранит незавершённые внешние уровни в списке, поэтому очень глубокая вложенность не приведёт к переполнению стека вызовов. Если интервьюер спросит о входных данных с вложенностью в тысячи уровней, ответ — стек.
Как обрабатывать количества, состоящие более чем из одной цифры?
Собирай число по мере чтения: начни с 0 и для каждой цифры устанавливай count = count × 10 + digit. Когда встречается [, число готово, поэтому 300[a] даёт 300. Сбрось счётчик на 0 сразу после того, как добавишь его в стек, иначе цифры следующей группы будут добавляться к нему.
Почему стек хранит текст, который находился перед каждой скобкой?
Когда открывается [, текст, уже декодированный на этом уровне, ещё не завершён: после него должны идти копии группы. Поместив этот текст в стек, вы сохраните его, пока декодируете тело, начиная с пустой строки. Когда появляется соответствующая ], извлечение из стека возвращает этот текст, и вы добавляете к нему копии.
Похожие задачи
Задачи на те же идеи. Решите две или три, и приём запомнится.
Python
def decodeString(s):
# Напишите код здесьСлучай 1
Случай 2
Случай 3
Ввод
s = "2[ab]3[c]x"
Ожидается
"ababcccx"