Come determinare la sottosequenza crescente più lunga usando la programmazione dinamica?


Risposte:


404

OK, descriverò prima la soluzione più semplice che è O (N ^ 2), dove N è la dimensione della raccolta. Esiste anche una soluzione O (N log N), che descriverò anche. Cerca qui nella sezione Algoritmi efficienti.

Presumo che gli indici dell'array siano compresi tra 0 e N - 1. Quindi definiamo DP[i]la lunghezza del LIS (sottosequenza crescente più lunga) che termina con l'elemento con indice i. Per calcolare DP[i]guardiamo tutti gli indici j < ie controlliamo sia se DP[j] + 1 > DP[i]che array[j] < array[i](vogliamo che aumenti). Se questo è vero, possiamo aggiornare l'attuale ottimale per DP[i]. Per trovare l'ottimale globale per l'array, è possibile ottenere il valore massimo da 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];
   }
}

Uso l'array prevper poter in seguito trovare la sequenza effettiva non solo la sua lunghezza. Torna indietro in modo ricorsivo da bestEndin un ciclo usando prev[bestEnd]. Il -1valore è un segno per interrompere.


OK, ora alla O(N log N)soluzione più efficiente :

Sia S[pos]definito come il numero intero più piccolo che termina una sequenza crescente di lunghezza pos. Ora scorrere tutti i numeri interi Xdel set di input ed eseguire le seguenti operazioni:

  1. Se X> ultimo elemento in S, quindi aggiungi Xalla fine di S. Questo significa essenzialmente che abbiamo trovato un nuovo più grande LIS.

  2. In caso contrario, trovare l'elemento più piccolo S, che è >=oltre X, e modificarlo X. Poiché Sè ordinato in qualsiasi momento, l'elemento può essere trovato utilizzando la ricerca binaria in log(N).

Runtime totale - Nnumeri interi e una ricerca binaria per ciascuno di essi - N * log (N) = O (N log N)

Ora facciamo un vero esempio:

Raccolta di numeri interi: 2 6 3 4 1 2 9 5 8

passi:

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

Quindi la lunghezza del LIS è 5(la dimensione di S).

Per ricostruire l'effettivo LISuseremo nuovamente un array genitore. Lascia che parent[i]sia il predecessore dell'elemento con indice inel LISfinale all'elemento con indice i.

Per semplificare le cose, possiamo mantenere l'array S, non gli interi reali, ma i loro indici (posizioni) nell'insieme. Non manteniamo {1, 2, 4, 5, 8}, ma manteniamo {4, 5, 3, 7, 8}.

Cioè input [4] = 1 , input [5] = 2 , input [3] = 4 , input [7] = 5 , input [8] = 8 .

Se aggiorniamo correttamente l'array principale, il LIS effettivo è:

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

Ora alla cosa importante: come possiamo aggiornare l'array genitore? Esistono due opzioni:

  1. Se X> ultimo elemento in S, quindi parent[indexX] = indexLastElement. Ciò significa che il genitore dell'elemento più recente è l'ultimo elemento. Abbiamo appena anteposto Xalla fine di S.

  2. Altrimenti trova l'indice dell'elemento più piccolo in S, che è >=di X, e modificalo in X. QUI parent[indexX] = S[index - 1].


4
Non importa Se DP[j] + 1 == DP[i]allora DP[i]non migliorerà con DP[i] = DP[j] + 1. Stiamo cercando di ottimizzare DP[i].
Petar Minchev,

11
Ma qui la risposta dovrebbe essere [1,2,5,8]: 4 viene prima di 1 nell'array, come può essere il LIS [1,2,4,5,8]?
SexyBeast,

19
@Cupidvogel - La risposta è [2,3,4,5,8]. Leggere attentamente: l' Sarray DOES NOTrappresenta una sequenza effettiva. Let S[pos] be defined as the smallest integer that ends an increasing sequence of length pos.
Petar Minchev,

