Existe-t-il un algorithme qui trouve des sous-séquences triées de taille trois en temps


21

Je veux prouver ou infirmer l'existence d'un algorithme qui, étant donné un tableau d'entiers, trouve trois indices i , j et k tels que i < j < k et A [ i ] < A [ j ] < A [ k ] (ou constate qu'il n'y a pas un tel triple) en temps linéaire.Ai,jki<j<kA[i]<A[j]<A[k]

Ce n'est pas une question de devoirs; Je l'ai vu sur un forum de programmation conçu comme «essayez d'implémenter un tel algorithme». Je soupçonne que c'est impossible après diverses expériences. Mon intuition me le dit, mais cela ne compte vraiment pour rien.

Je voudrais le prouver formellement. Comment faites-vous? J'aimerais idéalement voir une preuve présentée étape par étape, puis si vous êtes si enclin, une explication sur la façon de prouver / réfuter des questions simples comme celle-ci en général. Si cela peut vous aider, quelques exemples:

[1,5,2,0,3] → (1,2,3)
[5,6,1,2,3] → (1,2,3)
[1,5,2,3] → (1,2,3)
[5,6,1,2,7] → (1,2,7)
[5,6,1,2,7,8] → (1,2,7)
[1,2,999,3] → (1,2,999)
[999,1,2,3] → (1,2,3)
[11,12,8,9,5,6,3,4,1,2,3] → (1,2,3)
[1,5,2,0,-5,-2,-1] → (-5,-2,-1)

J'ai supposé que l'on pouvait itérer sur , et chaque fois qu'il y a un i < j (notre j actuel , c'est-à-dire), nous faisons un nouveau triple et le poussons sur un tableau. Nous continuons à avancer et à comparer chaque triple jusqu'à ce que l'un de nos triplets soit complet. Il est donc comme , ! Mais je pense que c'est plus complexe que le simple O ( n ) car le nombre de triplets sur notre triple tableau correspondrait dans le pire des cas à la taille de la liste d'entrée.Ai<jj[1,5,2,0,-5,-2,-1] → 1..2.. -5.. -2.. -1[1,5,2,0,-5,-2,3,-1] → 1..2.. -5.. -2.. 3O(n)


6
Voir la sous

Notez que dans le pire des cas (tableau trié), vous avez même nombreux triplets appropriés. Veuillez envisager de donner l'algorithme que vous proposez comme pseudo-code; Je pense que votre explication n'est pas complète. Θ(n3)
Raphael

Réponses:


14

Il s'agit de la variation du problème de sous- séquence croissant le plus long ; c'est la solution présentée sur Wikipédia en utilisant deux tableaux auxiliaires et P :MP

  • - stocke la position k de la plus petite valeur A [ k ] de sorte qu'il y ait une sous-séquence croissante de longueur j se terminant en A [ k ] sur la plage k i (notons que nous avons j k i ici, parce que j représente la longueur de la sous-séquence croissante, et k représente la position de sa terminaison. Évidemment, nous ne pouvons jamais avoir une sous-séquence croissante de longueur 13 se terminant en position 11M[j]kA[k]jA[k]kijkijk1311. par définition).ki
  • - stocke la position du prédécesseur de A [ k ] dans la sous-séquence croissante la plus longue se terminant en A [ k ] .P[k]A[k]A[k]

    De plus, l'algorithme stocke une variable représentant la longueur de la sous-séquence croissante la plus longue trouvée jusqu'à présent.L

Cet algorithme s'exécute dans le pire des cas . Votre problème est un cas spécial qui vous permet de retourner lorsque L = 3 qui pousse le runtime à O ( n ) car la recherche binaire ne s'exécute que sur des tableaux de longueur au plus deux, ce qui est donc dans le temps O ( 1 ) par opposition à Θ ( log n ) dans le cas général.Θ(nlogn)L=3O(n)O(1)Θ(logn)

