Existe um algoritmo que encontra subsequências ordenadas de tamanho três em tempo?

21

Eu quero provar ou refutar a existência de um algoritmo que, dada uma matriz de números inteiros, encontre três índices e modo que e (ou descobre que não existe esse triplo) no tempo linear.i , j k i < j < k A [ i ] < A [ j ] < A [ k ]Ai,jki<j<kA[i]<A[j]<A[k]

Esta não é uma questão de lição de casa; Eu vi isso em um fórum de programação enquadrado como "tente implementar um algoritmo desse tipo". Suspeito que seja impossível após várias experiências. Minha intuição me diz isso, mas isso realmente não conta para nada.

Eu gostaria de provar isso formalmente. Como você faz isso? Idealmente, gostaria de ver uma prova apresentada passo a passo e, se você estiver inclinado, algumas explicações sobre como provar / refutar perguntas simples como esta em geral. Se ajudar, alguns exemplos:

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

Eu supunha que alguém poderia iterar sobre , e cada vez que existe um (nosso atual , ou seja), fazemos um novo triplo e o empurramos para uma matriz. Continuamos avançando e comparando cada triplo até que um de nossos triplos esteja completo. Então, é como , ! Mas acho que isso é mais complexo do que mero pois o número de triplos em nossa matriz tripla corresponderia, na pior das hipóteses, ao tamanho da lista de entradas.i < j jAi<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)

Christopher Done
fonte
Observe que, na pior das hipóteses (matriz classificada), você ainda tem muitos triplos adequados. Por favor, considere fornecer o algoritmo que você propõe como pseudo-código; Eu acho que sua explicação não está completa. Θ(n3)
Raphael

Respostas:

14

Essa é a variação do maior problema de subsequência crescente ; esta é a solução apresentada na Wikipedia usando duas matrizes auxiliares e :PMP

  • k A [ k ] j A [ k ] k i j k i j k 13 11 k iM[j] - armazena a posição do menor valor forma que exista uma subsequência crescente de comprimento termina em no intervalo (observe que temos aqui, porque representa o comprimento do aumento da subsequência, e representa a posição do seu término. Obviamente, que nunca pode ter uma subsequência aumento de comprimento que termina na posição . por definição).kA[k]jA[k]kijkijk1311ki
  • A [ k ] A [ k ]P[k] - armazena a posição do predecessor de na subsequência crescente mais longa que termina em .A[k]A[k]

    Além disso, o algoritmo armazena uma variável representando o comprimento da subsequência crescente mais longa encontrada até o momento.L

Esse algoritmo é executado no pior dos casos . Seu problema é um caso especial que permite retornar quando que empurra o tempo de execução para porque a pesquisa binária é executada apenas em matrizes de comprimento no máximo duas, o que é, portanto, no tempo em oposição a no caso geral.L = 3 O ( n ) O ( 1 ) Θ ( log n )Θ(nlogn)L=3O(n)O(1)Θ(logn)

Considere o pseudo-có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.

fonte
@SaeedAmiri Vi o comentário, mas ainda não tive tempo de analisá-lo (postei a pergunta antes de ir para a cama). Eu suspeitava do seu link que nosso caso especial L = 3 ajudaria de alguma forma, mas não teve chance de entender os detalhes. Atualmente, estou no trabalho e com tempo limitado. Tenha certeza de que aprecio sua resposta. Seria superficial da minha parte agradecer por isso, sem entender completamente todas as linhas dentro dela.
Christopher Feito
@SaeedAmiri: Concordo que você espera mais "preenchimento de lacunas" aqui, mas você ainda precisa apresentar os argumentos de canto de uma prova (por mais superficial que seja). Em relação ao OP, ele parece estar baseado na Itália, então provavelmente estava dormindo profundamente entre o seu comentário e a resposta (e é provável que ele esteja ocupado com o leste agora).
Raphael
@ChristopherDone, eu não quero incomodá-lo, desculpe, é um erro meu, você está definitivamente certo.
+1: Isso generaliza bem, faz apenas uma passagem e é espaço. O(1)
Aryabhata
OK, parece bom. Demorei um pouco para entender o comportamento do algoritmo geral de seqüência crescente mais longo. Depois disso, a alteração do comprimento máximo == 3 é boa. Obrigado!
9114 Christopher Done
11

Uma nota sobre metodologia

Pensei um pouco sobre esse problema e cheguei a uma solução. Quando li a resposta de Saeed Amiri , percebi que o que surgiu foi uma versão especializada do algoritmo de localização de subsequência mais longa padrão para uma sequência de comprimento 3. Estou publicando o caminho para a solução, porque acho que é um exemplo interessante de solução de problemas.

A versão de dois elementos

Vamos começar pequenos: em vez de procurar três índices nos quais os elementos estão em ordem, vamos procurar dois: modo que .A [ i ] < A [ j ]i<jA[i]<A[j]

Se estiver diminuindo (por exemplo, ou equivalentemente ), então não existem tais índices. Caso contrário, há um índice tal que .Ai<j,A[i]A[j]i,A[i]A[i+1]iA[i]<A[i+1]

Este caso é muito simples; vamos tentar generalizar. Isso mostra que o problema descrito não é solucionável: os índices solicitados nem sempre existem. Portanto, pediremos que o algoritmo retorne índices válidos, se existirem, ou afirme corretamente que esses índices não existem.

Apresentando o algoritmo

Usarei o termo subsequência para significar uma extração da matriz consiste em índices que podem não ser consecutivos ( com ) e corra para significar elementos consecutivos de ( ).A(A[i1],,A[im])i1<<imA(A[i],A[i+1],,A[i+m1])

