web-dev-qa-db-fra.com

La plus longue sous-séquence croissante

Avec une séquence d'entrée, quel est le meilleur moyen de trouver la sous-séquence non décroissante la plus longue (pas nécessairement continue).

0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 # sequence

1, 9, 13, 15 # non-decreasing subsequence

0, 2, 6, 9, 13, 15 # longest non-deceasing subsequence (not unique)

Je cherche le meilleur algorithme. S'il y a du code, Python serait Nice, mais tout va bien.

30
Jungle Hunter

Je viens de trébucher dans ce problème, et est venu avec cette implémentation de Python 3:

def subsequence(seq):
    if not seq:
        return seq

    M = [None] * len(seq)    # offset by 1 (j -> j-1)
    P = [None] * len(seq)

    # Since we have at least one element in our list, we can start by 
    # knowing that the there's at least an increasing subsequence of length one:
    # the first element.
    L = 1
    M[0] = 0

    # Looping over the sequence starting from the second element
    for i in range(1, len(seq)):
        # Binary search: we want the largest j <= L
        #  such that seq[M[j]] < seq[i] (default j = 0),
        #  hence we want the lower bound at the end of the search process.
        lower = 0
        upper = L

        # Since the binary search will not look at the upper bound value,
        # we'll have to check that manually
        if seq[M[upper-1]] < seq[i]:
            j = upper

        else:
            # actual binary search loop
            while upper - lower > 1:
                mid = (upper + lower) // 2
                if seq[M[mid-1]] < seq[i]:
                    lower = mid
                else:
                    upper = mid

            j = lower    # this will also set the default value to 0

        P[i] = M[j-1]

        if j == L or seq[i] < seq[M[j]]:
            M[j] = i
            L = max(L, j+1)

    # Building the result: [seq[M[L-1]], seq[P[M[L-1]]], seq[P[P[M[L-1]]]], ...]
    result = []
    pos = M[L-1]
    for _ in range(L):
        result.append(seq[pos])
        pos = P[pos]

    return result[::-1]    # reversing

Puisqu'il m'a fallu un certain temps pour comprendre le fonctionnement de l'algorithme, j'ai été un peu commenté et j'ai ajouté une brève explication:

  • seq est la séquence d'entrée.
  • L est un nombre: il est mis à jour en boucle sur la séquence et marque la longueur de la plus longue sous-séquence croissante trouvée jusqu'à ce moment.
  • M est une liste. M[j-1] pointera vers un index de seq qui contient la plus petite valeur pouvant être utilisée (à la fin) pour construire une sous-séquence croissante de longueur j.
  • P est une liste. P[i] désignera M[j], où i est l'index de seq. En quelques mots, il indique quel est l'élément précédent de la sous-séquence. P est utilisé pour générer le résultat à la fin.

Comment fonctionne l'algorithme:

  1. Traite le cas particulier d'une séquence vide.
  2. Commencez avec une sous-séquence de 1 élément.
  3. Boucle sur la séquence d'entrée avec index i.
  4. Avec une recherche binaire, trouvez la j qui permet à seq[M[j] d'être < à seq[i].
  5. Mettez à jour P, M et L.
  6. Retracez le résultat et retournez-le inversé.

Note: Les seules différences avec l'algorithme wikipedia sont le décalage de 1 dans la liste M, et que X s'appelle ici seq. Je le teste également avec une version de test unitaire légèrement améliorée de celle présentée dans Eric Gustavson répond et il a réussi tous les tests.


Exemple:

seq = [30, 10, 20, 50, 40, 80, 60]

       0    1   2   3   4   5   6   <-- indexes

À la fin nous aurons:

M = [1, 2, 4, 6, None, None, None]
P = [None, None, 1, 2, 2, 4, 4]
result = [10, 20, 40, 60]

Comme vous le verrez, P est assez simple. Nous devons le regarder à partir de la fin, ainsi il indique qu'avant 60 il y a 40,avant 80 il y a 40, avant 40 il y a 20, avant 50 il y a 20 et avant 20 il y a 10, arrêt.

La partie compliquée est sur M. Au début, M était [0, None, None, ...] puisque le dernier élément de la sous-séquence de longueur 1 (d'où la position 0 dans M) était à l'indice 0: 30

À ce stade, nous allons commencer à boucler sur seq et regarder 10, puisque 10 est < à 30, M sera mis à jour:

if j == L or seq[i] < seq[M[j]]:
    M[j] = i

Alors maintenant, M ressemble à: [1, None, None, ...]. C'est une bonne chose, car 10 a plus de modifications pour créer une sous-séquence croissante plus longue. (Le nouveau 1 est l'indice de 10)

Maintenant c'est au tour de 20. Avec 10 et 20 nous avons une sous-séquence de longueur 2 (index 1 dans M), donc M sera: [1, 2, None, ...]. (Le nouveau 2 est l'indice de 20)

