Задача о наибольшей возрастающей подпоследовательности
Дан массив из
чисел: . Требуется найти в этой последовательности строго возрастающую подпоследовательность наибольшей длины.Определение: |
Наибольшая возрастающая подпоследовательность (НВП) (англ. Longest increasing subsequence - LIS) строки | длины - это последовательность символов строки таких, что , причем - наибольшее из возможных.
Решение за время O(N2)
Построим массив
, где это длина наибольшей возрастающей подпоследовательности, оканчивающейся в элементе, с индексом . Массив будем заполнять постепенно - сначала , потом и т.д. Ответом на нашу задачу будет максимум из всех элементов массива . Заполнение массива будет следующим: если , то искомая последовательность состоит только из числа . Если , то перед числом в подпоследовательности стоит какое-то другое число. Переберем его: это может быть любой элемент , но такой, что . Пусть на каком-то шаге нам надо посчитать очередное . Все элементы массива до него уже посчитаны. Значит наше мы можем посчитать следующим образом: , для всех при условии, что .Пока что мы нашли лишь максимальную длину наибольшей возрастающей подпоследовательности, но саму ее мы вывести не можем. Для восстановления ответа заведем массив
vector<int> Find(vector<int> a) int n = a.size();//размер исходной последовательности vector<int> prev(n); vector<int> d(n); for i = 0...n - 1 d[i] = 1; p[i] = -1; for j = 0...i - 1 if a[j] < a[i] if d[j] + 1 > d[i] d[i] = d[j] + 1; prev[i] = j; int length = d[0], pos = 0;//length - длина наибольшей подпоследовательности, pos - последний символ наибольшей возрастающей подпоследовательности for i = 0...n - 1 if d[i] > length length = d[i]; pos = i; vector<int> answer; while pos != -1 answer.push_back(a[pos]); pos = prev[pos]; reverse(answer); return answer;
Решение за O(NlogN)
Для более быстрого решения данной задачи построим следующую динамику: пусть
vector<int> Find(vector<int> a) int n = a.size(); int length = 0; int d[n], pos[n], prev[n]; prev[0] = -1; d[0] = -INFINITY; for i = 1...n - 1 d[i] = INFINITY; for i = 0...n - 1 int j = binsearch(d[], a[i]); if d[j - 1] < a[i] and a[i] < d[j] d[j] = a[i]; pos[j] = i; prev[i] = pos[d[j - 1]]; length = max(length, j); vector<int> answer; int pos = length; while pos != -INFINITY answer.push_back(a[prev[pos]]); pos = a[prev[pos]]; reverse(answer); return answer;
Ещё одно решение за О(NlogN)
Существует ещё одно решение, которое позволяет нам найти длину наибольшей возрастающей подпоследовательности, но без возможности восстановления данной подпоследовательности. Для этого мы воспользуемся таблом Юнга. Оно обладает таким свойством, что длина первой строки табла и будет являться искомой величиной.
Само табло представляет из себя расположение
+ +...+ различных целых чисел в массиве строк,выровненных по левому краю, где в i-той строке содержится элементов; при этом в каждой строке элементы возрастают слева направо, а элементы каждого столбца возрастают сверху вниз. Чтобы построить табло требуется прочитать очередной элемент a, если он больше либо равен , где j - длина строки, то просто добавить в конец строки, если меньше, то требуется найти первый элемент b, который больше данного a. Поставить элемент а вместо b. С элементом b требуется провести те же действия, что и с a, только уже на i+1 строке табла.Пример построения табла на массиве а (индексация ведётся от 0) :
а : 3,4,9,2,5,1
1 : Берём 3, видим, что 3>0, который расположен на первой строке в ячейке с индексом j=0. Увеличиваем j и в t[i,j]=3
t =
3
2 : Берём 4, видим, что 4>3. Увеличиваем j и в t[i,j]=4
t =
3,4
3 : Аналогично с 9
t =
3,4,9
4 : Берём 2. 2<9, то бин. поиском находим нужную нам позицию z, такую, что a[i,z-1]<=2<a[i,z]. В данном случае это первая позиция. Ставим на место a[i,z] 2 и проделываем такую же операцию, как и для 2, но на строке с индексом i+1.
t =
2,4,9 3
5 : Аналогично с 5
t=
2,4,5 3,9
6 : Аналогично для 1
t =
1,4,5 2,9 3
При построении нам потребуется лишь первая строка, поэтому хранить остальные не имеет смысла.