Как определить самую длинную возрастающую подпоследовательность с помощью динамического программирования?

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


person Tony    schedule 13.04.2010    source источник
comment
Говоря о решении DP, меня удивило, что никто не упомянул тот факт, что LIS можно сократить до LCS.   -  person Salvador Dali    schedule 25.04.2016


Ответы (20)


Хорошо, сначала я опишу простейшее решение - 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 входного набора и выполните следующие действия:

  1. Если X ›последний элемент в S, добавьте X в конец S. По сути, это означает, что мы нашли новый самый большой LIS.

  2. В противном случае найдите наименьший элемент в 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}.

То есть input [4] = 1, input [5] = 2, input [3] = 4, input [7] = < strong> 5, input [8] = 8.

Если мы правильно обновим родительский массив, фактическая LIS будет:

input[S[lastElementOfS]], 
input[parent[S[lastElementOfS]]],
input[parent[parent[S[lastElementOfS]]]],
........................................

Теперь о самом важном - как обновить родительский массив? Есть два варианта:

  1. Если X ›последний элемент в S, то parent[indexX] = indexLastElement. Это означает, что родительский элемент самого нового элемента является последним элементом. Мы просто добавляем X в конец S.

  2. В противном случае найдите индекс наименьшего элемента в S, который >=, чем X, и измените его на X. Вот parent[indexX] = S[index - 1].