Acabamos de ver que os índices solicitados nem sempre existem. Nossa estratégia será estudar quando os índices não existirem. Faremos isso supondo que estamos tentando encontrar os índices e ver como nossa pesquisa pode dar errado. Os casos em que a pesquisa não der errado fornecerão um algoritmo para encontrar os índices.

4,3,2,1,0

Com dois índices, podemos encontrar índices consecutivos. Com três índices, talvez não consigamos criar e . No entanto, podemos considerar o caso em que há uma execução de três elementos estritamente crescentes ( ) a serem resolvidos, pois é fácil reconhecer essas execuções, e veja como essa condição pode não ser atendida. Suponha que a sequência não tenha uma execução estritamente crescente de comprimento 3.j=i+1k=j+1A[i]<A[i+1]<A[i+2]

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

A sequência só tem execuções estritamente crescentes de comprimento 2 (que chamaremos de pares ordenados para abreviar), separadas por uma execução decrescente de comprimento pelo menos 2. Para uma execução estritamente crescente para fazer parte de uma sequência crescente de 3 elementos, deve haver um elemento anterior tal que ou um elemento posterior tal que .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

Um caso em que nem nem existe é quando cada par ordenado é inteiramente menor que o próximo. Isso não é tudo: quando os pares estão entrelaçados, precisamos compará-los com mais detalhes.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

O elemento mais à esquerda de uma subsequência crescente precisa chegar cedo e ser pequeno. O próximo elemento precisa ser maior, mas o menor possível para encontrar um terceiro elemento maior . O primeiro elemento nem sempre é o menor elemento da sequência e nem sempre é o primeiro para o qual existe um elemento maior subsequente - às vezes há uma subsequência de 2 elementos mais baixa adiante e às vezes há uma melhor apto para o mínimo já encontrado.ijki

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

Indo da esquerda para a direita, tentativamente escolhemos o menor elemento como . Se encontrarmos um elemento maior mais à direita, escolhemos esse par como tentativa . Se encontrarmos um ainda maior , vencemos. O ponto principal a ser observado é que nossa escolha de e nossa escolha de são atualizadas independentemente: se tivermos um candidato e encontrarmos modo que , se torna o próximo candidato mas permanece. Somente se acharmos tal que irái(i,j)ki(i,j)(i,j)i>jA[i]<A[i]ii(i,j)jA[j]<A[j](i,j) tornar-se o novo par candidato.

Declaração do algoritmo

Dado na sintaxe do Python, mas cuidado, eu não testei.

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

Esboço de prova

index1é o índice do mínimo da parte da matriz que já foi percorrida (se ocorrer várias vezes, reteremos a primeira ocorrência) ou Noneantes de processar o primeiro elemento. index2armazena os índices da crescente subsequência do comprimento 2 na parte já percorrida da matriz que possui o menor elemento maior ou Nonese essa sequência não existir.

Quando return (index2[0], index2[1], i)executado, temos value2[0] < value[1](isso é invariável value2) e value[1] < A[i](óbvio do contexto). Se o loop terminar sem chamar o retorno antecipado, value1 == Nonenesse caso, não haverá subsequência crescente do comprimento 2 e muito menos 3 ou value1conterá a subsequência crescente do comprimento 2 que possui o menor elemento maior. No último caso, temos além disso o invariante de que nenhuma subsequência crescente do comprimento 3 termina mais cedo do que value1; portanto, o último elemento de qualquer subsequência desse tipo, adicionado a value2, formaria uma subsequência crescente de comprimento 3: como também temos o invariante que value2não faz parte de uma subsequência crescente de comprimento 3 contida na parte já percorrida da matriz, não existe essa subsequência em toda a matriz.

Provar os invariantes mencionados acima é deixado como um exercício para o leitor.

Complexidade

Usamos memória adicional e atravessamos a matriz como um fluxo da esquerda para a direita. Executamos processamento para cada elemento, levando a um tempo de execução de .O ( 1 ) O ( n )O(1)O(1)O(n)

Prova formal

Deixado como um exercício para o leitor.

Gilles 'SO- parar de ser mau'
fonte
8

Existe um algoritmo de tempo , que usa espaço .O ( n )O(n)O(n)

Primeiro, percorra a matriz da esquerda para a direita, mantendo uma pilha e uma matriz auxiliar que informa para cada elemento o índice de um elemento maior que ele e à direita dele.

Inicialmente, a pilha está vazia e o array aux contém todos os s.1

Sempre que considerar um novo elemento na matriz, se esse elemento for maior que o elemento superior da pilha, você o retirará da pilha e configurará o elemento auxiliar da matriz correspondente à parte superior para ter o índice do novo elemento em consideração.

Continue retirando os elementos da pilha e configurando o índice correspondente, enquanto o elemento atual é maior. Quando a parte superior tiver um elemento que não seja menor (ou ficar vazio), empurre o elemento atual para a pilha e prossiga para o próximo elemento da matriz, repetindo a etapa acima.

Faça outra passagem (e outra matriz auxiliar), mas indo da direita para a esquerda.

Faça um passe pelas matrizes aux e escolha uma posição em que as duas entradas correspondentes na matriz não sejam .1

Como você considera cada elemento da matriz apenas um número constante de vezes, esse é o tempo .O(n)

Eu acredito que você também pode se livrar das pilhas (considerando "máximos de sufixo" e "mínimos de prefixo"), mas as pilhas fornecem os elementos mais próximos. Portanto, se você quiser minimizar o say , poderá usar as pilhas.ki

O pseudo-código para a primeira passagem pode ser assim:

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
fonte
"Como você considera cada elemento da matriz apenas um número constante de vezes, esse é o tempo O (n)." De alguma forma, eu havia descartado várias passagens constantes, descartando-a como não O (n). Muito estúpido. Sou grato pela sua explicação e tentarei mais uma vez resolvê-la.
Christopher Feito