Разбор задачи · LeetCode 161

161. One Edit Distance

11.08.2026 · Глеб Михайлов ·Где дают: Яндекс
Условие на LeetCode ↗

1. Условие задачи

Дано: две строки s и t. Они могут состоять из строчных и заглавных английских букв и цифр. Пустые строки тоже разрешены.

Разрешено: выполнить над строкой s одну из трёх операций:

Нужно: вернуть True, если строку s можно превратить в t ровно за одну операцию. Если потребуется ноль операций или больше одной, возвращаем False.

Последнее слово здесь самое важное: ровно одна операция. Поэтому одинаковые строки не подходят.

s = "ab", t = "acb"  → True

Между a и b можно вставить c.

s = "", t = ""  → False

Строки уже равны, то есть нужно ноль операций, а не одна.

Ограничения:

2. Решение

Ключевое наблюдение

Сначала посмотрим только на длины строк.

Теперь идём слева направо до первого несовпадения. Всё до него уже одинаково и редактировать там ничего не нужно. В точке несовпадения мы мысленно тратим нашу единственную операцию, после чего оставшиеся части строк обязаны полностью совпасть.

Именно такой однопроходный подход приводит официальный editorial LeetCode.

Первое решение: со слайсами

Для начала сделаем s более короткой строкой. Если строки разной длины, нам тогда останется рассматривать только вставку символа в s. Это избавляет код от зеркального случая с удалением.

Дальше ищем первое несовпадение s[i] != t[i]:

Если несовпадения вообще не нашлось, короткая строка целиком совпадает с началом длинной. Тогда ответ True только в одном случае: в t остался ровно один лишний символ в конце.

Алгоритм со слайсами

  1. Если s длиннее t, меняем строки местами.
  2. Если t длиннее s больше чем на один символ, возвращаем False.
  3. Идём по индексам короткой строки до первого несовпадения.
  4. При равных длинах пропускаем по одному символу в обеих строках и сравниваем оставшиеся хвосты.
  5. При разных длинах пропускаем один символ только в длинной строке и сравниваем хвосты.
  6. Если несовпадений не было, проверяем, что длинная строка содержит ровно один дополнительный символ.

Код на Python: решение со слайсами

class Solution:
    def isOneEditDistance(self, s: str, t: str) -> bool:
        # 1. Пусть s будет не длиннее t
        if len(s) > len(t):
            return self.isOneEditDistance(t, s)

        # 2. Разницу больше единицы одной операцией не исправить
        if len(t) - len(s) > 1:
            return False

        # 3. Ищем первое несовпадение
        for i in range(len(s)):
            if s[i] != t[i]:
                if len(s) == len(t):
                    # Замена: пропускаем несовпавший символ в обеих строках
                    return s[i + 1:] == t[i + 1:]

                # Вставка в s: пропускаем символ только в длинной t
                return s[i:] == t[i + 1:]

        # 4. Общий префикс совпал:
        # одна операция нужна только при одном лишнем символе в t
        return len(t) == len(s) + 1

Визуализация решения со слайсами

Возьмём:

s = "ab"
t = "acb"
Шаг i s[i] t[i] Что делаем
1 0 a a Символы равны, идём дальше
2 1 b c Первое несовпадение
3 1 - - t длиннее, пропускаем t[1]
4 - - - Сравниваем s[1:] == t[2:], то есть "b" == "b"

Хвосты совпали, значит, достаточно вставить c в строку s. Ответ True.

Почему решение со слайсами работает

До первого несовпадения строки одинаковы, поэтому тратить операцию в общем префиксе бессмысленно.

В первом несовпадении тип допустимой операции однозначно определяется длинами. При равных длинах мы обязаны заменить символ и после этого сравнивать позиции со сдвигом 0. При разнице длин 1 мы обязаны пропустить символ длинной строки и после этого сравнивать позиции со сдвигом 1.

Если оставшиеся хвосты равны, найденной операции достаточно. Если в хвостах есть ещё одно несовпадение, потребовалась бы вторая операция, поэтому ответ False.