person Petar Minchev    schedule 13.04.2010
comment
Условие должно быть if (DP[j] + 1 >= DP[i] && array[j] < array[i]) вместо if (DP[j] + 1 > DP[i] && array[j] < array[i]), вам так не кажется? - person SexyBeast; 02.11.2012
comment
Это не имеет значения. Если DP[j] + 1 == DP[i], то DP[i] не станет лучше с DP[i] = DP[j] + 1. Мы пытаемся оптимизировать DP[i]. - person Petar Minchev; 02.11.2012
comment
Можете ли вы объяснить O(N log N) решение, указанное по указанной вами ссылке? Я не понимаю.. - person SexyBeast; 21.11.2012
comment
Но здесь ответ должен быть [1,2,5,8], 4 идет перед 1 в массиве, как LIS может быть [1,2,4,5,8]? - person SexyBeast; 22.11.2012
comment
@Cupidvogel - Ответ [2,3,4,5,8]. Прочтите внимательно - массив S DOES NOT представляет собой фактическую последовательность. Let S[pos] be defined as the smallest integer that ends an increasing sequence of length pos. - person Petar Minchev; 22.11.2012
comment
Итак, теперь длина S равна 5, поэтому я знаю, что длина LIS равна 5. Но как мне построить из нее фактическую LIS, как вы это делали в своем исходном решении? - person SexyBeast; 22.11.2012
comment
позвольте нам продолжить обсуждение в чате - person Petar Minchev; 22.11.2012
comment
Я обновил свой пост. Если у вас есть вопросы, перейдите в чат, нажав continue this discussion in chat. - person Petar Minchev; 22.11.2012
comment
@PetarMinchev Хорошее объяснение +1. У меня есть одно сомнение, я могу сделать это DP[j] + 1 > DP[i] на DP[j] >= DP[i]? Спасибо. - person Trying; 04.08.2013
comment
Спасибо! Да, вы можете это сделать. - person Petar Minchev; 04.08.2013
comment
Кстати, этот алгоритм принадлежит Крейджу Шенстеду. - person hivert; 09.10.2013
comment
Кажется, что первый алгоритм находит самую длинную возрастающую подпоследовательность, а второй находит самую длинную возрастающую подпоследовательность. - person Snicolas; 14.01.2014
comment
Оба алгоритма находят самую длинную возрастающую подпоследовательность. Что заставило вас подумать, что второй алгоритм отличается? - person Petar Minchev; 14.01.2014
comment
Положение элементов массива не учитывается при упорядочивании. @PetarMinchev - person Snicolas; 14.01.2014
comment
Учитываются позиции. Пожалуйста, прочтите описание алгоритма на en.wikipedia.org/wiki/Longest_increasing_subsequence в разделе Эффективный алгоритмы. - person Petar Minchev; 14.01.2014
comment
@Kraken - Да, используя массив prev, вы найдете только одну подпоследовательность. Чтобы найти все подпоследовательности, вам не нужен массив prev. При восстановлении, когда вы находитесь в индексе cur, вам необходимо проверить все предыдущие индексы icur, такие как DP[i] = DP[cur] - 1 и array[i] < array[cur]. Вы можете использовать рекурсию, чтобы распечатать все. - person Petar Minchev; 19.01.2014
comment
@PetarMinchev Это будет использовать первый подход, верно? А как насчет второго? Как мне выполнить весь процесс в nlogn? - person Kraken; 20.01.2014
comment
@Kraken - Ну, вы можете сделать то же самое для второго подхода. Чтобы восстановить все последовательности, вам не нужен массив parent. Просто создайте еще один массив DP, который будет содержать длину LIS для каждой позиции в массиве. Когда длина S увеличивается еще на один элемент, затем DP[i] = length(S). Это был случай 1. Для случая 2 - DP[i] = positionOfSmallestElementFoundWithBinarySearch. После получения DP вы можете сделать то же самое, что и в моем предыдущем комментарии. - person Petar Minchev; 20.01.2014
comment
Но эффективное выполнение процесса, описанного в моем предыдущем комментарии, - это совсем другая история - then you have to examine all previous indices i < cur, such that DP[i] = DP[cur] - 1 and array[i] < array[cur]. - person Petar Minchev; 20.01.2014
comment
Я не часто вижу столь внятные объяснения. Это не только очень легко понять, потому что в объяснении устраняются сомнения, но и решает любые проблемы с реализацией, которые могут возникнуть. Потрясающие. - person Boyang; 16.04.2014
comment
+1 пошаговый пример. Но: 1. (основной) При первом чтении я подумал, что каждый шаг в вашем примере производит возрастающую подпоследовательность, причем последний шаг дает запрошенную подпоследовательность. Действительно, ваши комментарии дают понять это (например, Новая крупнейшая LIS). Конечно, это не так, но легко запутаться: каждый шаг дает возрастающую последовательность элементов исходного массива T, но НЕ ПОДПоследовательность T; 2. (второстепенный) заданная структура данных не имеет конца естественного порядка и правой стороны. Однако вы определяете последовательность, которая будет построена как набор, например, Инициализировать S для пустого набора - person candide; 06.05.2014
comment
@PetarMinchev Привет, Петар, я решаю проблему поиска ВСЕХ LIS. Я использовал второй метод сложности O (nlogn). Но если я распечатаю все решение с использованием рекурсии в структуре DP, не станет ли оно медленнее? Есть ли какой-нибудь оптимизированный способ распечатать всю LIS? - person Naman; 19.05.2014
comment
@PetarMinchev У меня возникли проблемы с пониманием массива родителей. Вы можете перечислить, как обновляется родительский массив на каждой итерации? - person Ben; 05.07.2014
comment
Как работает ваш пример? Ваш второй алгоритм с входом {2 6 3 4 1 2 9 5 8} имеет LIS {2 3 4 5 8 }, а НЕ {1, 2, 4, 5, 8} . Вы можете объяснить, что там происходит? - person seeker; 02.08.2014
comment
S не содержит LIS. Прочитать еще раз. Пусть S [pos] определяется как наименьшее целое число, которое могло бы закончить возрастающую последовательность длины pos. Для восстановления LIS вам понадобится родительский массив. - person Petar Minchev; 02.08.2014
comment
geeksforgeeks.org/, вероятно, лучшее объяснение этого я видел - person eb80; 03.08.2014
comment
@PetarMinchev Прекрасное объяснение. Намного лучше, чем geeksforgeeks.org и сама Википедия. Большое спасибо за то, что объяснили это так кратко и ясно. - person Ali; 26.08.2014
comment
я не мог понять 1) почему у нас DP[j] + 1 > DP[i] это состояние? 2) как мы узнаем, что array[j] < array[i] собирается проверить, что нет чисел больше array[j] перед j? - person bicepjai; 23.09.2014
comment
1) Условие состоит в том, чтобы убедиться, что меньшая последовательность не перезаписывает последовательность большей длины. Если DP[i] = 4 и DP[j] + 1 = 2, мы не хотим, чтобы DP[i] стал 2 и был перезаписан с меньшей длиной. - person Petar Minchev; 23.09.2014
comment
2) Вы, кажется, запутались здесь. Перед j могут быть числа больше array[j], и это нормально. Важно то, что мы знаем, что DP[j] содержит длину последовательности только строго возрастающих чисел. - person Petar Minchev; 23.09.2014
comment
@Peter спасибо за ваше объяснение. Я все еще пытаюсь создать массив LIS, построив S. Обновление массива parent не имеет смысла. Вы делаете это в том же цикле, в котором находите S? - person harryg; 28.11.2014
comment
Да, сразу после изменения чего-то в S вы тоже меняете parent. S содержит индексы. S[pos] = индекс наименьшего возможного целого числа, которое является последним числом в LIS длины pos. При обновлении S есть только два случая, и для каждого случая вы соответственно обновляете родительский элемент. Два случая описаны в конце - добавление в конец S или обновление S где-то внутри него. - person Petar Minchev; 29.11.2014
comment
@PetarMinchev Привет, не могли бы вы объяснить, как найти настоящую LIS с использованием алгоритма O (N log N)? - person MortalMan; 09.03.2016
comment
кроме того, как может S[lastElementOfS] существовать, если S [8] не существует? - person MortalMan; 09.03.2016
comment
Вы сказали это об обновлении родительского массива - We just prepend X to the end of S. Разве это не изменение S, а не родительский массив? - person MortalMan; 09.03.2016
comment
Мне очень трудно понять, почему все определения для DP [i] - это длина LIS (самая длинная возрастающая подпоследовательность), которая заканчивается элементом с индексом i. Почему мы определяем это как таковое? Не могли бы мы определить DP [i] как длину самой длинной возрастающей последовательности, которая присутствует в массиве до индекса i? - person Greener; 13.03.2016
comment
@Greener: потому что, если вы определяете DP [i] как длину LIS, это не поможет вам построить подструктуру, которую вы можете использовать для получения более высокого порядка ответов. Затем, со следующим значением с индексом i + 1, как узнать, увеличивает ли оно LIS в любой точке DP [0..i]. это просто становится похожим на жадное решение с полиномиальным временем. - person arviman; 12.07.2016
comment
Мне довольно трудно разобрать это: пусть parent [i] будет предшественником элемента с индексом i в LIS, заканчивающегося элементом с индексом i. - это Пусть parent [i] будет предшественником элемента с индексом i в LIS, заканчивающегося элементом с индексом i, или Пусть parent [i] будет предшественником элемента с индексом i в LIS заканчивается элементом с индексом i. - person arviman; 12.07.2016
comment
Еще одно объяснение, которое возглавит блог GeekgForGeeks: D lightoj.com/ - person Jemshit Iskenderov; 24.12.2019
comment
лучшее объяснение, которое я видел: youtube.com/watch?v=22s1xxRvy28 - person Eugene D. Gubenkov; 12.09.2020

Объяснение Петра Минчева помогло мне прояснить ситуацию, но мне было трудно разобрать, что именно было, поэтому я сделал реализацию Python с чрезмерно описательными именами переменных и множеством комментариев. Я сделал наивное рекурсивное решение, решение O (n ^ 2) и решение O (n log n).

Надеюсь, это поможет прояснить алгоритмы!

Рекурсивное решение