8
Non vedo spesso spiegazioni così chiare. Non solo è molto facile da capire, perché i dubbi vengono chiariti nella spiegazione, ma affronta anche qualsiasi problema di implementazione che potrebbe sorgere. Eccezionale.
Boyang,

16
geeksforgeeks.org/… è probabilmente la migliore spiegazione di ciò che ho visto
eb80

57

La spiegazione di Petar Minchev mi ha aiutato a chiarire le cose, ma è stato difficile per me analizzare cosa fosse tutto, quindi ho realizzato un'implementazione di Python con nomi di variabili eccessivamente descrittivi e molti commenti. Ho fatto una soluzione ricorsiva ingenua, la soluzione O (n ^ 2) e la soluzione O (n log n).

Spero che aiuti a chiarire gli algoritmi!

La soluzione ricorsiva

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                                                        

La soluzione di programmazione dinamica 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                                                   

La soluzione di programmazione dinamica 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
Anche se apprezzo lo sforzo qui, i miei occhi mi fanno male quando fisso quegli pseudo-codici.
mostruash,

94
mostruash - Non sono sicuro di cosa intendi. La mia risposta non ha pseudo codice; ha Python.
Sam King,

10
Beh, molto probabilmente intende la tua convenzione di denominazione di variabili e funzioni, che mi ha anche fatto 'male' agli occhi
Adilli Adil,

19
Se intendi la mia convenzione di denominazione, seguo principalmente la Guida allo stile di Google Python. Se stai sostenendo nomi di variabili brevi, preferisco nomi di variabili descrittivi perché rendono il codice più facile da capire e mantenere.
Sam King,

10
Per un'implementazione effettiva, probabilmente avrebbe senso usarlo bisect. Per una dimostrazione di come funziona un algoritmo e delle sue caratteristiche prestazionali, stavo cercando di mantenere le cose il più primitive possibile.
Sam King,

22

Parlando della soluzione DP, ho trovato sorprendente che nessuno abbia menzionato il fatto che il LIS può essere ridotto a LCS . Tutto quello che devi fare è ordinare la copia della sequenza originale, rimuovere tutti i duplicati ed eseguirne LCS. In pseudocodice è:

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

E l'implementazione completa scritta in Go. Non è necessario mantenere l'intera matrice n ^ 2 DP se non è necessario ricostruire la soluzione.

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 sì, è un po 'scritto nella risposta con LCS, matrice n ^ 2
Salvador Dali,

10

La seguente implementazione C ++ include anche del codice che crea l'attuale sottosequenza crescente più lunga usando un array chiamato 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;
}

L'implementazione senza stack ha appena invertito il vettore

#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

Ecco tre passaggi per valutare il problema dal punto di vista della programmazione dinamica:

  1. Definizione di ricorrenza: maxLength (i) == 1 + maxLength (j) dove 0 <j <i e array [i]> array [j]
  2. Limite dei parametri di ricorrenza: potrebbero esserci da 0 a i - 1 sotto-sequenze passate come parametro
  3. Ordine di valutazione: poiché sta aumentando la sottosequenza, deve essere valutata da 0 a n

Se prendiamo come esempio una sequenza {0, 8, 2, 3, 7, 9}, all'indice:

  • [0] otterremo la sottosequenza {0} come caso base
  • [1] abbiamo 1 nuova sottosequenza {0, 8}
  • [2] cercando di valutare due nuove sequenze {0, 8, 2} e {0, 2} aggiungendo l'elemento all'indice 2 alle sottosequenze esistenti - solo una è valida, quindi aggiungendo solo la terza possibile sequenza {0, 2} alla lista parametri ...

Ecco il codice C ++ 11 funzionante:

#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;
}

Penso che la definizione di ricorrenza dovrebbe essere maxLength (i) = 1 + max (maxLength (j)) per 0 <j <i e array [i]> array [j] piuttosto che senza max ().
Slothworks,

1

Ecco un'implementazione Scala dell'algoritmo 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

