Два указателя
Подпоследовательность
Проверить является ли одна строка подпоследовательностью другой
Постановка задачи
Описание (LeetCode)
Даны две строки s и t. Верните true, если s является
подпоследовательностью t, или false в противном случае.
Подпоследовательность строки - это строка, образованная из исходной строки путем удаления некоторого количества символов (удалений также может и не быть) без изменения относительного положения оставшихся. Например, "ace" является подпоследовательностью "abcde", а "aec" - нет.
Пример 1:
s = "abc", t = "ahbgdc"
Результат:
true
Пояснение:
В строке "ahbgdc" можно выделить подпоследовательность "abc", удалив символы "h", "g", "d".
Пример 2:
s = "axc", t = "ahbgdc"
Результат:
false
Пояснение:
В строке "ahbgdc" нельзя выделить подпоследовательность "axc".
Ограничения:
0 <= s.length <= 1000 <= t.length <= 10⁴sиtсостоят из строчных английских букв.
Решение
Это очень простая задача - разминка, которую дают на интервью для разогрева. В ней интересно то, что паттерн двух указателей применяется не в одной структуре данных, а в разных (в двух разных строках). Задача показывает, можете ли вы обойтись без создания лишних копий строк и не спотыкаетесь ли вы в индексах.
Хотя для этой задачи "плохое" решение придумать сложно, новички иногда пытаются
использовать генерацию всех возможных подпоследовательностей строки t, чтобы
потом проверить, входит ли строка s в этот список.
Проблема с таким подходом в том, что если в строке t всего 30 символов, то
количество подпоследовательностей будет 2³⁰ (больше миллиарда). Это
катастрофически медленно и по памяти, и по времени.
Идея решения:
Идея оптимального решения заключается в том, что нам не нужно генерировать подпоследовательности. Мы просто проходим по этим строкам одновременно двумя указателями.
Первый указатель будет отслеживать текущий символ в строке s для
сопоставления, а второй будет постоянно двигаться по строке t. Если под
указателями оказались одинаковые символы, это означает, что мы нашли один из
нужных символов подпоследовательности s и можем сдвинуть первый указатель.
Основные шаги:
-
Первый указатель
iставим в начало короткой строкиs. -
Вторым указателем
jидем в цикле по строкеt.- Если символы под указателями совпали,
s[i] == t[j], значит, мы нашли нужный символ в нужном порядке.- Сдвигаем
i. - Если
i == s.length, значит, все символы найдены иs- это подпоследовательностьt.
- Сдвигаем
- Если символы под указателями совпали,
-
Завершение цикла:
- Мы прошли всю строку
t, но строкаsоказалась пройдена не полностью, то есть мы не нашли подпоследовательность. Возвращаемfalse.
- Мы прошли всю строку
Вот как это работает на примере:
s = "abc", t = "ahbgdc"
- Указатель
i = 0. - Идем циклом по строке
t.
j = 0(a): совпало,iстал1.j = 1(h): идем дальше.j = 2(b): совпало,iстал2.j = 3(g): идем дальше.j = 4(d): идем дальше.j = 5(c): совпало,iстал3.i == s.length- возвращаемtrue.
Итог: "abc" - это подпоследовательность "ahbgdc".
Оценка сложности
Временная сложность
В худшем случае (когда строка s не является подпоследовательностью или ее
последний символ стоит в самом конце t) нам приходится пройти по всей строке
t один раз.
Таким образом, получаем временную сложность O(n), где n - длина строки t.
Пространственная сложность
Мы не создаем никаких дополнительных структур данных кроме переменных для
указателей, поэтому пространственная сложность равна O(1).
Код решения
Приведем код решения.
func isSubsequence(s string, t string) bool {
if len(s) == 0 {
return true
}
i := 0
for j := 0; j < len(t); j++ {
if s[i] == t[j] {
i++
if i == len(s) {
return true
}
}
}
return false
}Задача простая, но на ней можно споткнуться. Вот реальный случай из нашей практики: разработчик на собеседовании в крупной технологической компании решал эту задачу весь раунд интервью. Он придумал решение, но потратил на него много времени. В итоге интервьюер посоветовал набрать побольше практики на LeetCode.
Именно поэтому мы рекомендуем после прочтения разборов самостоятельно проработать каждую задачу, чтобы вы могли написать решение без подсказок.
Итоги
Задача хорошо подходит для иллюстрации паттерна два указателя.
-
Основная идея. Мы сопоставляем указатели, сохраняя их относительный порядок.
-
Стратегия сдвига. Используем поэтапное однонаправленное движение указателей.
-
Граничный случай. Пустая строка всегда является подпоследовательностью.
-
Ранний выход. Как только первый указатель достиг конца, можно сразу возвращать результат.