def recursive_solution(remaining_sequence, bigger_than=None):
    """Finds the longest increasing subsequence of remaining_sequence that is      
    bigger than bigger_than and returns it.  This solution is O(2^n)."""

    # Base case: nothing is remaining.                                             
    if len(remaining_sequence) == 0:
        return remaining_sequence

    # Recursive case 1: exclude the current element and process the remaining.     
    best_sequence = recursive_solution(remaining_sequence[1:], bigger_than)

    # Recursive case 2: include the current element if it's big enough.            
    first = remaining_sequence[0]

    if (first > bigger_than) or (bigger_than is None):

        sequence_with = [first] + recursive_solution(remaining_sequence[1:], first)

        # Choose whichever of case 1 and case 2 were longer.                         
        if len(sequence_with) >= len(best_sequence):
            best_sequence = sequence_with

    return best_sequence                                                        

Решение для динамического программирования O (n ^ 2)

def dynamic_programming_solution(sequence):
    """Finds the longest increasing subsequence in sequence using dynamic          
    programming.  This solution is O(n^2)."""

    longest_subsequence_ending_with = []
    backreference_for_subsequence_ending_with = []
    current_best_end = 0

    for curr_elem in range(len(sequence)):
        # It's always possible to have a subsequence of length 1.                    
        longest_subsequence_ending_with.append(1)

        # If a subsequence is length 1, it doesn't have a backreference.             
        backreference_for_subsequence_ending_with.append(None)

        for prev_elem in range(curr_elem):
            subsequence_length_through_prev = (longest_subsequence_ending_with[prev_elem] + 1)

            # If the prev_elem is smaller than the current elem (so it's increasing)   
            # And if the longest subsequence from prev_elem would yield a better       
            # subsequence for curr_elem.                                               
            if ((sequence[prev_elem] < sequence[curr_elem]) and
                    (subsequence_length_through_prev >
                         longest_subsequence_ending_with[curr_elem])):

                # Set the candidate best subsequence at curr_elem to go through prev.    
                longest_subsequence_ending_with[curr_elem] = (subsequence_length_through_prev)
                backreference_for_subsequence_ending_with[curr_elem] = prev_elem
                # If the new end is the best, update the best.    

        if (longest_subsequence_ending_with[curr_elem] >
                longest_subsequence_ending_with[current_best_end]):
            current_best_end = curr_elem
            # Output the overall best by following the backreferences.  

    best_subsequence = []
    current_backreference = current_best_end

    while current_backreference is not None:
        best_subsequence.append(sequence[current_backreference])
        current_backreference = (backreference_for_subsequence_ending_with[current_backreference])

    best_subsequence.reverse()

    return best_subsequence                                                   

Решение для динамического программирования O (n log n)

def find_smallest_elem_as_big_as(sequence, subsequence, elem):
    """Returns the index of the smallest element in subsequence as big as          
    sequence[elem].  sequence[elem] must not be larger than every element in       
    subsequence.  The elements in subsequence are indices in sequence.  Uses       
    binary search."""

    low = 0
    high = len(subsequence) - 1

    while high > low:
        mid = (high + low) / 2
        # If the current element is not as big as elem, throw out the low half of    
        # sequence.                                                                  
        if sequence[subsequence[mid]] < sequence[elem]:
            low = mid + 1
            # If the current element is as big as elem, throw out everything bigger, but 
        # keep the current element.                                                  
        else:
            high = mid

    return high


def optimized_dynamic_programming_solution(sequence):
    """Finds the longest increasing subsequence in sequence using dynamic          
    programming and binary search (per                                             
    http://en.wikipedia.org/wiki/Longest_increasing_subsequence).  This solution   
    is O(n log n)."""

    # Both of these lists hold the indices of elements in sequence and not the        
    # elements themselves.                                                         
    # This list will always be sorted.                                             
    smallest_end_to_subsequence_of_length = []

    # This array goes along with sequence (not                                     
    # smallest_end_to_subsequence_of_length).  Following the corresponding element 
    # in this array repeatedly will generate the desired subsequence.              
    parent = [None for _ in sequence]

    for elem in range(len(sequence)):
        # We're iterating through sequence in order, so if elem is bigger than the   
        # end of longest current subsequence, we have a new longest increasing          
        # subsequence.                                                               
        if (len(smallest_end_to_subsequence_of_length) == 0 or
                    sequence[elem] > sequence[smallest_end_to_subsequence_of_length[-1]]):
            # If we are adding the first element, it has no parent.  Otherwise, we        
            # need to update the parent to be the previous biggest element.            
            if len(smallest_end_to_subsequence_of_length) > 0:
                parent[elem] = smallest_end_to_subsequence_of_length[-1]
            smallest_end_to_subsequence_of_length.append(elem)
        else:
            # If we can't make a longer subsequence, we might be able to make a        
            # subsequence of equal size to one of our earlier subsequences with a         
            # smaller ending number (which makes it easier to find a later number that 
            # is increasing).                                                          
            # Thus, we look for the smallest element in                                
            # smallest_end_to_subsequence_of_length that is at least as big as elem       
            # and replace it with elem.                                                
            # This preserves correctness because if there is a subsequence of length n 
            # that ends with a number smaller than elem, we could add elem on to the   
            # end of that subsequence to get a subsequence of length n+1.              
            location_to_replace = find_smallest_elem_as_big_as(sequence, smallest_end_to_subsequence_of_length, elem)
            smallest_end_to_subsequence_of_length[location_to_replace] = elem
            # If we're replacing the first element, we don't need to update its parent 
            # because a subsequence of length 1 has no parent.  Otherwise, its parent  
            # is the subsequence one shorter, which we just added onto.                
            if location_to_replace != 0:
                parent[elem] = (smallest_end_to_subsequence_of_length[location_to_replace - 1])

    # Generate the longest increasing subsequence by backtracking through parent.  
    curr_parent = smallest_end_to_subsequence_of_length[-1]
    longest_increasing_subsequence = []

    while curr_parent is not None:
        longest_increasing_subsequence.append(sequence[curr_parent])
        curr_parent = parent[curr_parent]

    longest_increasing_subsequence.reverse()

    return longest_increasing_subsequence         
