¿Hay algún algoritmo que encuentre subsecuencias ordenadas de tamaño tres en el tiempo

21

Quiero probar o refutar la existencia de un algoritmo que, dada una matriz de enteros, encuentra tres índices y manera que y (o encuentra que no existe tal triple) en tiempo lineal.UNAyo,jkyo<j<kUNA[yo]<UNA[j]<UNA[k]

Esta no es una pregunta de tarea; Lo vi en un foro de programación enmarcado como "tratar de implementar dicho algoritmo". Sospecho que es imposible después de varios experimentos. Mi intuición me lo dice, pero eso realmente no cuenta para nada.

Me gustaría demostrarlo formalmente. ¿Cómo lo haces? Idealmente, me gustaría ver una prueba presentada paso a paso, y luego, si está tan inclinado, alguna explicación sobre cómo probar / refutar preguntas simples como esta en general. Si ayuda, algunos ejemplos:

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

Supuse que uno podría iterar sobre , y cada vez que hay un (nuestro actual , es decir), hacemos un nuevo triple y lo empujamos a una matriz. Continuamos dando un paso y comparando cada triple hasta que uno de nuestros triples esté completo. Así es como , ! Pero creo que esto es más complejo que el simple ya que el número de triples en nuestra matriz triple en el peor de los casos correspondería al tamaño de la lista de entrada.UNAyo<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(norte)

Christopher Done
fuente
Tenga en cuenta que en el peor de los casos (matriz ordenada) incluso tiene muchos triples adecuados. Considere dar el algoritmo que propone como pseudocódigo; Creo que tu explicación no está completa. Θ(norte3)
Raphael

Respuestas:

14

Esta es la variación del problema de subsecuencia creciente más largo ; Esta es la solución presentada en Wikipedia utilizando dos matrices auxiliares y P :MP

  • : almacena la posición k del valor más pequeño A [ k ] de modo que hay una subsecuencia creciente de longitud j que termina en A [ k ] en el rango k i (tenga en cuenta que aquí tenemos j k i , porque j representa la longitud de la subsecuencia creciente yk representa la posición de su terminación. Obviamente, nunca podemos tener una subsecuencia creciente de longitud 13 que termine en la posición 11M[j]kA[k]jA[k]kijkijk1311. por definición).ki
  • : almacena la posición del predecesor de A [ k ] en la subsecuencia creciente más larga que termina en A [ k ] .P[k]A[k]A[k]

    Además, el algoritmo almacena una variable representa la longitud de la subsecuencia creciente más larga encontrada hasta ahora.L

Este algoritmo se ejecuta en el peor de los casos . Su problema es un caso especial que le permite regresar cuando L = 3, lo que empuja el tiempo de ejecución a O ( n ) porque la búsqueda binaria se ejecuta solo en matrices de longitud como máximo dos, que por lo tanto es en el tiempo O ( 1 ) en lugar de Θ ( log n ) en el caso general.Θ(nlogn)L=3O(n)O(1)Θ(logn)

Considere el pseudocódigo modificado:

 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.

fuente
@SaeedAmiri Vi el comentario pero aún no había tenido tiempo de revisarlo (publiqué la pregunta antes de acostarme). Sospeché por su enlace que nuestro caso especial L = 3 ayudaría de alguna manera, pero no había tenido la oportunidad de comprender los detalles. Actualmente estoy en el trabajo y tengo poco tiempo. Tenga la seguridad de que aprecio su respuesta. Sería superficial de mi parte agradecerle sin comprender completamente cada línea dentro de él.
Christopher Hecho el
@SaeedAmiri: Estoy de acuerdo en que esperas más "llenar el vacío" aquí, pero todavía tienes que dar los argumentos de la esquina de una prueba (aunque sea incompleta) al menos. Con respecto al OP, parece estar basado en Italia, por lo que probablemente estaba profundamente dormido entre su comentario y respuesta (y es probable que ahora esté ocupado con el este).
Raphael
@ChristopherDone, no quiero molestarte, lo siento, es mi error, definitivamente tienes razón.
+1: Esto se generaliza muy bien, realiza una sola pasada y es espacio. O(1)
Aryabhata
OK, se ve bien. Me tomó un tiempo asimilar el comportamiento del algoritmo general de secuencia creciente más largo. Después de eso, el cambio de longitud máxima == 3 está bien. ¡Gracias!
Christopher Hecho el
11