Maintenant c'est au tour de 50. 50 ne fera partie d'aucune sous-séquence, donc rien ne change.

Maintenant c'est au tour de 40. Avec 10, 20 et 40 nous avons un sous de longueur 3 (index 2 dans M, donc M sera: [1, 2, 4, None, ...]. (Le nouveau 4 est l’index de 40)

Etc...

Pour une lecture complète du code, vous pouvez le copier/coller ici :)

28
Rik Poggi

Voici comment trouver simplement la sous-séquence croissante/décroissante la plus longue dans Mathematica:

 LIS[list_] := LongestCommonSequence[Sort[list], list];
 input={0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15};
 LIS[input]
 -1*LIS[-1*input]

Sortie:

{0, 2, 6, 9, 11, 15}
{12, 10, 9, 5, 3}

Mathematica a également LongestIncreasingSubsequence fonction dans le Combinatorica` libary. Si vous n'avez pas Mathematica, vous pouvez interroger le WolframAlpha .

C++ O(nlogn) solution

Il y a aussi une solution O(nlogn) basée sur quelques observations . Soit Ai, j la plus petite Queue possible parmi toutes les sous-séquences croissantes de De longueur j en utilisant Éléments a1, une2, ... , uneje. Notez que, pour tout Particulier i, Aje, 1, UNEje, 2, ... , UNEje, j. Cela suggère que si Nous voulons la sous-séquence la plus longue qui se termine par Se terminant par ai + 1, il suffit de rechercher Pour aj tel que Ai, j <ai + 1 <= Ai, j + 1 et la longueur sera j + 1. Notez que dans ce cas, Ai + 1, j + 1 Sera égal à ai + 1, et tous Ai + 1, k sera égal à Ai, k pour k! = j + 1. En outre, il y a au plus une différence entre l'ensemble Ai et le définissez Ai + 1, ce qui est causé par cette recherche . Puisque A est toujours ordonné dans l'ordre croissant, et que l'opération Ne change pas cet ordre, nous pouvons Effectuer une recherche binaire pour chaque1, une2, ... , unen.

Implementation C++ (algorithme O (nlogn))

#include <vector>
using namespace std;

/* Finds longest strictly increasing subsequence. O(n log k) algorithm. */
void find_lis(vector<int> &a, vector<int> &b)
{
  vector<int> p(a.size());
  int u, v;

  if (a.empty()) return;

  b.Push_back(0);

  for (size_t i = 1; i < a.size(); i++) {
      if (a[b.back()] < a[i]) {
          p[i] = b.back();
          b.Push_back(i);
          continue;
      }

      for (u = 0, v = b.size()-1; u < v;) {
          int c = (u + v) / 2;
          if (a[b[c]] < a[i]) u=c+1; else v=c;
      }

      if (a[i] < a[b[u]]) {
          if (u > 0) p[i] = b[u-1];
          b[u] = i;
      }   
  }

  for (u = b.size(), v = b.back(); u--; v = p[v]) b[u] = v;
}

/* Example of usage: */
#include <cstdio>
int main()
{
  int a[] = { 1, 9, 3, 8, 11, 4, 5, 6, 4, 19, 7, 1, 7 };
  vector<int> seq(a, a+sizeof(a)/sizeof(a[0]));
  vector<int> lis;
        find_lis(seq, lis);

  for (size_t i = 0; i < lis.size(); i++)
      printf("%d ", seq[lis[i]]);
        printf("\n");    

  return 0;
}

Source: lien

J'ai réécrit l'implémentation C++ en Java il y a quelque temps et je peux confirmer que cela fonctionne. La variante de vecteur en python est List. Mais si vous voulez le tester vous-même, voici le lien pour le compilateur en ligne avec un exemple d'implémentation chargé: link

Des exemples de données sont: { 1, 9, 3, 8, 11, 4, 5, 6, 4, 19, 7, 1, 7 } Et répondez: 1 3 4 5 6 7.

8
Margus

Voici quelques codes python avec des tests qui implémentent l'algorithme exécuté dans O (n * log (n)). J'ai trouvé ceci sur une page de discussion wikipedia à propos de la sous-séquence croissante la plus longue

import unittest


def LongestIncreasingSubsequence(X):
    """
    Find and return longest increasing subsequence of S.
    If multiple increasing subsequences exist, the one that ends
    with the smallest value is preferred, and if multiple
    occurrences of that value can end the sequence, then the
    earliest occurrence is preferred.
    """
    n = len(X)
    X = [None] + X  # Pad sequence so that it starts at X[1]
    M = [None]*(n+1)  # Allocate arrays for M and P
    P = [None]*(n+1)
    L = 0
    for i in range(1,n+1):
        if L == 0 or X[M[1]] >= X[i]:
            # there is no j s.t. X[M[j]] < X[i]]
            j = 0
        else:
            # binary search for the largest j s.t. X[M[j]] < X[i]]
            lo = 1      # largest value known to be <= j
            hi = L+1    # smallest value known to be > j
            while lo < hi - 1:
                mid = (lo + hi)//2
                if X[M[mid]] < X[i]:
                    lo = mid
                else:
                    hi = mid
            j = lo

        P[i] = M[j]
        if j == L or X[i] < X[M[j+1]]:
            M[j+1] = i
            L = max(L,j+1)

    # Backtrack to find the optimal sequence in reverse order
    output = []
    pos = M[L]
    while L > 0:
        output.append(X[pos])
        pos = P[pos]
        L -= 1

    output.reverse()
    return output

# Try small lists and check that the correct subsequences are generated.

class LISTest(unittest.TestCase):
    def testLIS(self):
        self.assertEqual(LongestIncreasingSubsequence([]),[])
        self.assertEqual(LongestIncreasingSubsequence(range(10,0,-1)),[1])
        self.assertEqual(LongestIncreasingSubsequence(range(10)),range(10))
        self.assertEqual(LongestIncreasingSubsequence(\
            [3,1,4,1,5,9,2,6,5,3,5,8,9,7,9]), [1,2,3,5,8,9])

unittest.main()
2
zzz

Voici une solution assez générale qui:

  • fonctionne dans O(n log n) time,
  • gère les sous-séquences croissantes, non décroissantes, décroissantes et non croissantes,
  • fonctionne avec n'importe quel objet de séquence, y compris list, numpy.array, str et plus,
  • prend en charge les listes d'objets et les méthodes de comparaison personnalisées via le paramètre key qui fonctionne comme celui de la fonction intégrée sorted,
  • peut renvoyer les éléments de la sous-séquence ou leurs indices.

Le code:

from bisect import bisect_left, bisect_right
from functools import cmp_to_key

def longest_subsequence(seq, mode='strictly', order='increasing',
                        key=None, index=False):

  bisect = bisect_left if mode.startswith('strict') else bisect_right

  # compute keys for comparison just once
  rank = seq if key is None else map(key, seq)
  if order == 'decreasing':
    rank = map(cmp_to_key(lambda x,y: 1 if x<y else 0 if x==y else -1), rank)
  rank = list(rank)

  if not rank: return []

  lastoflength = [0] # end position of subsequence with given length
  predecessor = [None] # penultimate element of l.i.s. ending at given position

  for i in range(1, len(seq)):
    # seq[i] can extend a subsequence that ends with a lesser (or equal) element
    j = bisect([rank[k] for k in lastoflength], rank[i])
    # update existing subsequence of length j or extend the longest
    try: lastoflength[j] = i
    except: lastoflength.append(i)
    # remember element before seq[i] in the subsequence
    predecessor.append(lastoflength[j-1] if j > 0 else None)

  # trace indices [p^n(i), ..., p(p(i)), p(i), i], where n=len(lastoflength)-1
  def trace(i):
    if i is not None:
      yield from trace(predecessor[i])
      yield i
  indices = trace(lastoflength[-1])

  return list(indices) if index else [seq[i] for i in indices]

J'ai écrit une docstring pour la fonction que je n'ai pas collée ci-dessus afin de montrer le code:

"""
Return the longest increasing subsequence of `seq`.

Parameters
----------
seq : sequence object
  Can be any sequence, like `str`, `list`, `numpy.array`.
mode : {'strict', 'strictly', 'weak', 'weakly'}, optional
  If set to 'strict', the subsequence will contain unique elements.
  Using 'weak' an element can be repeated many times.
  Modes ending in -ly serve as a convenience to use with `order` parameter,
  because `longest_sequence(seq, 'weakly', 'increasing')` reads better.
  The default is 'strict'.
order : {'increasing', 'decreasing'}, optional
  By default return the longest increasing subsequence, but it is possible
  to return the longest decreasing sequence as well.
key : function, optional
  Specifies a function of one argument that is used to extract a comparison
  key from each list element (e.g., `str.lower`, `lambda x: x[0]`).
  The default value is `None` (compare the elements directly).
index : bool, optional
  If set to `True`, return the indices of the subsequence, otherwise return
  the elements. Default is `False`.

Returns
-------
elements : list, optional
  A `list` of elements of the longest subsequence.
  Returned by default and when `index` is set to `False`.
indices : list, optional
  A `list` of indices pointing to elements in the longest subsequence.
  Returned when `index` is set to `True`.
"""

Quelques exemples:

>>> seq = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]

>>> longest_subsequence(seq)
[0, 2, 6, 9, 11, 15]

>>> longest_subsequence(seq, order='decreasing')
[12, 10, 9, 5, 3]

>>> txt = ("Given an input sequence, what is the best way to find the longest"
               " (not necessarily continuous) non-decreasing subsequence.")

>>> ''.join(longest_subsequence(txt))
' ,abdegilnorsu'

>>> ''.join(longest_subsequence(txt, 'weak'))
'              ceilnnnnrsssu'

>>> ''.join(longest_subsequence(txt, 'weakly', 'decreasing'))
'vuutttttttssronnnnngeee.'

>>> dates = [
...   ('2015-02-03', 'name1'),
...   ('2015-02-04', 'nameg'),
...   ('2015-02-04', 'name5'),
...   ('2015-02-05', 'nameh'),
...   ('1929-03-12', 'name4'),
...   ('2023-07-01', 'name7'),
...   ('2015-02-07', 'name0'),
...   ('2015-02-08', 'nameh'),
...   ('2015-02-15', 'namex'),
...   ('2015-02-09', 'namew'),
...   ('1980-12-23', 'name2'),
...   ('2015-02-12', 'namen'),
...   ('2015-02-13', 'named'),
... ]

>>> longest_subsequence(dates, 'weak')

[('2015-02-03', 'name1'),
 ('2015-02-04', 'name5'),
 ('2015-02-05', 'nameh'),
 ('2015-02-07', 'name0'),
 ('2015-02-08', 'nameh'),
 ('2015-02-09', 'namew'),
 ('2015-02-12', 'namen'),
 ('2015-02-13', 'named')]

>>> from operator import itemgetter

>>> longest_subsequence(dates, 'weak', key=itemgetter(0))

[('2015-02-03', 'name1'),
 ('2015-02-04', 'nameg'),
 ('2015-02-04', 'name5'),
 ('2015-02-05', 'nameh'),
 ('2015-02-07', 'name0'),
 ('2015-02-08', 'nameh'),
 ('2015-02-09', 'namew'),
 ('2015-02-12', 'namen'),
 ('2015-02-13', 'named')]

>>> indices = set(longest_subsequence(dates, key=itemgetter(0), index=True))

>>> [e for i,e in enumerate(dates) if i not in indices]

[('2015-02-04', 'nameg'),
 ('1929-03-12', 'name4'),
 ('2023-07-01', 'name7'),
 ('2015-02-15', 'namex'),
 ('1980-12-23', 'name2')]

Cette réponse a été inspirée en partie par la question de Code Review et en partie par une question sur les valeurs "hors séquence" .

2
arekolek
    int[] a = {1,3,2,4,5,4,6,7};
    StringBuilder s1 = new StringBuilder();
    for(int i : a){
     s1.append(i);
    }       
    StringBuilder s2 = new StringBuilder();
    int count = findSubstring(s1.toString(), s2);       
    System.out.println(s2.reverse());

public static int findSubstring(String str1, StringBuilder s2){     
    StringBuilder s1 = new StringBuilder(str1);
    if(s1.length() == 0){
        return 0;
    }
    if(s2.length() == 0){
        s2.append(s1.charAt(s1.length()-1));
        findSubstring(s1.deleteCharAt(s1.length()-1).toString(), s2);           
    } else if(s1.charAt(s1.length()-1) < s2.charAt(s2.length()-1)){ 
        char c = s1.charAt(s1.length()-1);
        return 1 + findSubstring(s1.deleteCharAt(s1.length()-1).toString(), s2.append(c));
    }
    else{
        char c = s1.charAt(s1.length()-1);
        StringBuilder s3 = new StringBuilder();
        for(int i=0; i < s2.length(); i++){
            if(s2.charAt(i) > c){
                s3.append(s2.charAt(i));
            }
        }
        s3.append(c);
        return Math.max(findSubstring(s1.deleteCharAt(s1.length()-1).toString(), s2), 
                findSubstring(s1.deleteCharAt(s1.length()-1).toString(), s3));
    }       
    return 0;
}
1
benben

Voici le code et l'explication avec Java, peut-être que j'ajouterai bientôt pour python.

arr = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
  1. list = {0} - Initialise la liste dans le jeu vide
  2. list = {0,8} - Nouveau plus grand LIS
  3. list = {0, 4} - Modifié de 8 à 4
  4. list = {0, 4, 12} - Nouveau plus grand LIS
  5. list = {0, 2, 12} - Modifié de 4 à 2
  6. list = {0, 2, 10} - Modifié de 12 à 10
  7. list = {0, 2, 6} - Modifié de 10 à 6
  8. list = {0, 2, 6, 14} - Nouveau plus grand LIS
  9. list = {0, 1, 6, 14} - Passé de 2 à 1
  10. list = {0, 1, 6, 9} - Modifié de 14 à 9
  11. list = {0, 1, 5, 9} - Modifié de 6 à 5
  12. list = {0, 1, 6, 9, 13} - Modifié 3 en 2
  13. list = {0, 1, 3, 9, 11} - Nouveau plus grand LIS
  14. list = {0, 1, 3, 9, 11} - Modifié de 9 à 5
  15. list = {0, 1, 3, 7, 11} - Nouveau plus grand LIS
  16. list = {0, 1, 3, 7, 11, 15} - Nouveau plus grand LIS

Donc, la longueur du LIS est de 6 (la taille de la liste).

import Java.util.ArrayList;
import Java.util.List;


public class LongestIncreasingSubsequence {
    public static void main(String[] args) {
        int[] arr = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
        increasingSubsequenceValues(arr);
    }

    public static void increasingSubsequenceValues(int[] seq) {
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < seq.length; i++) {
            int j = 0;
            boolean elementUpdate = false;
            for (; j < list.size(); j++) {
                if (list.get(j) > seq[i]) {
                    list.add(j, seq[i]);
                    list.remove(j + 1);
                    elementUpdate = true;
                    break;
                }
            }
            if (!elementUpdate) {
                list.add(j, seq[i]);
            }
        }
        System.out.println("Longest Increasing Subsequence" + list);
    }


}

Sortie pour le code ci-dessus: Sous-séquence croissante la plus longue [0, 1, 3, 7, 11, 15]

1
Deepak Singhvi

Il y a plusieurs réponses dans le code, mais je les ai trouvées un peu difficiles à comprendre. Voici donc une explication de l'idée générale, en laissant de côté toutes les optimisations. Je vais arriver aux optimisations plus tard.

Nous allons utiliser les séquences 2, 8, 4, 12, 3, 10 et, pour que cela soit plus facile à suivre, nous demanderons à la séquence de saisie de ne pas être vide et de ne pas inclure le même nombre plus d'une fois. 

Nous parcourons la séquence dans l'ordre.

Comme nous le faisons, nous conservons un ensemble de séquences, les meilleures séquences que nous avons trouvées jusqu'à présent pour chaque longueur. Après avoir trouvé la première séquence de longueur 1, qui est le premier élément de la séquence d'entrée, nous avons la garantie de disposer d'un ensemble de séquences pour chaque longueur possible de 1 à la plus longue que nous ayons trouvée jusqu'à présent. Cela est évident, car si nous avons une séquence de longueur 3, les 2 premiers éléments de cette séquence sont une séquence de longueur 2.

Nous commençons donc avec le premier élément étant une séquence de longueur 1 et notre ensemble ressemble à

 1: 2

Nous prenons l'élément suivant de la séquence (8) et cherchons la plus longue séquence à laquelle nous pouvons l'ajouter. Ceci est la séquence 1, donc nous obtenons

1: 2
2: 2 8

Nous prenons l'élément suivant de la séquence (4) et cherchons la plus longue séquence à laquelle nous pouvons l'ajouter. La séquence la plus longue à laquelle nous pouvons l'ajouter est celle de longueur 1 (qui est simplement 2). Voici ce que j'ai trouvé être la partie la plus délicate (ou du moins la moins évidente). Parce que nous n’avons pas pu l’ajouter à la fin de la séquence de longueur 2 (2 8), cela signifie que il doit être un meilleur choix pour terminer la longueur 2 candidate. Si l'élément était supérieur à 8, il aurait ajouté la séquence de longueur 2 et nous aurait donné une nouvelle séquence de longueur 3. Nous savons donc qu’il est inférieur à 8 et remplaçons donc le 8 par le 4.

Algorithmiquement, ce que nous disons, c'est que quelle que soit la séquence la plus longue sur laquelle nous pouvons coller l'élément, cette séquence, plus cet élément, est le meilleur candidat pour une séquence de la longueur résultante. Notez que chaque élément que nous traitons doit appartenir quelque part (car nous avons exclu les numéros en double dans l'entrée). S'il est plus petit que l'élément de longueur 1, il s'agit de la nouvelle longueur 1, sinon la fin de certains séquence existante. Ici, la séquence de longueur 1 plus l'élément 4 devient la nouvelle séquence de longueur 2 et nous avons:

1: 2
2: 2 4 (replaces 2 8)

L'élément suivant, 12, nous donne une séquence de longueur 3 et nous avons

1: 2
2: 2 4
3: 2 4 12

L'élément suivant, 3, nous donne une meilleure séquence de longueur 2:

1: 2
2: 2 3 (replaces 2 4)
3: 2 4 12

Notez que nous ne pouvons pas modifier la séquence de longueur 3 (en substituant le 3 au 4) car elles ne se sont pas produites dans cet ordre dans la séquence d'entrée. L'élément suivant, 10, s'occupe de cela. Parce que le mieux que nous puissions faire avec 10 est de l’ajouter à 2 3 il devient la nouvelle liste de longueur 3:

1: 2
2: 2 3
3: 2 3 10 (replaces 2 4 12)

Notez qu'en termes d'algorithme, nous ne nous soucions vraiment pas de ce qui précède le dernier élément de l'une de nos séquences candidates, mais nous devons bien sûr garder une trace pour qu'à la fin nous puissions sortir la séquence complète. 

Nous continuons à traiter les éléments d’entrée de la manière suivante: clouez chacun sur la séquence la plus longue possible et définissez-la comme nouvelle séquence candidate pour la longueur résultante, car il est garanti qu’elle n’est pas pire que la séquence existante de cette longueur. À la fin, nous produisons la plus longue séquence trouvée.

Optimisations

Une optimisation est que nous n’avons pas vraiment besoin de stocker la séquence complète de chaque longueur. Cela prendrait un espace de O (n ^ 2). Pour l’essentiel, nous pouvons nous contenter de stocker le dernier élément de chaque séquence, car c’est tout ce à quoi nous nous comparons. (J'arriverai à comprendre pourquoi ce n'est pas tout à fait suffisant dans un instant. Voyez si vous pouvez comprendre pourquoi avant que j'y arrive.)

Supposons donc que nous stockons notre ensemble de séquences sous forme de tableau MM[x] contient le dernier élément de la séquence de longueur x. Si vous y réfléchissez, vous réaliserez que les éléments de M sont eux-mêmes en ordre croissant: ils sont triés. Si M[x+1] était inférieur à M[x], il aurait remplacé M[x] à la place. 

Puisque M est trié, la prochaine optimisation passe à quelque chose que j'ai totalement passé sous silence ci-dessus: comment trouver la séquence à ajouter? Eh bien, puisque M est trié, nous pouvons simplement faire une recherche binaire pour trouver le M[x] le plus grand inférieur à l'élément à ajouter. C'est la séquence que nous ajoutons à. 

C'est très bien si tout ce que nous voulons faire est de trouver la longueur de la plus longue séquence. Cependant, M n'est pas suffisant pour reconstruire la séquence elle-même. Rappelez-vous, à un moment donné, notre ensemble ressemblait à ceci:

1: 0
2: 0 2
3: 0 4 12

Nous ne pouvons pas simplement sortir M lui-même en tant que séquence. Nous avons besoin de plus d'informations pour pouvoir reconstruire la séquence. Pour cela, nous apportons 2 modifications supplémentaires . First , nous stockons la séquence d'entrée dans un tableau seq et au lieu de stocker la valeur de l'élément dans M[x], nous stockons l'index de l'élément dans seq, la valeur est donc seq[M[x]]

Nous faisons cela pour pouvoir garder une trace de toute la séquence en enchaînant des sous-séquences. Comme vous l'avez vu au début, chaque séquence est créée en ajoutant un seul élément à la fin d'une séquence existante. Ainsi, second , nous conservons un autre tableau P qui stocke l’index (dans seq) du dernier élément de la séquence à laquelle nous ajoutons. Afin de le rendre chaînable, puisque ce que nous stockons dans P est un index de seq, nous devons indexer P lui-même par un index de seq

Cela fonctionne de la manière suivante: lors du traitement de l'élément i of seq, nous trouvons la séquence sur laquelle nous ajoutons. Rappelez-vous que nous allons coller seq[i] sur une séquence de longueur x pour créer une nouvelle séquence de longueur x+1 pour une x, et nous stockons i, pas seq[i] dans M[x+1]. Plus tard, quand nous constatons que x+1 est la plus grande longueur possible, nous allons vouloir reconstruire la séquence, mais le seul point de départ que nous avons est M[x+1]

Ce que nous faisons est de définir M[x+1] = i et P[i] = M[x] (ce qui est identique à P[M[x+1]] = M[x]), ce qui revient à dire que pour chaque élément i ajouté, nous stockons i comme dernier élément de la chaîne la plus longue possible et que nous stockons l'index du dernier élément. de la chaîne que nous étendons dans P[i]. Donc nous avons:

last element: seq[M[x]]
 before that: seq[P[M[x]]]
 before that: seq[P[P[M[x]]]]
 etc...

Et maintenant nous avons fini. Si vous voulez comparer cela au code réel, vous pouvez regarder le autreexemples . Les principales différences sont qu’elles utilisent j au lieu de x, peuvent stocker la liste de longueur j dans M[j-1] au lieu de M[j] pour éviter de gaspiller l’espace dans M[0] et peuvent appeler la séquence de saisie X au lieu de seq.

0
Old Pro

voici une implémentation compacte utilisant "énumérer"

def lis(l):

# we will create a list of lists where each sub-list contains
# the longest increasing subsequence ending at this index
lis = [[e] for e in l]
# start with just the elements of l as contents of the sub-lists

# iterate over (index,value) of l
for i, e in enumerate(l):
    # (index,value) tuples for elements b where b<e and a<i
    lower_tuples = filter(lambda (a,b): b<e, enumerate(l[:i]))
    # if no such items, nothing to do
    if not lower_tuples: continue
    # keep the lis-es of such items
    lowerlises = [lis[a] for a,b in  lower_tuples ]
    # choose the longest one of those and add
    # to the current element's lis
    lis[i] = max(lowerlises, key=len) + [e]

# retrun the longest of lis-es
return max(lis, key=len)
0
cmantas

L'algorithme le plus efficace pour cela est O(NlogN) décrit ici .

Une autre façon de résoudre ce problème consiste à utiliser la la plus longue sous-séquence commune } (LCS) du tableau d'origine et sa version triée, qui prend O (N2) temps.

0
codaddict

Voici ma solution C++ du problème. La solution est plus simple que toutes les solutions fournies ici jusqu'à présent, et elle est rapide: complexité de temps algorithmique N*log(N). J'ai soumis la solution à leetcode, elle s'exécute en 4 ms, plus rapidement que 100% des solutions C++ soumises.

 enter image description here

L’idée est (à mon avis) claire: parcourez le tableau de nombres donné de gauche à droite. Maintenir en plus un tableau de nombres (seq dans mon code), qui contient des sous-séquences croissantes. Lorsque le nombre pris est supérieur à tous les numéros de la sous-séquence, mettez-le à la fin de seq et augmentez le compteur de longueur de la sous-séquence de 1. Lorsque le nombre est inférieur au plus grand nombre de la sous-séquence, mettez-le quand même dans seq , à l’endroit où il appartient de garder la sous-séquence triée en remplaçant un nombre existant. La sous-séquence est initialisée avec la longueur du tableau de nombres d'origine et avec la valeur initiale -inf, ce qui signifie le plus petit int du système d'exploitation donné.

Exemple:

nombres = {10, 9, 2, 5, 3, 7, 101, 18}

seq = {-inf, -inf, -inf, -inf, -inf, -inf, -inf}

voici comment la séquence change lorsque nous parcourons les nombres de gauche à droite:

 seq = {10, -inf, -inf, -inf, -inf, -inf, -inf}
 seq = {9, -inf, -inf, -inf, -inf, -inf, -inf}
 seq = {2, -inf, -inf, -inf, -inf, -inf, -inf}
 seq = {2, 5, -inf, -inf, -inf, -inf, -inf}
 seq = {2, 3, -inf, -inf, -inf, -inf, -inf}
 seq = {2, 3, 7, -inf, -inf, -inf, -inf}
 seq = {2, 3, 7, 101, -inf, -inf, -inf}
 seq = {2, 3, 7, 18, -inf, -inf, -inf}

La sous-séquence croissante la plus longue du tableau a une longueur de 4.

Voici le code:

int longestIncreasingSubsequence(const vector<int> &numbers){
    if (numbers.size() < 2)
        return numbers.size();
    vector<int>seq(numbers.size(), numeric_limits<int>::min());
    seq[0] = numbers[0];
    int len = 1;
    vector<int>::iterator end = next(seq.begin());
    for (size_t i = 1; i < numbers.size(); i++) {
        auto pos = std::lower_bound(seq.begin(), end, numbers[i]);
        if (pos == end) {
            *end = numbers[i];
            end = next(end);
            len++;
        }
        else
            *pos = numbers[i];
    }
    return len;
}

Bien, jusqu'ici tout va bien, mais comment savons-nous que l'algorithme calcule la longueur de la sous-séquence la plus longue (ou l'une des plus longues, il peut y avoir plusieurs sous-séquences de la même taille)? Voici ma preuve: 

Supposons que l'algorithme ne calcule pas la longueur de la sous-séquence la plus longue. Dans la séquence d'origine, il doit exister un nombre tel que l'algorithme manque et qui rallongerait la sous-séquence. Disons, pour une sous-séquence x1, X2, ..., Xn il existe un nombre y tel que xk <y <xk + 1, 1 <= k <= n. Pour contribuer à la sous-séquence, y doit être situé dans la séquence d'origine entre xk et xk + 1. Mais nous avons une contradiction: lorsque l’algorithme parcourt la séquence initiale de gauche à droite, chaque fois qu’il rencontre un nombre supérieur à un nombre quelconque dans la sous-séquence courante, il étend la sous-séquence de 1. Au moment où l’algorithme le rencontrera, la sous-séquence aurait une longueur k et contiendrait des nombres x1, X2, ..., Xk. Parce que xk <y, l’algorithme étendrait la sous-séquence de 1 et inclurait y dans la sous-séquence. La même logique s'applique lorsque y est le plus petit numéro de la sous-séquence et situé à gauche de x1 ou quand y est le plus grand nombre de la sous-séquence et est situé à droite de xn. Conclusion: ce nombre y n'existe pas et l'algorithme calcule la sous-séquence croissante la plus longue. J'espère que cela à du sens.

Dans la déclaration finale, je voudrais mentionner que l’algorithme peut être facilement généralisé pour calculer également la sous-séquence décroissante la plus longue, pour tous les types de données dont les éléments peuvent être ordonnés. L’idée est la même, voici le code :

template<typename T, typename cmp = std::less<T>>
size_t longestSubsequence(const vector<T> &elements)
{
    if (elements.size() < 2)
        return elements.size();
    vector<T>seq(elements.size(), T());
    seq[0] = elements[0];
    size_t len = 1;
    auto end = next(seq.begin());
    for (size_t i = 1; i < elements.size(); i++) {
        auto pos = std::lower_bound(seq.begin(), end, elements[i], cmp());
        if (pos == end) {
            *end = elements[i];
            end = next(end);
            len++;
        }
        else
            *pos = elements[i];
    }
    return len;
}

Exemples d'utilisation:

int main()
{
    vector<int> nums = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
    size_t l = longestSubsequence<int>(nums); // l == 6 , longest increasing subsequence

    nums = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
    l = longestSubsequence<int, std::greater<int>>(nums); // l == 5, longest decreasing subsequence

    vector<string> vstr = {"b", "a", "d", "bc", "a"};
    l = longestSubsequence<string>(vstr); // l == 2, increasing


    vstr = { "b", "a", "d", "bc", "a" };
    l = longestSubsequence<string, std::greater<string>>(vstr); // l == 3, decreasing

} 
0
Andrushenko Alexander

Voici une implémentation Python plus compacte mais toujours plus efficace:

def longest_increasing_subsequence_indices(seq):
    from bisect import bisect_right

    if len(seq) == 0:
        return seq

    # m[j] in iteration i is the last index of the increasing subsequence of seq[:i]
    # that ends with the lowest possible value while having length j
    m = [None] * len(seq)
    predecessor = [None] * len(seq)
    best_len = 0

    for i, item in enumerate(seq):
        j = bisect_right([seq[k] for k in m[:best_len]], item)
        m[j] = i
        predecessor[i] = m[j-1] if j > 0 else None
        best_len = max(best_len, j+1)

    result = []
    i = m[best_len-1]
    while i is not None:
        result.append(i)
        i = predecessor[i]
    result.reverse()
    return result

def longest_increasing_subsequence(seq):
    return [seq[i] for i in longest_increasing_subsequence_indices(seq)]
0
isarandi