person Sam King    schedule 09.11.2013
comment
Ваш оптимизированный алгоритм неверен. Пожалуйста, проверьте случай, когда последовательность равна 5, 19, 5, 81, 50, 28, 29, 1, 83, 23. Ваш алгоритм возвращает 5, 19, 81, 83, когда он должен возвращать 5, 19, 28, 29, 83. . - person Johan S; 27.01.2014
comment
Вы уверены? Когда я запускаю optimized_dynamic_programming_solution ([5, 19, 5, 81, 50, 28, 29, 1, 83, 23]) на моем компьютере, он возвращает [5, 19, 28, 29, 83]. - person Sam King; 24.02.2014
comment
Хотя я ценю приложенные здесь усилия, у меня болят глаза, когда я смотрю на эти псевдокоды. - person mostruash; 12.06.2014
comment
mostruash - я не совсем понимаю, что вы имеете в виду. В моем ответе нет псевдокода; у него есть Python. - person Sam King; 12.06.2014
comment
Что ж, он, скорее всего, имел в виду ваше соглашение об именах переменных и функций, которое также заставило меня `` болеть '' - person Adilli Adil; 04.01.2015
comment
Если вы имеете в виду мое соглашение об именах, я в основном следую Руководству по стилю Google Python. Если вы выступаете за короткие имена переменных, я предпочитаю описательные имена переменных, потому что они упрощают понимание и поддержку кода. - person Sam King; 05.01.2015
comment
Круто. но не думаете ли вы, что использование bisect вместо find_smallest_elem_as_big_as прояснит это? - person ihadanny; 05.04.2015
comment
Для реальной реализации, вероятно, имеет смысл использовать bisect. Для демонстрации того, как работает алгоритм и его характеристики производительности, я старался сделать все как можно более примитивным. - person Sam King; 05.04.2015
comment
@SamKing, откуда вы взяли рекурсивное решение? - person Will; 06.11.2015
comment
[править: nvm, кажется, я нашел] jeffe.cs. illinois.edu/teaching/algorithms/notes/ - person Will; 06.11.2015
comment
Это очень полезное решение, но я должен отметить, что я принял TypeError: list indices must be integers or slices, not float ошибку в коде динамического решения из-за строки mid = (high + low) / 2. Я думаю, это должно быть mid = (high + low) // 2 или mid = int((high + low) / 2) - person Alperen; 09.10.2017
comment
Весь код - это рабочий код python2. Если вы запускаете его на python3, у вас могут возникнуть проблемы. - person Sam King; 09.10.2017

Говоря о решении DP, меня удивило, что никто не упомянул тот факт, что LIS можно свести к LCS. Все, что вам нужно сделать, это отсортировать копию исходной последовательности, удалить все дубликаты и выполнить LCS из них. В псевдокоде это:

def LIS(S):
    T = sort(S)
    T = removeDuplicates(T)
    return LCS(S, T)

И полная реализация написана на Go. Вам не нужно поддерживать всю матрицу DP n ^ 2, если вам не нужно восстанавливать решение.

func lcs(arr1 []int) int {
    arr2 := make([]int, len(arr1))
    for i, v := range arr1 {
        arr2[i] = v
    }
    sort.Ints(arr1)
    arr3 := []int{}
    prev := arr1[0] - 1
    for _, v := range arr1 {
        if v != prev {
            prev = v
            arr3 = append(arr3, v)
        }
    }

    n1, n2 := len(arr1), len(arr3)

    M := make([][]int, n2 + 1)
    e := make([]int, (n1 + 1) * (n2 + 1))
    for i := range M {
        M[i] = e[i * (n1 + 1):(i + 1) * (n1 + 1)]
    }

    for i := 1; i <= n2; i++ {
        for j := 1; j <= n1; j++ {
            if arr2[j - 1] == arr3[i - 1] {
                M[i][j] = M[i - 1][j - 1] + 1
            } else if M[i - 1][j] > M[i][j - 1] {
                M[i][j] = M[i - 1][j]
            } else {
                M[i][j] = M[i][j - 1]
            }
        }
    }

    return M[n2][n1]
}
person Salvador Dali    schedule 25.04.2016
comment
@max да, это вроде написано в ответе с помощью матрицы LCS, n ^ 2 - person Salvador Dali; 28.06.2017

Следующая реализация C ++ включает также некоторый код, который строит фактическую самую длинную возрастающую подпоследовательность с использованием массива с именем prev.

std::vector<int> longest_increasing_subsequence (const std::vector<int>& s)
{
    int best_end = 0;
    int sz = s.size();

    if (!sz)
        return std::vector<int>();

    std::vector<int> prev(sz,-1);
    std::vector<int> memo(sz, 0);

    int max_length = std::numeric_limits<int>::min();

    memo[0] = 1;

    for ( auto i = 1; i < sz; ++i)
    {
        for ( auto j = 0; j < i; ++j)
        {
            if ( s[j] < s[i] && memo[i] < memo[j] + 1 )
            {
                memo[i] =  memo[j] + 1;
                prev[i] =  j;
            }
        }

        if ( memo[i] > max_length ) 
        {
            best_end = i;
            max_length = memo[i];
        }
    }

    // Code that builds the longest increasing subsequence using "prev"
    std::vector<int> results;
    results.reserve(sz);

    std::stack<int> stk;
    int current = best_end;

    while (current != -1)
    {
        stk.push(s[current]);
        current = prev[current];
    }

    while (!stk.empty())
    {
        results.push_back(stk.top());
        stk.pop();
    }

    return results;
}

Реализация без стека просто переворачивает вектор

#include <iostream>
#include <vector>
#include <limits>
std::vector<int> LIS( const std::vector<int> &v ) {
  auto sz = v.size();
  if(!sz)
    return v;
  std::vector<int> memo(sz, 0);
  std::vector<int> prev(sz, -1);
  memo[0] = 1;
  int best_end = 0;
  int max_length = std::numeric_limits<int>::min();
  for (auto i = 1; i < sz; ++i) {
    for ( auto j = 0; j < i ; ++j) {
      if (s[j] < s[i] && memo[i] < memo[j] + 1) {
        memo[i] = memo[j] + 1;
        prev[i] = j;
      }
    }
    if(memo[i] > max_length) {
      best_end = i;
      max_length = memo[i];
    }
  }

  // create results
  std::vector<int> results;
  results.reserve(v.size());
  auto current = best_end;
  while (current != -1) {
    results.push_back(s[current]);
    current = prev[current];
  }
  std::reverse(results.begin(), results.end());
  return results;
}
person bjackfly    schedule 28.10.2013

