15.1. Наибольшая общая подпоследовательность
Определение
Подпоследовательность (subsequence) последовательности $a_1, \ldots, a_n$ — это последовательность, полученная удалением нуля или более элементов без изменения порядка оставшихся. Наибольшая общая подпоследовательность (НОП, longest common subsequence, LCS) последовательностей $a$ и $b$ — это подпоследовательность максимальной длины, являющаяся подпоследовательностью обеих.
Пусть даны две последовательности чисел $a_{1}, \ldots, a_{n}$ и $b_{1}, \ldots, b_{m}$. Требуется найти последовательность $c$ как можно большей длины, которая была бы подпоследовательностью и $a$, и $b$. Например, для последовательностей $2,1,2,3,1,2$ и $2,3,2,2,1$ возможные ответы — $2,2,2$; $2,3,2$; $2,3,1$ и $2,2,1$.
Пусть $l[i, j]$ — длина НОП последовательностей $a_{1}, \ldots, a_{i}$ и $b_{1}, \ldots, b_{j}$. Начальные значения: $l[i, 0] = l[0, j] = 0$. Нас интересует $l[n, m]$.
Как вычислить $l[i, j]$? Посмотрим на $a_{i}$ и $b_{j}$: либо один из них не входит в ответ (то есть $l[i, j]=l[i-1, j]$ или $l[i, j]=l[i, j-1]$), либо они оба совпадают с последним элементом ответа, тогда $l[i, j]=l[i-1, j-1]+1$. Последний случай возможен лишь при $a_{i}=b_{j}$. Поскольку нас интересует наибольшая общая подпоследовательность, нужно взять максимум по всем возможным переходам. Если перебирать состояния в порядке увеличения $i$, а потом $j$, то к моменту рассмотрения состояния $(i, j)$ состояния $(i-1, j)$, $(i, j-1)$ и $(i-1, j-1)$ уже будут рассмотрены. Получаем решение за $O(nm)$.
Значения $l$ для последовательностей $2,1,2,3,1,2$ и $2,3,2,2,1$:
$$ \begin{array}{|cc|c|c|c|c|c|c|} \hline & & & \color{blue}{2} & \color{blue}{3} & \color{blue}{2} & \color{blue}{2} & \color{blue}{1} \\ & & 0 & 1 & 2 & 3 & 4 & 5 \\ \hline & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \hline\color{blue}{2} & 1 & 0 & 1 & 1 & 1 & 1 & 1 \\ \hline\color{blue}{1} & 2 & 0 & 1 & 1 & 1 & 1 & 2 \\ \hline\color{blue}{2} & 3 & 0 & 1 & 1 & 2 & 2 & 2 \\ \hline\color{blue}{3} & 4 & 0 & 1 & 2 & 2 & 2 & 2 \\ \hline\color{blue}{1} & 5 & 0 & 1 & 2 & 2 & 2 & 3 \\ \hline\color{blue}{2} & 6 & 0 & 1 & 2 & 3 & 3 & 3 \\ \hline \end{array} $$Восстановление ответа
Пусть требуется узнать не только длину НОП, но и найти саму подпоследовательность. Откатываемся из конечного состояния $(n, m)$, переходя по оптимальным переходам и добавляя символ в ответ, когда переход идёт «по диагонали».
Оптимизация памяти
Пусть нас интересует только длина НОП, тогда можно обойтись $O(\min(m, n))$ дополнительной памяти. При вычислении $i$-й строки таблицы мы используем только $i$-ю и $(i-1)$-ю строки, поэтому все предыдущие строки можно не хранить.
Свойства
Алгоритм DP для НОП работает за $\Theta(nm)$ в худшем случае. Задача тесно связана с расстоянием Левенштейна и является основой алгоритма Хиршберга.