2.4.2.2 — таблица над двумя строками: шаблон для целого семейства задач сравнения последовательностей2.4.2.2 — a table over two strings: the template for a whole family of sequence-comparison problems

Наибольшая Общая ПодпоследовательностьLongest Common Subsequence

Даны две строки A и B, нужно найти самую длинную подпоследовательность (не обязательно непрерывную — можно пропускать символы, но нельзя менять их порядок), общую для обеих. В отличие от рюкзака (2.4.2.1), где состояние — "первые i предметов", здесь состояние двумерно по-другому — "первые i символов A и первые j символов B" — и это устройство таблицы переиспользуется почти без изменений в редакционном расстоянии (2.4.2.5): разница только в логике перехода. LCS — это шаблон, который стоит понять один раз, чтобы затем узнавать его в целой группе задач сравнения строк/последовательностей.Given two strings A and B, find the longest subsequence (not necessarily contiguous — characters can be skipped, but their order can't change) common to both. Unlike knapsack (2.4.2.1), where the state is "the first i items," here the state is two-dimensional in a different way — "the first i characters of A and the first j characters of B" — and this table layout gets reused almost unchanged in edit distance (2.4.2.5): only the transition logic differs. LCS is a template worth understanding once, so it becomes recognizable across a whole family of string/sequence-comparison problems.

dp[i][j] = dp[i-1][j-1]+1 еслиif A[i]=B[j] O(n·m) время и памятьtime and memory → обобщается в редакционное расстояние (2.4.2.5)→ generalized into edit distance (2.4.2.5)

Идея и выводIdea & derivation

Пусть dp[i][j] — длина LCS первых i символов строки A и первых j символов строки B. Если последние рассматриваемые символы совпадают (A[i-1] == B[j-1] в 0-индексации), их выгодно включить в общую подпоследовательность: dp[i][j] = dp[i-1][j-1] + 1 — расширяем LCS префиксов без этих двух символов на один общий символ. Если символы не совпадают, общий символ на этой паре позиций не образуется, и LCS первых i и j символов не может быть длиннее, чем LCS одного из двух более коротких префиксов: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) — либо отбрасываем последний символ A, либо последний символ B, и берём лучший вариант. Базовый случай: dp[0][j] = dp[i][0] = 0 (пустая строка не имеет общих символов ни с чем).Let dp[i][j] be the length of the LCS of the first i characters of A and the first j characters of B. If the last characters under consideration match (A[i-1] == B[j-1] with 0-indexing), it's always beneficial to include them in the common subsequence: dp[i][j] = dp[i-1][j-1] + 1 — extending the LCS of the prefixes without these two characters by one shared character. If they don't match, no shared character is formed at this pair of positions, and the LCS of the first i and j characters can't be longer than the LCS of one of the two shorter prefixes: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) — either drop A's last character or B's last character, and take the better option. The base case: dp[0][j] = dp[i][0] = 0 (an empty string shares nothing with anything).

Ответ на задачу — dp[n][m], где n = |A|, m = |B|. Чтобы восстановить саму подпоследовательность, а не только её длину, идут обратным проходом от dp[n][m]: если A[i-1] == B[j-1], этот символ входит в LCS — добавляем его в начало ответа и переходим по диагонали к dp[i-1][j-1]; иначе идём в сторону большего из dp[i-1][j] и dp[i][j-1] (при равенстве можно выбрать любое направление — оба ведут к LCS одинаковой длины, хотя, возможно, разной по составу).The answer to the problem is dp[n][m], where n = |A|, m = |B|. To reconstruct the actual subsequence, not just its length, trace backward from dp[n][m]: if A[i-1] == B[j-1], that character is part of the LCS — prepend it to the answer and move diagonally to dp[i-1][j-1]; otherwise move toward whichever of dp[i-1][j] and dp[i][j-1] is larger (on a tie, either direction can be taken — both lead to an LCS of the same length, though possibly a different composition).

dp[i][j] — совпадение символов ведёт по диагонали, иначе — max сверху/слева dp[i-1][j-1] dp[i-1][j] dp[i][j-1] dp[i][j] A[i]=B[j]: +1 иначе: max(верх, лево) та же форма таблицы у edit distance (2.4.2.5) — другая логика перехода

три возможных источника ячейки dp[i][j]: диагональ (совпадение), верх, левоthree possible sources for cell dp[i][j]: diagonal (match), above, left

int lcsLength(const std::string& a, const std::string& b) {
    int n = a.size(), m = b.size();
    std::vector<std::vector<int>> dp(n + 1, std::vector<int>(m + 1, 0));
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            if (a[i-1] == b[j-1])
                dp[i][j] = dp[i-1][j-1] + 1;      // совпадение — по диагонали// a match — diagonally
            else
                dp[i][j] = std::max(dp[i-1][j], dp[i][j-1]);
    return dp[n][m];
}

Демонстрация вживуюLive demonstration

Две короткие строки — демонстрация проигрывает заполнение 2D-таблицы ячейка за ячейкой, слева направо, сверху вниз, а затем восстанавливает саму подпоследовательность обратным проходом от правого нижнего угла.Two short strings — the demo auto-plays filling the 2D table cell by cell, left to right, top to bottom, then reconstructs the actual subsequence with a backward pass from the bottom-right corner.

введите две строки (до 7 символов каждая) и нажмите «запустить»enter two strings (up to 7 characters each) and press "run"

Где встречаетсяWhere it shows up

01

diff-утилиты и системы контроля версийdiff utilities and version control systems

Построчное сравнение, лежащее в основе Unix diff и git diff, ищет наибольшую общую подпоследовательность строк между двумя версиями файла — совпавшие строки образуют LCS, а всё, что осталось за её пределами, помечается как удалённое или добавленное.The line-by-line comparison underlying Unix diff and git diff searches for the longest common subsequence of lines between two file versions — matched lines form the LCS, and everything left outside it is marked as deleted or added.

02

Выравнивание последовательностей ДНК и белковDNA and protein sequence alignment

Поиск общих участков в геномных или белковых последовательностях для оценки эволюционного родства во многих упрощённых постановках сводится напрямую к LCS, а в более точных — к её взвешенному обобщению, редакционному расстоянию (2.4.2.5).Finding shared regions in genomic or protein sequences to assess evolutionary relatedness reduces, in many simplified formulations, directly to LCS, and in more precise ones, to its weighted generalization, edit distance (2.4.2.5).