Вот три шага оценки проблемы с точки зрения динамического программирования:

  1. Определение повторения: maxLength (i) == 1 + maxLength (j), где 0 ‹j‹ i и array [i]> array [j]
  2. Граница параметра повторения: в качестве параметра могут быть переданы подпоследовательности от 0 до i - 1
  3. Порядок оценки: по мере увеличения подпоследовательности ее следует оценивать от 0 до n.

Если мы возьмем в качестве примера последовательность {0, 8, 2, 3, 7, 9}, по индексу:

  • [0] мы получим подпоследовательность {0} в качестве базового случая
  • [1] у нас есть 1 новая подпоследовательность {0, 8}
  • [2] попытка оценить две новые последовательности {0, 8, 2} и {0, 2} путем добавления элемента с индексом 2 к существующим подпоследовательностям - только одна действительна, поэтому добавляется только третья возможная последовательность {0, 2} к списку параметров ...

Вот рабочий код C ++ 11:

#include <iostream>
#include <vector>

int getLongestIncSub(const std::vector<int> &sequence, size_t index, std::vector<std::vector<int>> &sub) {
    if(index == 0) {
        sub.push_back(std::vector<int>{sequence[0]});
        return 1;
    }

    size_t longestSubSeq = getLongestIncSub(sequence, index - 1, sub);
    std::vector<std::vector<int>> tmpSubSeq;
    for(std::vector<int> &subSeq : sub) {
        if(subSeq[subSeq.size() - 1] < sequence[index]) {
            std::vector<int> newSeq(subSeq);
            newSeq.push_back(sequence[index]);
            longestSubSeq = std::max(longestSubSeq, newSeq.size());
            tmpSubSeq.push_back(newSeq);
        }
    }
    std::copy(tmpSubSeq.begin(), tmpSubSeq.end(),
              std::back_insert_iterator<std::vector<std::vector<int>>>(sub));

    return longestSubSeq;
}

int getLongestIncSub(const std::vector<int> &sequence) {
    std::vector<std::vector<int>> sub;
    return getLongestIncSub(sequence, sequence.size() - 1, sub);
}

int main()
{
    std::vector<int> seq{0, 8, 2, 3, 7, 9};
    std::cout << getLongestIncSub(seq);
    return 0;
}
person Iuri Covalisin    schedule 13.11.2013
comment
Я думаю, что определение повторения должно быть maxLength (i) = 1 + max (maxLength (j)) для 0 ‹j‹ i и array [i] ›array [j], а не без max (). - person Slothworks; 16.10.2016

Вот реализация алгоритма O (n ^ 2) в Scala:

object Solve {
  def longestIncrSubseq[T](xs: List[T])(implicit ord: Ordering[T]) = {
    xs.foldLeft(List[(Int, List[T])]()) {
      (sofar, x) =>
        if (sofar.isEmpty) List((1, List(x)))
        else {
          val resIfEndsAtCurr = (sofar, xs).zipped map {
            (tp, y) =>
              val len = tp._1
              val seq = tp._2
              if (ord.lteq(y, x)) {
                (len + 1, x :: seq) // reversely recorded to avoid O(n)
              } else {
                (1, List(x))
              }
          }
          sofar :+ resIfEndsAtCurr.maxBy(_._1)
        }
    }.maxBy(_._1)._2.reverse
  }

  def main(args: Array[String]) = {
    println(longestIncrSubseq(List(
      0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15)))
  }
}
person lcn    schedule 14.12.2014

Вот еще одна реализация JAVA O (n ^ 2). Нет рекурсии / мемоизации для генерации фактической подпоследовательности. Просто массив строк, в котором хранится фактическая LIS на каждом этапе, и массив для хранения длины LIS для каждого элемента. Довольно просто. Посмотри:

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * Created by Shreyans on 4/16/2015
 */

class LNG_INC_SUB//Longest Increasing Subsequence
{
    public static void main(String[] args) throws Exception
    {
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        System.out.println("Enter Numbers Separated by Spaces to find their LIS\n");
        String[] s1=br.readLine().split(" ");
        int n=s1.length;
        int[] a=new int[n];//Array actual of Numbers
        String []ls=new String[n];// Array of Strings to maintain LIS for every element
        for(int i=0;i<n;i++)
        {
            a[i]=Integer.parseInt(s1[i]);
        }
        int[]dp=new int[n];//Storing length of max subseq.
        int max=dp[0]=1;//Defaults
        String seq=ls[0]=s1[0];//Defaults
        for(int i=1;i<n;i++)
        {
            dp[i]=1;
            String x="";
            for(int j=i-1;j>=0;j--)
            {
                //First check if number at index j is less than num at i.
                // Second the length of that DP should be greater than dp[i]
                // -1 since dp of previous could also be one. So we compare the dp[i] as empty initially
                if(a[j]<a[i]&&dp[j]>dp[i]-1)
                {
                    dp[i]=dp[j]+1;//Assigning temp length of LIS. There may come along a bigger LIS of a future a[j]
                    x=ls[j];//Assigning temp LIS of a[j]. Will append a[i] later on
                }
            }
            x+=(" "+a[i]);
            ls[i]=x;
            if(dp[i]>max)
            {
                max=dp[i];
                seq=ls[i];
            }
        }
        System.out.println("Length of LIS is: " + max + "\nThe Sequence is: " + seq);
    }
}

Код в действии: http://ideone.com/sBiOQx

person bholagabbar    schedule 16.04.2015

вот реализация java O (nlogn)

import java.util.Scanner;

public class LongestIncreasingSeq {