Ecco un'altra implementazione O (n ^ 2) JAVA. Nessuna ricorsione / memoizzazione per generare la sottosequenza effettiva. Solo un array di stringhe che memorizza il LIS effettivo in ogni fase e un array per memorizzare la lunghezza del LIS per ciascun elemento. Abbastanza dannatamente facile. Dare un'occhiata:

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

Codice in azione: http://ideone.com/sBiOQx


0

Questo può essere risolto in O (n ^ 2) usando la Programmazione dinamica. Il codice Python per lo stesso sarebbe come: -

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)

Per input:5 19 5 81 50 28 29 1 83 23

l'output sarebbe:[1, 2, 1, 3, 3, 3, 4, 1, 5, 3] 5

List_index dell'elenco di output è list_index dell'elenco di input. Il valore di un dato list_index nell'elenco di output indica la lunghezza di sottosequenza crescente più lunga per quel list_index.


0

ecco l'implementazione di 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

Questa è un'implementazione Java in O (n ^ 2). Non ho usato la ricerca binaria per trovare l'elemento più piccolo in S, che è> = di X. Ho appena usato un ciclo for. L'uso della ricerca binaria renderebbe la complessità in 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

controlla il codice in java per la sottosequenza crescente più lunga con gli elementi dell'array

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

Questo può essere risolto in O (n ^ 2) usando la programmazione dinamica.

Elaborare gli elementi di input in ordine e mantenere un elenco di tuple per ciascun elemento. Ogni tupla (A, B), per l'elemento i indicherà, A = lunghezza della sotto-sequenza crescente più lunga che termina con i e B = indice del predecessore dell'elenco [i] nella sotto-sequenza crescente più lunga che termina nell'elenco [i ].

Inizia dall'elemento 1, l'elenco di tuple per l'elemento 1 sarà [(1,0)] per l'elemento i, scansiona l'elenco 0..i e trova l'elenco di elementi [k] tale che l'elenco [k] <elenco [i] , il valore di A per l'elemento i, Ai sarà Ak + 1 e Bi sarà k. Se ci sono più di questi elementi, aggiungili all'elenco delle tuple per l'elemento i.

Alla fine, trova tutti gli elementi con il valore massimo di A (lunghezza del LIS che termina all'elemento) e torna indietro usando le tuple per ottenere l'elenco.

Ho condiviso il codice per lo stesso all'indirizzo http://www.edufyme.com/code/?id=66f041e16a60928b05a7e228a89c3799


3
È necessario includere il codice nella risposta poiché i collegamenti potrebbero interrompersi.
NathanOliver,

0

O (n ^ 2) implementazione 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)

anche se esiste un modo per risolverlo in tempo O (nlogn) (questo risolve in tempo O (n ^ 2)), ma in questo modo offre un approccio di programmazione dinamica che è anche buono.


0

Ecco la mia soluzione Leetcode usando Ricerca binaria: ->

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

La soluzione LIS più semplice in C ++ con complessità temporale 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;
}

USCITA:
4


0

Conseguenza crescente più lunga (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

Ho implementato LIS in Java usando la programmazione dinamica e la memorizzazione. Insieme al codice ho fatto il calcolo della complessità, cioè perché è O (n Log (base2) n). A mio avviso, le spiegazioni teoriche o logiche sono buone, ma la dimostrazione pratica è sempre migliore per la comprensione.

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;

    }
}

Mentre ho eseguito il codice sopra -

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


Fornisce una risposta errata per l'input: {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15};
ahadcse il

0

L'approccio O (NLog (N)) per trovare la sequenza secondaria crescente più lunga Manteniamo
un array in cui l'elemento ith è il numero più piccolo possibile con il quale può terminare una sequenza secondaria con dimensioni ai.

Di proposito sto evitando ulteriori dettagli poiché la risposta più votata lo spiega già, ma questa tecnica alla fine porta a un'implementazione ordinata usando la struttura di dati impostata (almeno in c ++).

Ecco l'implementazione in c ++ (presupponendo che sia richiesta la dimensione della sub sequenza più lunga strettamente crescente)

#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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.