ОК, сначала я опишу самое простое решение, которое O (N ^ 2), где N - размер коллекции. Также существует решение O (N log N), которое я также опишу. Посмотрите здесь для этого в разделе Эффективные алгоритмы.
Я предполагаю, что индексы массива находятся в диапазоне от 0 до N - 1. Итак, давайте определим DP[i]
как длину LIS (самая длинная возрастающая подпоследовательность), которая заканчивается в элементе с индексом i
. Чтобы вычислить DP[i]
, мы смотрим на все индексы j < i
и проверяем оба значения, если DP[j] + 1 > DP[i]
и array[j] < array[i]
(мы хотим, чтобы оно увеличивалось). Если это правда, мы можем обновить текущий оптимум для DP[i]
. Чтобы найти глобальный оптимум для массива, вы можете взять максимальное значение из DP[0...N - 1]
.
int maxLength = 1, bestEnd = 0;
DP[0] = 1;
prev[0] = -1;
for (int i = 1; i < N; i++)
{
DP[i] = 1;
prev[i] = -1;
for (int j = i - 1; j >= 0; j--)
if (DP[j] + 1 > DP[i] && array[j] < array[i])
{
DP[i] = DP[j] + 1;
prev[i] = j;
}
if (DP[i] > maxLength)
{
bestEnd = i;
maxLength = DP[i];
}
}
Я использую массив prev
, чтобы позже можно было найти фактическую последовательность, а не только ее длину. Просто вернитесь рекурсивно из bestEnd
в цикле, используя prev[bestEnd]
. Значение -1
является знаком остановки.
Хорошо, теперь к более эффективному O(N log N)
решению:
Пусть S[pos]
определяется как наименьшее целое число, заканчивающееся возрастающей последовательностью длины pos
. Теперь переберите каждое целое число X
входного набора и сделайте следующее:
Если X
> последний элемент в S
, тогда добавьте X
в конец S
. Это существенно означает, что мы нашли новый крупнейший LIS
.
В противном случае найдите наименьший элемент в S
, который на >=
, чем X
, и измените его на X
.
Поскольку S
сортируется в любое время, элемент можно найти с помощью двоичного поиска в log(N)
.
Общее время выполнения - N
целых чисел и двоичный поиск для каждого из них - N * log (N) = O (N log N)
Теперь давайте сделаем реальный пример:
Коллекция целых чисел:
2 6 3 4 1 2 9 5 8
Шаги:
0. S = {} - Initialize S to the empty set
1. S = {2} - New largest LIS
2. S = {2, 6} - New largest LIS
3. S = {2, 3} - Changed 6 to 3
4. S = {2, 3, 4} - New largest LIS
5. S = {1, 3, 4} - Changed 2 to 1
6. S = {1, 2, 4} - Changed 3 to 2
7. S = {1, 2, 4, 9} - New largest LIS
8. S = {1, 2, 4, 5} - Changed 9 to 5
9. S = {1, 2, 4, 5, 8} - New largest LIS
Таким образом, длина LIS составляет 5
(размер S).
Для восстановления фактического LIS
мы снова будем использовать родительский массив.
Пусть parent[i]
будет предшественником элемента с индексом i
в LIS
, заканчивающемся элементом с индексом i
.
Чтобы упростить задачу, мы можем сохранить в массиве S
не фактические целые числа, а их индексы (позиции) в наборе. Мы не держим {1, 2, 4, 5, 8}
, но сохраняем {4, 5, 3, 7, 8}
.
Это вход [4] = 1 , вход [5] = 2 , вход [3] = 4 , вход [7] = 5 , вход [8] = 8 .
Если мы правильно обновим родительский массив, фактическая LIS будет:
input[S[lastElementOfS]],
input[parent[S[lastElementOfS]]],
input[parent[parent[S[lastElementOfS]]]],
........................................
Теперь самое важное - как обновить родительский массив? Есть два варианта:
Если X
> последний элемент в S
, то parent[indexX] = indexLastElement
. Это означает, что родительский элемент нового элемента является последним элементом. Мы просто добавляем X
к концу S
.
В противном случае найдите индекс наименьшего элемента в S
, который на >=
, чем X
, и измените его на X
. Здесь parent[indexX] = S[index - 1]
.