    private static int binarySearch(int table[],int a,int len){

        int end = len-1;
        int beg = 0;
        int mid = 0;
        int result = -1;
        while(beg <= end){
            mid = (end + beg) / 2;
            if(table[mid] < a){
                beg=mid+1;
                result = mid;
            }else if(table[mid] == a){
                return len-1;
            }else{
                end = mid-1;
            }
        }
        return result;
    }
    
    public static void main(String[] args) {        
        
//        int[] t = {1, 2, 5,9,16};
//        System.out.println(binarySearch(t , 9, 5));
        Scanner in = new Scanner(System.in);
        int size = in.nextInt();//4;
        
        int A[] = new int[size];
        int table[] = new int[A.length]; 
        int k = 0;
        while(k<size){
            A[k++] = in.nextInt();
            if(k<size-1)
                in.nextLine();
        }        
        table[0] = A[0];
        int len = 1; 
        for (int i = 1; i < A.length; i++) {
            if(table[0] > A[i]){
                table[0] = A[i];
            }else if(table[len-1]<A[i]){
                table[len++]=A[i];
            }else{
                table[binarySearch(table, A[i],len)+1] = A[i];
            }            
        }
        System.out.println(len);
    }    
}

// TreeSet можно использовать

person fatih tekin    schedule 14.05.2015

Это можно решить за O (n ^ 2) с помощью динамического программирования. Код Python для того же будет выглядеть так: -

def LIS(numlist):
    LS = [1]
    for i in range(1, len(numlist)):
        LS.append(1)
        for j in range(0, i):
            if numlist[i] > numlist[j] and LS[i]<=LS[j]:
                LS[i] = 1 + LS[j]
    print LS
    return max(LS)

numlist = map(int, raw_input().split(' '))
print LIS(numlist)

Для ввода: 5 19 5 81 50 28 29 1 83 23

вывод будет: [1, 2, 1, 3, 3, 3, 4, 1, 5, 3] 5

List_index выходного списка - это list_index входного списка. Значение данного list_index в выходном списке обозначает самую длинную увеличивающуюся длину подпоследовательности для этого list_index.

person Barun Sharma    schedule 13.04.2014

Это реализация Java в O (n ^ 2). Я просто не использовал двоичный поиск, чтобы найти наименьший элемент в S, который> =, чем X. Я просто использовал цикл for. Использование двоичного поиска сделает сложность в O (n logn)

public static void olis(int[] seq){

    int[] memo = new int[seq.length];

    memo[0] = seq[0];
    int pos = 0;

    for (int i=1; i<seq.length; i++){

        int x = seq[i];

            if (memo[pos] < x){ 
                pos++;
                memo[pos] = x;
            } else {

                for(int j=0; j<=pos; j++){
                    if (memo[j] >= x){
                        memo[j] = x;
                        break;
                    }
                }
            }
            //just to print every step
            System.out.println(Arrays.toString(memo));
    }

    //the final array with the LIS
    System.out.println(Arrays.toString(memo));
    System.out.println("The length of lis is " + (pos + 1));

}
person Shashank Agarwal    schedule 08.03.2015

проверить код в java для самой длинной возрастающей подпоследовательности с элементами массива

http://ideone.com/Nd2eba

/**
 **    Java Program to implement Longest Increasing Subsequence Algorithm
 **/

import java.util.Scanner;

/** Class  LongestIncreasingSubsequence **/
 class  LongestIncreasingSubsequence
{
    /** function lis **/
    public int[] lis(int[] X)
    {        
        int n = X.length - 1;
        int[] M = new int[n + 1];  
        int[] P = new int[n + 1]; 
        int L = 0;

        for (int i = 1; i < n + 1; i++)
        {
            int j = 0;

            /** Linear search applied here. Binary Search can be applied too.
                binary search for the largest positive j <= L such that 
                X[M[j]] < X[i] (or set j = 0 if no such value exists) **/

            for (int pos = L ; pos >= 1; pos--)
            {
                if (X[M[pos]] < X[i])
                {
                    j = pos;
                    break;
                }
            }            
            P[i] = M[j];
            if (j == L || X[i] < X[M[j + 1]])
            {
                M[j + 1] = i;
                L = Math.max(L,j + 1);
            }
        }

        /** backtrack **/

        int[] result = new int[L];
        int pos = M[L];
        for (int i = L - 1; i >= 0; i--)
        {
            result[i] = X[pos];
            pos = P[pos];
        }
        return result;             
    }

    /** Main Function **/
    public static void main(String[] args) 
    {    
        Scanner scan = new Scanner(System.in);
        System.out.println("Longest Increasing Subsequence Algorithm Test\n");

        System.out.println("Enter number of elements");
        int n = scan.nextInt();
        int[] arr = new int[n + 1];
        System.out.println("\nEnter "+ n +" elements");
        for (int i = 1; i <= n; i++)
            arr[i] = scan.nextInt();

        LongestIncreasingSubsequence obj = new LongestIncreasingSubsequence(); 
        int[] result = obj.lis(arr);       

        /** print result **/ 

        System.out.print("\nLongest Increasing Subsequence : ");
        for (int i = 0; i < result.length; i++)
            System.out.print(result[i] +" ");
        System.out.println();
    }
}
person jayant singh    schedule 27.10.2015

Это можно решить за O (n ^ 2) с помощью динамического программирования.

Обработайте входные элементы по порядку и ведите список кортежей для каждого элемента. Каждый кортеж (A, B) для элемента i будет обозначать, A = длина самой длинной возрастающей подпоследовательности, заканчивающейся на i, и B = индекс предшественника list [i] в ​​самой длинной возрастающей подпоследовательности, заканчивающейся на list [i ].

Начиная с элемента 1, список кортежей для элемента 1 будет [(1,0)] для элемента i, просканируйте список 0..i и найдите список элементов [k] так, чтобы list [k] ‹list [i] , значение A для элемента i, Ai будет Ak + 1, а Bi будет k. Если таких элементов несколько, добавьте их в список кортежей для элемента i.