Una nota sobre metodología

Pensé un poco en este problema y llegué a una solución. Cuando leí la respuesta de Saeed Amiri , me di cuenta de que se me ocurrió una versión especializada del algoritmo estándar de búsqueda de subsecuencias más largo para una secuencia de longitud 3. Estoy publicando la forma en que se me ocurrió la solución, porque creo que Es un ejemplo interesante de resolución de problemas.

La versión de dos elementos.

Comencemos con algo pequeño: en lugar de buscar tres índices en los que los elementos estén en orden, busquemos dos: tal que A [ i ] < A [ j ] .yo<jUNA[yo]<UNA[j]

Si está disminuyendo (es decir, i < j , A [ i ] A [ j ] , o de manera equivalente i , A [ i ] A [ i + 1 ] , entonces no existen tales índices. De lo contrario, hay un índice i tal que A [ i ] < A [ i + 1 ] .UNAyo<j,UNA[yo]UNA[j]yo,UNA[yo]UNA[yo+1]yoUNA[yo]<UNA[yo+1]

Este caso es muy simple; Intentaremos generalizarlo. Muestra que el problema tal como se indicó no tiene solución: los índices solicitados no siempre existen. Por lo tanto, pediremos más bien que el algoritmo devuelva índices válidos, si existen, o afirme correctamente que no existen tales índices.

Subiendo con el algoritmo

Usaré el término subsecuencia para significar un extracto de la matriz consiste en índices que pueden no ser consecutivos ( ( A [ i 1 ] , , A [ i m ] ) con i 1 < < i m ), y ejecutaré significa elementos consecutivos de A ( ( A [ i ] , A [ i + 1 ] , , A [UNA(UNA[yo1],...,UNA[yometro])yo1<<yometroUNA ).(A[i],A[i+1],,A[i+m1])

Acabamos de ver que los índices solicitados no siempre existen. Nuestra estrategia será estudiar cuando los índices no existen. Haremos esto suponiendo que estamos tratando de encontrar los índices y viendo cómo nuestra búsqueda podría salir mal. Entonces, los casos en los que la búsqueda no sale mal proporcionarán un algoritmo para encontrar los índices.

4,3,2,1,0

Con dos índices, podríamos encontrar índices consecutivos. Con tres índices, es posible que no podamos encontrar y k = j + 1 . Sin embargo, podemos considerar el caso cuando hay una serie de tres elementos estrictamente crecientes ( A [ i ] < A [ i + 1 ] < A [ i + 2 ] ) por resolver, ya que es fácil reconocer tales series, y vea cómo esta condición podría no cumplirse. Suponga que la secuencia no tiene una ejecución estrictamente creciente de longitud 3.j=yo+1k=j+1UNA[yo]<UNA[yo+1]<UNA[yo+2]

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

La secuencia solo tiene carreras estrictamente crecientes de longitud 2 (que llamaré pares ordenados para abreviar), separadas por una carrera decreciente de longitud al menos 2. Para una carrera estrictamente creciente para formar parte de una secuencia creciente de 3 elementos, debe haber un elemento anterior i tal que A [ i ] < A [ j ] o un elemento posterior k tal que A [ j + 1 ] < A [ kUNA[j]<UNA[j+1]yoUNA[yo]<UNA[j]k .UNA[j+1]<UNA[k]

4,3,2,2.5,1.5,0.5,1,0

Un caso en el que ni ni k existe es cuando cada par ordenado es completamente inferior al siguiente. Esto no es todo: cuando los pares están entrelazados, necesitamos compararlos más finamente.yok

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

El elemento más a la izquierda de una subsecuencia creciente necesita llegar temprano y ser pequeño. El siguiente elemento j debe ser más grande, pero lo más pequeño posible para poder encontrar un tercer elemento más grande k . El primer elemento i no siempre es el elemento más pequeño de la secuencia, y tampoco es siempre el primero para el que hay un elemento posterior más grande, a veces hay una subsecuencia inferior de 2 elementos más adelante, y a veces hay una mejor apto para el mínimo ya encontrado.yojkyo

2.1,3,2,1,2.5,1.5,0.5,2,1,0 1,2,0,2.5,1.5,0.5

yo(yo,j)kyo(yo,j)(yo,j)yo>jUNA[yo]<UNA[yo]yoyo(yo,j)jUNA[j]<UNA[j](yo,j)

Declaración del algoritmo

Dado en la sintaxis de Python, pero tenga en cuenta que no lo he probado.

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

Bosquejo de prueba

index1es el índice del mínimo de la parte de la matriz que ya se ha atravesado (si ocurre varias veces, conservamos la primera aparición) o Noneantes de procesar el primer elemento. index2almacena los índices de la subsecuencia creciente de longitud 2 en la parte ya atravesada de la matriz que tiene el elemento más grande más bajo, o Nonesi tal secuencia no existe.

Cuando se return (index2[0], index2[1], i)ejecuta, tenemos value2[0] < value[1](esto es una invariante de value2) y value[1] < A[i](obvio por el contexto). Si el ciclo termina sin invocar el retorno temprano, tampoco value1 == None, en cuyo caso no hay subsecuencia creciente de longitud 2 y mucho menos 3, o value1contiene la subsecuencia creciente de longitud 2 que tiene el elemento más grande más bajo. En el último caso, además tenemos la invariante de que ninguna subsecuencia creciente de longitud 3 termina antes que value1; por lo tanto, el último elemento de cualquiera de esas subsecuencias, sumado a value2, formaría una subsecuencia creciente de longitud 3: como también tenemos el invariante que value2no es parte de una subsecuencia creciente de longitud 3 contenida en la parte ya atravesada de la matriz, allí no existe tal subsecuencia en toda la matriz.

Probar los invariantes antes mencionados se deja como un ejercicio para el lector.

Complejidad

O(1)O(1)O(norte)

Prueba formal

A la izquierda como ejercicio para el lector.

Gilles 'SO- deja de ser malvado'
fuente
8

O(norte)O(norte)

Primero, recorra la matriz de izquierda a derecha manteniendo una pila y una matriz auxiliar que le indica para cada elemento, el índice de un elemento mayor que él y a la derecha del mismo.

Inicialmente, la pila está vacía y la matriz auxiliar contiene todos -1s.

Cada vez que considera un nuevo elemento en la matriz, si ese elemento es mayor que el elemento superior de la pila, lo saca de la pila y establece el elemento de matriz auxiliar correspondiente a la parte superior para tener el índice del nuevo elemento debajo consideración.

Continúe sacando elementos de la pila y estableciendo el índice correspondiente, mientras que el elemento actual es mayor. Una vez que la parte superior tiene un elemento que no es menor (o se vacía), empuje el elemento actual hacia la pila y continúe con el siguiente elemento de la matriz, repitiendo el paso anterior.

Haga otra pasada (y otra matriz auxiliar), pero vaya de derecha a izquierda.

Pase por las matrices auxiliares y elija una posición donde las dos entradas correspondientes en la matriz no estén -1.

Dado que considera cada elemento de la matriz solo un número constante de veces, esto es O(norte) hora.

Creo que también puedes deshacerte de las pilas (considerando "máximos de sufijo" y "mínimos de prefijo"), pero las pilas te dan los elementos más cercanos. Entonces, si quisieras minimizar decirk-yo, podrías usar las pilas.

El pseudocódigo para la primera pasada podría verse así:

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);
}
Aryabhata
fuente
"Dado que considera cada elemento de la matriz solo un número constante de veces, este es el tiempo O (n)". Oh, cielos. De alguna manera, había descartado múltiples pases constantes, descartándolo como no O (n). Muy estúpido. Estoy agradecido por su explicación, e intentaré una vez más resolverlo.
Christopher Hecho el