Considérez le pseudo-code modifié:

 L = 0
 for i = 1, 2, ... n:
    binary search for the largest positive j ≤ L
      such that X[M[j]] < X[i] (or set j = 0 if no such value exists)
    P[i] = M[j]
    if j == L or X[i] < X[M[j+1]]:
       M[j+1] = i
       L = max(L, j+1)
   if L==3 : return true; // you can break here, and return true.
return false; // because L is smaller than 3.

@SaeedAmiri J'ai vu le commentaire mais je n'ai pas encore eu le temps de le relire (j'ai posté la question avant d'aller me coucher). J'ai soupçonné de votre lien que notre cas spécial L = 3 aiderait d'une manière ou d'une autre mais n'avait pas eu la chance de comprendre les détails. Je suis actuellement au travail et à temps limité. Soyez assuré que j'apprécie votre réponse. Il serait superficiel de ma part de vous en remercier sans bien comprendre chaque ligne qu'il contient.
Christopher Fait le

@SaeedAmiri: Je suis d'accord que vous vous attendez à plus de "comblement des lacunes" ici, mais vous devez encore donner les arguments de coin d'une preuve (aussi sommaire) au moins. En ce qui concerne l'OP, il semble être basé en Italie, il était donc probablement endormi rapidement entre votre commentaire et votre réponse (et il est fort probable qu'il s'occupe de l'est maintenant).
Raphael

@ChristopherDone, je ne veux pas t'énerver, désolé c'est mon erreur, tu as définitivement raison.

+1: Cela se généralise bien, ne fait qu'une seule passe et est un espace . O(1)
Aryabhata

OK, ça a l'air bien. Il m'a fallu un certain temps pour comprendre le comportement de l'algorithme général de séquence augmentant le plus longtemps. Après cela, le changement de longueur maximale == 3 est très bien. Merci!
Christopher Fait le

11

Une note sur la méthodologie

J'ai réfléchi un peu à ce problème et suis parvenu à une solution. Quand j'ai lu la réponse de Saeed Amiri , j'ai réalisé que ce que j'ai trouvé était une version spécialisée de l'algorithme de recherche de sous-séquence le plus long standard pour une séquence de longueur 3. Je poste la façon dont j'ai trouvé la solution, parce que je pense qu'elle est un exemple intéressant de résolution de problèmes.

La version à deux éléments

i<jA[i]<A[j]

Ai<j,A[i]A[j]i,A[i]A[i+1]iA[i]<A[i+1]

Ce cas est très simple; nous essaierons de le généraliser. Cela montre que le problème tel qu'il est énoncé n'est pas résoluble: les indices demandés n'existent pas toujours. Nous demanderons donc plutôt que l'algorithme renvoie des indices valides, s'ils existent, ou affirme correctement qu'aucun indice de ce type n'existe.

Venir avec l'algorithme

A(A[i1],,A[im])i1<<imA(A[i],A[i+1],,A[i+m1])

Nous venons de voir que les indices demandés n'existent pas toujours. Notre stratégie sera d'étudier lorsque les indices n'existent pas. Nous le ferons en supposant que nous essayons de trouver les indices et de voir comment notre recherche pourrait mal tourner. Ensuite, les cas où la recherche ne se passe pas mal fourniront un algorithme pour trouver les indices.

4,3,2,1,0

j=i+1k=j+1A[i]<A[i+1]<A[i+2]

4,3,2,1,2,3,2,1,0

A[j]<A[j+1]iA[i]<A[j]kA[j+1]<A[k]

4,3,2,2,5,1,5,0,5,1,0

ik

3,2,1,3,5,2,5,1,5,0,5, -0,5,1,25, -0,25 3,2,1,2,5,1,5,0,5,2,1,0

ijki

2.1,3,2,1,2.5,1,5,0,5,2,1,0 1,2,0,2,5,1,5,0,5

i(i,j)ki(i,j)(i,j)i>jA[i]<A[i]ii(i,j)jA[j]<A[j](i,j)

Énoncé de l'algorithme

Donné en syntaxe Python, mais attention, je ne l'ai pas testé.

