Is Subsequence
Вам даны две строки: s и t. Верните true, если можно получить s из t, удалив некоторые буквы (возможно, ни одной), сохранив порядок оставшихся букв, и false в противном случае. Например, ace является подпоследовательностью abcde, а aec — нет.
Функция
- sstring
- строка, которую нужно найти
- tstring
- строка, из которой нужно удалить буквы
- Возвращаетboolean
- true, если s можно прочитать внутри t по порядку, возможно, с пропусками
Ограничения
1 ≤ s.length ≤ 3 × 1041 ≤ t.length ≤ 5 × 104sиtсодержат только строчные английские буквы.
Примеры
- Ввод
- s = "ace"t = "abcde"
- Вывод
- true
- Пояснение
- Удалите
bиdизabcde, и останетсяaceв том же порядке.
- Ввод
- s = "aec"t = "abcde"
- Вывод
- false
- Пояснение
- В
tесть все три буквы, но единственнаяcнаходится перед единственнойe. После того как ты используешьeс индексом 4, справа от неё не остаётся ни однойc.
- Ввод
- s = "moon"t = "monsoon"
- Вывод
- true
- Пояснение
- Используйте
mс индексом 0, буквыoс индексами 1 и 4 иnс индексом 6 в словеmonsoon. Буквы между ними удаляются.
Дополнительный вопрос
Если t остаётся неизменной, и ты проверяешь миллион строк s на соответствие ей, как подготовить t, чтобы каждая проверка выполнялась быстрее, чем чтение всей t?
Подсказки
Открывайте по одной. Каждая подсказывает чуть больше.
Посмотри на первую букву в
s. Какую её копию вtследует использовать?Используй самое раннее вхождение. Более позднее вхождение может только оставить меньше
tдля оставшейся частиs, поэтому самый ранний выбор никогда не будет хуже.Храни один индекс в
sи один вt. Проходи поtпо одной букве за раз, при каждом совпадении продвигай индекс вs, а в конце проверь, достиг ли он концаs.
Решение
Подпоследовательность может пропускать буквы в t где угодно, поэтому может показаться, что нужно попробовать множество вариантов размещения s. Но это не нужно: сопоставлять каждую букву из s в самом раннем возможном месте всегда не хуже, чем делать любой другой выбор, поэтому достаточно одного прохода слева направо с двумя указателями.
Динамическое программирование по префиксам
Верно, но не успевает на самых больших тестах
Идея
Пусть dp[i][j] показывает, помещаются ли первые i букв s среди первых j букв t. Они помещаются, если уже помещаются и без t[j-1] либо если s[i-1] равно t[j-1] и первые i-1 букв помещаются среди t[:j-1]. Пустой префикс помещается везде, а строка i использует только строку i-1, поэтому достаточно двух строк длины m+1.
Таблица заполняет каждую ячейку: при длине s в 25,000 букв и длине t в 50,000 букв это 1.25 × 10^9 ячеек.
Алгоритм
- Создай строку
prevизm+1значений, всеtrue: пустаяsподходит к каждому префиксуt. - Для каждого
iот 1 доnсоздай строкуcurсcur[0] = false. - Для каждого
jот 1 доmустановиcur[j]в значениеcur[j-1]илиprev[j-1], еслиs[i-1]равноt[j-1]. - Замени
prevнаcur. - Верни
prev[m].
def isSubsequence(s, t):
n, m = len(s), len(t)
# prev[j]: the first i-1 letters of s fit inside t[:j]. An empty s fits anywhere.
prev = [True] * (m + 1)
for i in range(1, n + 1):
cur = [False] * (m + 1)
for j in range(1, m + 1):
cur[j] = cur[j - 1] or (s[i - 1] == t[j - 1] and prev[j - 1])
prev = cur
return prev[m]Два указателя с жадным сопоставлением
Идея
Храни указатель i на следующую нужную букву в s и бери первое её вхождение, которое встретишь в t. Такой выбор безопасен. Если в допустимом размещении используется более позднее вхождение s[i], замена его на самое раннее сохраняет порядок и оставляет больше символов t для оставшейся части s. Поэтому, если существует хотя бы одно размещение, жадный алгоритм его найдёт.
Цикл читает каждую букву t не более одного раза и хранит два индекса: время O(n + m) и память O(1).
Алгоритм
- Установи
i = 0дляsиj = 0дляt. - Пока оба индекса находятся внутри своих строк, сравнивай
s[i]сt[j]. - Если они равны, увеличь
i. - Увеличивай
jв любом случае. - Верни результат проверки, равно ли
iдлинеs.
def isSubsequence(s, t):
i = j = 0
while i < len(s) and j < len(t):
if s[i] == t[j]:
i += 1
j += 1
return i == len(s)
Ловушки и крайние случаи
- Поиск каждой буквы из
sгде угодно вt, а не после предыдущего совпадения. Это приводит к тому, чтоaecсчитается подпоследовательностью вabcde. - Использование одной и той же буквы дважды.
noonне является подпоследовательностьюmoon: единственнаяn— с индексом 3 — не может быть одновременно первой и последней буквой. - Возврат значения, показывающего, достиг ли
jконцаt. Цикл часто заканчивается там независимо от того, найдена лиs; толькоiподскажет вам это. - Продолжение работы, когда в
tзакончились символы. Дляabcиabнужно вернутьfalse. - Чтение
s[i]после того, какiдостиг концаs. В Python или Java такое чтение вызывает исключение, поэтому проверяйтеiперед сравнением.
Частые вопросы3
Какова временная сложность задачи «Is Subsequence»?
Решение с двумя указателями работает за время O(n + m), где n и m — длины s и t, и использует O(1) дополнительной памяти. Цикл останавливается не более чем через m шагов. Для построения таблицы префиксов требуется время O(n × m).
Как задача «Является ли подпоследовательностью» связана с задачей о наибольшей общей подпоследовательности?
s является подпоследовательностью t тогда и только тогда, когда длина их наибольшей общей подпоследовательности равна n, то есть длине s. Таблица префиксов из первого подхода — это та таблица, которую вы строите для этой задачи. Здесь нужно знать только, помещается ли в t вся строка s, поэтому достаточно жадного прохода.
В чем разница между подпоследовательностью и подстрокой?
Подстрока — это блок идущих подряд букв, а подпоследовательность может пропускать буквы, если их порядок сохраняется. ace — подпоследовательность abcde, но не её подстрока. Каждая подстрока является подпоследовательностью, но не наоборот.
Похожие задачи
Задачи на те же идеи. Решите две или три, и приём запомнится.
def isSubsequence(s, t):
# Напишите код здесьТест #1
Тест #2
Тест #3
Аргументы
s = "ace" t = "abcde"
Ожидаемый вывод
true