Як визначити найдовше зростаючу послідовність за допомогою динамічного програмування?


215

У мене є набір цілих чисел. Я хочу знайти найдовше зростаючу послідовність цього набору за допомогою динамічного програмування.


10
Говорячи про рішення DP, я здивував, що ніхто не згадав про те, що LIS можна звести до LCS .
Сальвадор Далі

Відповіді:


404

Добре, я опишу спочатку найпростіший варіант, який є 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}.

Це вхід [4] = 1 , вхід [5] = 2 , вхід [3] = 4 , вхід [7] = 5 , вхід [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. Here parent[indexX] = S[index - 1].


4
Це не має значення. Якщо DP[j] + 1 == DP[i]тоді DP[i]не стане краще з DP[i] = DP[j] + 1. Ми намагаємось оптимізувати DP[i].
Петро Мінчев

11
Але тут відповідь повинна бути [1,2,5,8]: 4 надходить до 1 в масиві, як може бути LIS [1,2,4,5,8]?
SexyBeast

19
@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.
Петро Мінчев

8
Я не бачу таких чітких пояснень. Це не тільки дуже легко зрозуміти, оскільки сумніви усуваються в поясненні, але і вони вирішують будь-яку проблему, яка може виникнути. Дивовижно.
Боянг

16
geeksforgeeks.org/… - це, мабуть, найкраще пояснення цього, що я бачив
eb80

57

Пояснення Петра Мінчева допомогло мені зрозуміти речі, але мені було важко розібратися, що все було, тому я зробив реалізацію 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         

19
Хоча я ціную тут зусилля, мені болять очі, коли я дивлюся на ці псевдокоди.
mostruash

94
mostruash - я не впевнений, що ти маєш на увазі. У моїй відповіді немає псевдокоду; він має Python.
Сем Кінг

10
Ну, він, швидше за все, має на увазі вашу назву
згоди

19
Якщо ви маєте на увазі мою конвенцію про іменування, я здебільшого дотримуюся посібника зі стилю Google Python. Якщо ви виступаєте за короткі імена змінних, я віддаю перевагу описовим іменам змінних, оскільки вони полегшують розуміння та підтримку коду.
Сем Кінг

10
Для реальної реалізації це, мабуть, має сенс використовувати bisect. Для демонстрації того, як працює алгоритм та його експлуатаційні характеристики, я намагався зберегти речі максимально примітивними.
Сем Кінг

22

Говорячи про рішення DP, я здивував, що ніхто не згадав про те, що LIS можна звести до LCS . Все, що вам потрібно зробити, це сортувати копію оригінальної послідовності, видалити всі дублікати та зробити LCS з них. У псевдокоді це:

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

І повна реалізація написана на Go. Вам не потрібно підтримувати всю матрицю n ^ 2 DP, якщо вам не потрібно реконструювати рішення.

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]
}

@max так, це якось написано у відповіді з LCS, n ^ 2 матрицею
Сальвадор Далі

10

Наступна реалізація 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;
}

4

Ось три етапи оцінки проблеми з точки зору динамічного програмування:

  1. Визначення повторності: maxLength (i) == 1 + maxLength (j), де 0 <j <i та масив [i]> масив [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;
}

Я думаю, що визначення рецидиву повинно бути maxLength (i) = 1 + max (maxLength (j)) для 0 <j <i та масив [i]> масив [j], а не без max ().
Slothworks

1

Ось реалізація Scala алгоритму O (n ^ 2):

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)))
  }
}

1

Ось ще одна реалізація O (n ^ 2) JAVA. Немає рекурсії / запам'ятовування, щоб генерувати фактичну послідовність. Просто масив рядків, який зберігає фактичний 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


0

Це можна вирішити в 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.


0

ось реалізація 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);
    }    
}

0

Це реалізація Java в O (n ^ 2). Я просто не використовував двійковий пошук, щоб знайти найменший елемент у S, який> =, ніж X. Я просто використав для циклу. Використання двійкового пошуку дозволило б скласти складність при 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));

}

0

перевірити код у 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();
    }
}

0

Це можна вирішити в O (n ^ 2) за допомогою динамічного програмування.

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

Почніть з елемента 1, список кортежу для елемента 1 буде [(1,0)] для елемента i, скануйте список 0..i і знайдіть список елементів [k] таким, що список [k] <список [i] , значення A для елемента i, Ai буде Ak + 1, а Bi буде k. Якщо таких елементів декілька, додайте їх до списку кортежів для елемента i.

Зрештою, знайдіть усі елементи з максимальним значенням A (довжина LIS, що закінчується на елементі) та зворотній шлях, використовуючи кортежі, щоб отримати список.

Я поділився цим кодом на веб- сайті http://www.edufyme.com/code/?id=66f041e16a60928b05a7e228a89c3799


3
Ви повинні включити код у свою відповідь, оскільки посилання можуть порватися.
NathanOliver

0

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]+" ");
        }
    }

0
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)), але все-таки цей спосіб дає динамічний підхід до програмування, який також хороший.


0

Ось моє рішення 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)

0

Найпростіше рішення 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


0

Найдовша зростаюча послідовність (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+" ");
        }

    }
}

0

Я реалізував 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


Дає неправильну відповідь для введення: {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15};
ahadcse

0

Підхід O (NLog (N)) для пошуку найбільш тривалої послідовності збільшення
Давайте підтримувати масив, де i-й елемент є найменшим можливим числом, яким може закінчитися послідовність розміру ai.

Я цілеспрямовано уникаю подальших деталей, оскільки відповідь у верхній частині голосу вже пояснює це, але ця методика врешті-решт призводить до акуратної реалізації з використанням заданої структури даних (принаймні, в c ++).

Ось реалізація в 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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.