def subsequence3(A):
    """Return the indices of a subsequence of length 3, or None if there is none."""
    index1 = None; value1 = None
    index2 = None; value2 = None
    for i in range(0,len(A)):
        if index1 == None or A[i] < value1:
            index1 = i; value1 = A[i]
        else if A[i] == value1: pass
        else if index2 == None:
            index2 = (index1, i); value2 = (value1, A[i])
        else if A[i] < value2[1]:
            index2[1] = i; value2[1] = A[i]
        else if A[i] > value2[1]:
            return (index2[0], index2[1], i)
    return None

Croquis de preuve

index1est l'indice du minimum de la partie du tableau qui a déjà été parcourue (s'il se produit plusieurs fois, on retient la première occurrence), ou Noneavant de traiter le premier élément. index2stocke les indices de la sous-séquence croissante de longueur 2 dans la partie déjà traversée du tableau qui a l'élément le plus grand le plus bas, ou Nonesi une telle séquence n'existe pas.

Lors des return (index2[0], index2[1], i)exécutions, nous avons value2[0] < value[1](c'est un invariant de value2) et value[1] < A[i](évident d'après le contexte). Si la boucle se termine sans invoquer le retour anticipé value1 == None, dans ce cas, il n'y a pas de sous-séquence croissante de longueur 2 sans parler de 3, ou value1contient la sous-séquence croissante de longueur 2 qui a l'élément le plus grand le plus bas. Dans ce dernier cas, nous avons en outre l'invariant qu'aucune sous-séquence croissante de longueur 3 ne se termine plus tôt que value1; par conséquent, le dernier élément d'une telle sous-séquence, ajouté à value2, formerait une sous-séquence croissante de longueur 3: comme nous avons également l'invariant qui value2ne fait pas partie d'une sous-séquence croissante de longueur 3 contenue dans la partie déjà traversée du tableau, il est pas une telle sous-séquence dans l'ensemble du tableau.

Prouver les invariants susmentionnés est laissé comme un exercice pour le lecteur.

Complexité

O(1)O(1)O(n)

Preuve formelle

Laissé en exercice au lecteur.


8

O(n)O(n)

Tout d'abord, parcourez le tableau de gauche à droite en conservant une pile et un tableau auxiliaire qui vous indique pour chaque élément, l'indice d'un élément supérieur à lui et à droite de celui-ci.

1

Chaque fois que vous considérez un nouvel élément dans le tableau, si cet élément est supérieur à l'élément supérieur de la pile, vous le supprimez de la pile et définissez l'élément de tableau aux correspondant au sommet pour avoir l'index du nouvel élément sous considération.

Continuez à extraire des éléments de la pile et à définir l'index correspondant, tandis que l'élément en cours est supérieur. Une fois que le sommet a un élément qui n'est pas moindre (ou devient vide), poussez l'élément actuel sur la pile et passez à l'élément suivant du tableau, en répétant l'étape ci-dessus.

Faites un autre passage (et un autre tableau auxiliaire), mais en allant de droite à gauche.

1

O(n)

ki

Le pseudo-code de la première passe peut ressembler à ceci:

Stack <Pair<Elem, Index>> greats;
Elem auxArr[inputArr.Length];

for (Index i = 0; i < inputArr.Length; i++) {

    while (!greats.IsEmpty() && inputArr[i] > greats.PeekTop().Elem) {
        Pair top = greats.Pop();
        auxArr[top.Index] = i;
    }

    Pair p;
    p.Elem = inputArr[i];
    p.Index = i;

    greats.Push(p);
}

"Puisque vous ne considérez chaque élément du tableau qu'un nombre constant de fois, c'est le temps O (n)." Oh, crums. D'une manière ou d'une autre, j'avais exclu plusieurs passes constantes, en les rejetant comme n'étant pas O (n). Très stupide. Je vous suis reconnaissant de votre explication et je vais essayer une fois de plus de la résoudre.
Christopher Fait le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.