В конце найдите все элементы с максимальным значением A (длина LIS, заканчивающаяся элементом) и выполните возврат, используя кортежи, чтобы получить список.

Я поделился кодом для этого на http://www.edufyme.com/code/?id=66f041e16a60928b05a7e228a89c3799

person Somil Bhandari    schedule 16.11.2015
comment
Вы должны включить код в свой ответ, так как ссылки могут сломаться. - person NathanOliver; 16.11.2015

O (n ^ 2) реализация Java:

void LIS(int arr[]){
        int maxCount[]=new int[arr.length];
        int link[]=new int[arr.length];
        int maxI=0;
        link[0]=0;
        maxCount[0]=0;

        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < i; j++) {
                if(arr[j]<arr[i] && ((maxCount[j]+1)>maxCount[i])){
                    maxCount[i]=maxCount[j]+1;
                    link[i]=j;
                    if(maxCount[i]>maxCount[maxI]){
                        maxI=i;
                    }
                }
            }
        }


        for (int i = 0; i < link.length; i++) {
            System.out.println(arr[i]+"   "+link[i]);
        }
        print(arr,maxI,link);

    }

    void print(int arr[],int index,int link[]){
        if(link[index]==index){
            System.out.println(arr[index]+" ");
            return;
        }else{
            print(arr, link[index], link);
            System.out.println(arr[index]+" ");
        }
    }
person Mostafizar    schedule 17.02.2017

def longestincrsub(arr1):
    n=len(arr1)
    l=[1]*n
    for i in range(0,n):
        for j in range(0,i)  :
            if arr1[j]<arr1[i] and l[i]<l[j] + 1:
                l[i] =l[j] + 1
    l.sort()
    return l[-1]
arr1=[10,22,9,33,21,50,41,60]
a=longestincrsub(arr1)
print(a)

хотя есть способ, которым вы можете решить эту проблему за время O (nlogn) (это решает за время O (n ^ 2)), но все же этот способ дает подход динамического программирования, который также хорош.

person ravi tanwar    schedule 06.07.2018

Вот мое решение Leetcode с использованием двоичного поиска: ->

class Solution:
    def binary_search(self,s,x):
        low=0
        high=len(s)-1
        flag=1
        while low<=high:
              mid=(high+low)//2
              if s[mid]==x:
                 flag=0
                 break
              elif s[mid]<x:
                  low=mid+1
              else:
                 high=mid-1
        if flag:
           s[low]=x
        return s

    def lengthOfLIS(self, nums: List[int]) -> int:
         if not nums:
            return 0
         s=[]
         s.append(nums[0])
         for i in range(1,len(nums)):
             if s[-1]<nums[i]:
                s.append(nums[i])
             else:
                 s=self.binary_search(s,nums[i])
         return len(s)
person Abhinav Vajpeyi    schedule 27.06.2019

Простейшее решение LIS на C ++ с временной сложностью O (nlog (n))

#include <iostream>
#include "vector"
using namespace std;

// binary search (If value not found then it will return the index where the value should be inserted)
int ceilBinarySearch(vector<int> &a,int beg,int end,int value)
{
    if(beg<=end)
    {
        int mid = (beg+end)/2;
        if(a[mid] == value)
            return mid;
        else if(value < a[mid])
            return ceilBinarySearch(a,beg,mid-1,value);
        else
            return ceilBinarySearch(a,mid+1,end,value);

    return 0;
    }

    return beg;

}
int lis(vector<int> arr)
{
    vector<int> dp(arr.size(),0);
    int len = 0;
    for(int i = 0;i<arr.size();i++)
    {
        int j = ceilBinarySearch(dp,0,len-1,arr[i]);
        dp[j] = arr[i];
        if(j == len)
            len++;

    }
    return len;
}

int main()
{
    vector<int> arr  {2, 5,-1,0,6,1,2};
    cout<<lis(arr);
    return 0;
}

ВЫХОД:
4

person Ashish kumar    schedule 17.07.2019

Самая длинная возрастающая подпоследовательность (Java)

import java.util.*;

class ChainHighestValue implements Comparable<ChainHighestValue>{
    int highestValue;
    int chainLength;
    ChainHighestValue(int highestValue,int chainLength) {
        this.highestValue = highestValue;
        this.chainLength = chainLength;
    }
    @Override
    public int compareTo(ChainHighestValue o) {
       return this.chainLength-o.chainLength;
    }

}


public class LongestIncreasingSubsequenceLinkedList {


    private static LinkedList<Integer> LongestSubsequent(int arr[], int size){
        ArrayList<LinkedList<Integer>> seqList=new ArrayList<>();
        ArrayList<ChainHighestValue> valuePairs=new ArrayList<>();
        for(int i=0;i<size;i++){
            int currValue=arr[i];
            if(valuePairs.size()==0){
                LinkedList<Integer> aList=new LinkedList<>();
                aList.add(arr[i]);
                seqList.add(aList);
                valuePairs.add(new ChainHighestValue(arr[i],1));

            }else{
                try{
                    ChainHighestValue heighestIndex=valuePairs.stream().filter(e->e.highestValue<currValue).max(ChainHighestValue::compareTo).get();
                    int index=valuePairs.indexOf(heighestIndex);
                    seqList.get(index).add(arr[i]);
                    heighestIndex.highestValue=arr[i];
                    heighestIndex.chainLength+=1;

                }catch (Exception e){
                    LinkedList<Integer> aList=new LinkedList<>();
                    aList.add(arr[i]);
                    seqList.add(aList);
                    valuePairs.add(new ChainHighestValue(arr[i],1));
                }
            }
        }
        ChainHighestValue heighestIndex=valuePairs.stream().max(ChainHighestValue::compareTo).get();
        int index=valuePairs.indexOf(heighestIndex);
        return seqList.get(index);
    }