Второе решение: без слайсов и без дополнительной памяти

Слайсы делают первое решение коротким, но в Python выражение вроде s[i:] создаёт новую строку. Поэтому в худшем случае на копирование хвостов требуется O(N) дополнительной памяти. Это отдельно отмечено в официальном editorial и часто обсуждается в комментариях к нему.

Чтобы получить честные O(1) по памяти, не будем создавать хвосты. Сравним их на месте с помощью двух указателей:

Если символы равны, двигаем оба указателя. При первом несовпадении увеличиваем edits:

При втором несовпадении сразу возвращаем False. Такой подход лежит в основе популярных пользовательских решений без слайсов, например [Python3] Pointer One Pass Solution и [Python3] O(1) space solution.

Алгоритм без слайсов

  1. Если s длиннее t, меняем строки местами.
  2. Если разница длин больше 1, возвращаем False.
  3. Ставим указатели i = 0, j = 0 и счётчик edits = 0.
  4. Пока оба указателя внутри строк, сравниваем s[i] и t[j].
  5. Если символы равны, двигаем оба указателя.
  6. Если символы различаются, учитываем одну операцию. При второй операции возвращаем False.
  7. При равных длинах после несовпадения двигаем оба указателя, при разных - только указатель длинной строки.
  8. Если после цикла в длинной строке остался символ, учитываем ещё одну вставку.
  9. Возвращаем True, только если набралась ровно одна операция.

Код на Python: решение без слайсов

class Solution:
    def isOneEditDistance(self, s: str, t: str) -> bool:
        # 1. Пусть s будет не длиннее t
        if len(s) > len(t):
            s, t = t, s

        # 2. Одной операцией такую разницу не исправить
        if len(t) - len(s) > 1:
            return False

        i = 0
        j = 0
        edits = 0

        # 3. Сравниваем символы на месте, не создавая подстрок
        while i < len(s) and j < len(t):
            if s[i] == t[j]:
                i += 1
                j += 1
                continue

            # Нашли очередное несовпадение
            edits += 1
            if edits > 1:
                return False

            if len(s) == len(t):
                # Замена: пропускаем символ в обеих строках
                i += 1
                j += 1
            else:
                # Вставка в s: пропускаем символ только в длинной t
                j += 1

        # 4. В t мог остаться один символ в самом конце
        if j < len(t):
            edits += 1

        return edits == 1

Визуализация решения без слайсов

Снова возьмём s = "ab" и t = "acb".

Шаг i j Сравнение edits Действие
1 0 0 a == a 0 Двигаем i и j
2 1 1 b != c 1 t длиннее, двигаем только j
3 1 2 b == b 1 Двигаем i и j
4 2 3 обе строки закончились 1 Возвращаем True

Мы не создавали строки "b" и "b", а сравнили те же символы по индексам.

Почему указатели не пропускают ответ

После нормализации s не длиннее t, поэтому возможны только два случая.

Если короткая строка закончилась раньше, а в t остался последний символ, это тоже одно редактирование: вставка в конец. Счётчик edits гарантирует, что мы принимаем только строки с ровно одной, а не с нулём или двумя операциями.

Краевые случаи

3. Сложность алгоритма

Обозначим через N длину более длинной строки.

Решение со слайсами

Итог по времени: O(N).

Итог по памяти: O(N) из-за слайсов.

Решение без слайсов

Итог по времени: O(N).

Итог по памяти: O(1).

Вывод

Главный паттерн задачи: на первом несовпадении длины строк подсказывают, какую операцию нужно выполнить. Равные длины означают замену и сдвиг двух указателей, разница в один символ означает вставку или удаление и сдвиг только по длинной строке.

Слайсы дают очень короткое и наглядное решение, близкое к официальному editorial. Но если на собеседовании требуют O(1) дополнительной памяти, те же хвосты нужно сравнивать на месте двумя указателями.

Официальный разбор: leetcode.com/problems/one-edit-distance/editorial
Решения пользователей: leetcode.com/problems/one-edit-distance/solutions

🐆