Бесплатная бета: 60 дней полного доступа, без карты.мест осталось: 84Зарегистрироваться бесплатно

Мы используем необходимые куки для работы сайта (вход и язык). Если ты согласишься, мы также загрузим Google Analytics, чтобы видеть, какие страницы читают, и Google reCAPTCHA для защиты форм обратной связи и сообщений об ошибке от спама. Политика конфиденциальности

Все паттерны

DP над подпоследовательностями (LIS / LCS / расстояние редактирования)

DP on subsequences (LIS / LCS / edit distance)

O(n2)/O(nm)

Для одной последовательности dp[i] хранит самую длинную цепочку, которая кончается на i. Для двух ячейка i и j отвечает за пару префиксов.

Обновлено 17 окт. 2026 г.

DP над подпоследовательностями (LIS / LCS / расстояние редактирования): как это работает?

Для одной последовательности dp[i] это самая длинная возрастающая цепочка, которая кончается на элементе i. Каждое dp[i] начинается с 1: элемент сам по себе.

Для каждого i просмотрите все предыдущие j. Если arr[j] меньше, цепочка на j может взять arr[i] следующим.

Тогда dp[i] становится большим из самого себя и dp[j] плюс один. Ответ это наибольшее dp[i], а не последнее.

Для двух последовательностей строят таблицу. Ячейка i и j это ответ для этих двух префиксов.

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

Нулевая строка и нулевой столбец означают пустые префиксы. Каждая строка читает только строку над собой, поэтому хватает одной строки памяти.

Источники: Википедия: Наибольшая общая подпоследовательность, Википедия: Расстояние Левенштейна, Wagner, Fischer (1974), The string-to-string correction problem (англ.)

  1. arr = [2, 5, 3, 7, 4, 8], все dp = 1Каждый элемент сам по себе это цепочка длины 1.
  2. dp[1] = 2, dp[2] = 2И 5, и 3 могут идти после 2. Других меньших элементов перед ними нет.
  3. dp[3] = 37 может идти после 5 или 3, у обоих dp равно 2. Прибавляем один.
  4. dp[4] = 34 может идти после 3, но не после 5 или 7. Значит оно продолжает 2, 3.
  5. dp = [1, 2, 2, 3, 3, 4], ответ = 48 может идти после любого элемента. Одна из самых длинных цепочек это 2, 5, 7, 8.
Самая длинная возрастающая подпоследовательность
longestIncreasingSubsequence([2, 5, 3, 7, 4, 8])
arr
2
5
3
7
4
8
dp[i]: самая длинная возрастающая цепочка, которая кончается на arr[i]
1
1
1
1
1
1
best = 1

Ищем самую длинную возрастающую подпоследовательность. Каждый dp[i] начинается с 1: само arr[i] уже цепочка длины 1.

Гонка скорости
Самая длинная возрастающая подпоследовательность одних и тех же 11 чисел: перебор подмножеств, квадратичная таблица или терпеливая сортировка

Столбики: исходные числа, их никто не переставляет. Подсвечено число, которое сейчас читается; при выборе подмножества подсвечены все его элементы.

Одна операция: одно чтение элемента или выбор следующего подмножества. За каждый кадр все дорожки делают одинаковое число операций, поэтому медленная отстаёт.

Перебор всех подмножеств O(2ⁿ·n)
операций: 0
Квадратичная таблица O(n²)
операций: 0
Терпеливая сортировка с бинарным поиском O(n log n)
операций: 0

 

А если чисел больше

Операции для n чисел и примерное время при 100 миллионах простых операций в секунду. Это оценка по тому же счёту, а не замер.

n = 10
Перебор всех подмножеств3,6 тыс. · меньше 1 мс
Квадратичная таблица55 · меньше 1 мс
Терпеливая сортировка с бинарным поиском26,6 · меньше 1 мс
n = 20
Перебор всех подмножеств3,7 млн · 37 мс
Квадратичная таблица210 · меньше 1 мс
Терпеливая сортировка с бинарным поиском63,2 · меньше 1 мс
n = 40
Перебор всех подмножеств3,8 трлн · 11 ч
Квадратичная таблица820 · меньше 1 мс
Терпеливая сортировка с бинарным поиском146,4 · меньше 1 мс

DP над подпоследовательностями (LIS / LCS / расстояние редактирования): шаблон кода