    public static void main(String[] args){
        int arry[]={5,1,3,6,11,30,32,5,3,73,79};
        //int arryB[]={3,1,5,2,6,4,9};
        LinkedList<Integer> LIS=LongestSubsequent(arry, arry.length);
        System.out.println("Longest Incrementing Subsequence:");
        for(Integer a: LIS){
            System.out.print(a+" ");
        }

    }
}
person Atique Reza    schedule 06.02.2020

Я реализовал LIS на java, используя динамическое программирование и мемоизацию. Наряду с кодом я выполнил расчет сложности, то есть почему это O (n Log (base2) n). Насколько я понимаю, теоретические или логические объяснения хороши, но практическая демонстрация всегда лучше для понимания.

package com.company.dynamicProgramming;

import java.util.HashMap;
import java.util.Map;

public class LongestIncreasingSequence {

    static int complexity = 0;

    public static void main(String ...args){


        int[] arr = {10, 22, 9, 33, 21, 50, 41, 60, 80};
        int n = arr.length;

        Map<Integer, Integer> memo = new HashMap<>();

        lis(arr, n, memo);

        //Display Code Begins
        int x = 0;
        System.out.format("Longest Increasing Sub-Sequence with size %S is -> ",memo.get(n));
        for(Map.Entry e : memo.entrySet()){

            if((Integer)e.getValue() > x){
                System.out.print(arr[(Integer)e.getKey()-1] + " ");
                x++;
            }
        }
        System.out.format("%nAnd Time Complexity for Array size %S is just %S ", arr.length, complexity );
        System.out.format( "%nWhich is equivalent to O(n Log n) i.e. %SLog(base2)%S is %S",arr.length,arr.length, arr.length * Math.ceil(Math.log(arr.length)/Math.log(2)));
        //Display Code Ends

    }



    static int lis(int[] arr, int n, Map<Integer, Integer> memo){

        if(n==1){
            memo.put(1, 1);
            return 1;
        }

        int lisAti;
        int lisAtn = 1;

        for(int i = 1; i < n; i++){
            complexity++;

            if(memo.get(i)!=null){
                lisAti = memo.get(i);
            }else {
                lisAti = lis(arr, i, memo);
            }

            if(arr[i-1] < arr[n-1] && lisAti +1 > lisAtn){
                lisAtn = lisAti +1;
            }
        }

        memo.put(n, lisAtn);
        return lisAtn;

    }
}

Пока я запускал приведенный выше код -

Longest Increasing Sub-Sequence with size 6 is -> 10 22 33 50 60 80 
And Time Complexity for Array size 9 is just 36 
Which is equivalent to O(n Log n) i.e. 9Log(base2)9 is 36.0
Process finished with exit code 0

person atul sachan    schedule 06.04.2020
comment
Дает неправильный ответ на ввод: {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}; - person ahadcse; 21.04.2020

Подход O (NLog (N)) для поиска самой длинной возрастающей подпоследовательности
Давайте сохраним массив, где i-й элемент - это наименьшее возможное число, которым может заканчиваться подпоследовательность размера i.

Я специально избегаю дальнейших подробностей, поскольку ответ, получивший наибольшее количество голосов, уже объясняет это, но этот метод в конечном итоге приводит к аккуратной реализации с использованием заданной структуры данных (по крайней мере, в C ++).

Вот реализация на С ++ (при условии, что требуется строго увеличивать размер самой длинной подпоследовательности)

#include <bits/stdc++.h> // gcc supported header to include (almost) everything
using namespace std;
typedef long long ll;

int main()
{
  ll n;
  cin >> n;
  ll arr[n];
  set<ll> S;

  for(ll i=0; i<n; i++)
  {
    cin >> arr[i];
    auto it = S.lower_bound(arr[i]);
    if(it != S.end())
      S.erase(it);
    S.insert(arr[i]);
  }

  cout << S.size() << endl; // Size of the set is the required answer

  return 0;
}
person Mayank    schedule 30.06.2020

Рекурсивный подход DP O (NLog (N)) к поиску самой длинной возрастающей подпоследовательности (LIS)


Объяснение

Этот алгоритм включает создание дерева с форматом узла как (a,b).

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

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

Алгоритм

  1. Мы начинаем с недопустимого корня (INT_MIN, 0), указывая на нулевой индекс массива, поскольку подпоследовательность в этой точке пуста, то есть b = 0.

  2. Base Case: вернуть 1, если b >= array.length.

  3. Прокрутите все элементы в массиве от индекса b до конца массива, то есть i = b ... array.length-1. i) Если элемент array[i] является greater than текущим a, он квалифицируется как один из элементов, добавляемых к уже имеющейся подпоследовательности. ii) Рекурсия в узел (array[i],b+1), где a - это элемент, который мы встретили в 2(i), который квалифицируется для добавления к подпоследовательности, которая у нас есть до сих пор. И b+1 - это следующий индекс массива, который необходимо учитывать. iii) Верните длину max, полученную при прохождении через i = b ... array.length. В случае, когда a больше любого другого элемента из i = b to array.length, верните 1.

  4. Вычислите уровень построенного дерева как level. Наконец, level - 1 - это желаемый LIS. Это число edges на самом длинном пути дерева.

NB: запоминание в алгоритме не учитывается, так как оно ясно из дерева.

Случайный пример Узлы, отмеченные x, извлекаются из мемоизированных значений БД. введите описание изображения здесь

Реализация Java

public int lengthOfLIS(int[] nums) {
            return LIS(nums,Integer.MIN_VALUE, 0,new HashMap<>()) -1;
    }
    public int LIS(int[] arr, int value, int nextIndex, Map<String,Integer> memo){
        if(memo.containsKey(value+","+nextIndex))return memo.get(value+","+nextIndex);
        if(nextIndex >= arr.length)return 1;

        int max = Integer.MIN_VALUE;
        for(int i=nextIndex; i<arr.length; i++){
            if(arr[i] > value){
                max = Math.max(max,LIS(arr,arr[i],i+1,memo));
            }
        }
        if(max == Integer.MIN_VALUE)return 1;
        max++;
        memo.put(value+","+nextIndex,max);
        return max;
    }
person Wilson    schedule 13.12.2020