function longestIncreasingSubsequence(arr) {
    const dp = new Array(arr.length).fill(1);
    let best = arr.length ? 1 : 0;
    for (let i = 1; i < arr.length; i++) {
        for (let j = 0; j < i; j++) {
            if (arr[j] < arr[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
        }
        best = Math.max(best, dp[i]);
    }
    return best;
}

DP над подпоследовательностями (LIS / LCS / расстояние редактирования): разбор примера

Расстояние редактирования между словами

Верните наименьшее число посимвольных правок, превращающих одно слово в другое.

Правка вставляет, удаляет или заменяет ровно один символ.

Ячейка i и j это стоимость превращения первых i букв в первые j.

При совпадении стоимость берётся с диагонали. При расхождении это единица плюс дешёвый сосед.

function minDistance(a, b) {
    // row 0 and column 0 hold the cost of deleting a whole prefix
    const dp = Array.from({ length: a.length + 1 }, (_, i) =>
        Array.from({ length: b.length + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0))
    );

    for (let i = 1; i <= a.length; i++) {
        for (let j = 1; j <= b.length; j++) {
            if (a[i - 1] === b[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1]; // no edit needed
            } else {
                // replace, delete, insert
                dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }

    return dp[a.length][b.length];
}

DP над подпоследовательностями (LIS / LCS / расстояние редактирования): когда применять?

Паттерн «DP над подпоследовательностями (LIS / LCS / расстояние редактирования)» нужен, когда в условии есть слова «самая длинная возрастающая подпоследовательность», «самая длинная общая подпоследовательность двух строк» или «минимум правок, чтобы превратить одну строку в другую». Решение на нём обычно укладывается в O(n^2)/O(nm).

  • самая длинная возрастающая подпоследовательность
  • самая длинная общая подпоследовательность двух строк
  • минимум правок, чтобы превратить одну строку в другую
  • сохранить относительный порядок, можно пропускать элементы
  • diff или выравнивание двух последовательностей

Слова в условии для всех паттернов собраны в одной таблице: шпаргалка по паттернам алгоритмов.

DP над подпоследовательностями (LIS / LCS / расстояние редактирования): с чем путают?

Паттерн «DP над подпоследовательностями (LIS / LCS / расстояние редактирования)» чаще всего путают с этими паттернами: Динамическое программирование (1-D), DP на строках (word break), Бинарный поиск (массив) и Два указателя (в одну сторону).

  • Динамическое программирование (1-D) (Dynamic programming (1-D)): Там хватает одного индекса. Сравнению двух последовательностей нужны два.
  • DP на строках (word break) (DP on strings (word break)): Та страница режет одну строку на куски. Эта выравнивает две последовательности.
  • Бинарный поиск (массив) (Binary search (array)): Версия возрастающей подпоследовательности за n log n использует бинарный поиск. Табличная версия медленнее.
  • Два указателя (в одну сторону) (Two pointers (same direction)): Проверке, является ли строка подпоследовательностью, таблица не нужна. Поиску наибольшей общей нужна.

DP над подпоследовательностями (LIS / LCS / расстояние редактирования): частые ошибки

  • Путают подпоследовательность и подстроку

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

  • Ошибаются на единицу между таблицей и строкой

    Ячейка i указывает на символ с индексом i минус один. Их смешение сдвигает все сравнения.

  • Заполняют базовую строку нулями

    Расстояние редактирования начинается со стоимости удаления целого префикса. Нули там дают неверный ответ.

  • Берут таблицу при большом n

    O(n в квадрате) умирает на 1e5 элементов. У возрастающей подпоследовательности есть форма за n log n.

Паттерн «DP над подпоследовательностями (LIS / LCS / расстояние редактирования)» решает такие задачи с собеседований: Наибольшая общая подпоследовательность, Расстояние редактирования, Наибольшая возрастающая подпоследовательность, Удаления для двух строк, Число различных подпоследовательностей, Чередование строк и Кратчайшая общая надпоследовательность.

  • Наибольшая общая подпоследовательность: Чистая форма для двух последовательностей.
  • Расстояние редактирования: Выбор идёт из трёх соседей, а не из двух.
  • Наибольшая возрастающая подпоследовательность: Одна последовательность, сравниваемая сама с собой.
  • Удаления для двух строк: Общая длина минус удвоенная общая подпоследовательность.
  • Число различных подпоследовательностей: Считается количество способов, а не длина.
  • Чередование строк: Два источника, питающие одну целевую строку.
  • Кратчайшая общая надпоследовательность: Ответ восстанавливают, идя по таблице назад.

DP над подпоследовательностями (LIS / LCS / расстояние редактирования): сложность по времени и памяти

У паттерна «DP над подпоследовательностями (LIS / LCS / расстояние редактирования)» целевая сложность O(n2)/O(nm).

Одна последовательность из n элементов стоит O(n²) времени. Две длины n и m стоят O(nm), и хватает одной строки памяти.

Где этот паттерн стоит в 150 шагах