Localizando duplicatas no tempo O (n) e no espaço O (1)

121

Entrada: Dada uma matriz de n elementos que contém elementos de 0 a n-1, com qualquer um desses números aparecendo inúmeras vezes.

Objetivo: encontrar esses números repetidos em O (n) e usando apenas espaço de memória constante.

Por exemplo, seja n 7 e a matriz seja {1, 2, 3, 1, 3, 0, 6}, a resposta deve ser 1 e 3. Verifiquei perguntas semelhantes aqui, mas as respostas usaram algumas estruturas de dados como HashSetetc.

Algum algoritmo eficiente para o mesmo?

Zaki
fonte

Respostas:

164

Isto é o que eu criei, que não requer o bit de sinal adicional:

for i := 0 to n - 1
    while A[A[i]] != A[i] 
        swap(A[i], A[A[i]])
    end while
end for

for i := 0 to n - 1
    if A[i] != i then 
        print A[i]
    end if
end for

O primeiro loop permite a matriz, de modo que, se o elemento xestiver presente pelo menos uma vez, uma dessas entradas estará na posição A[x].

Observe que ele pode não parecer O (n) à primeira vista, mas é - embora tenha um loop aninhado, ainda é executado no O(N)tempo. Uma troca ocorre apenas se houver uma ital que A[i] != i, e cada troca define pelo menos um elemento como A[i] == i, onde isso não era verdade antes. Isso significa que o número total de swaps (e, portanto, o número total de execuções do whilecorpo do loop) é no máximo N-1.

O segundo loop imprime os valores dos xquais A[x]não é igual x- já que o primeiro loop garante que, se xexistir pelo menos uma vez na matriz, uma dessas instâncias estará em A[x], isso significa que ele imprime os valores dos xquais não estão presentes em a matriz.

(Link Ideone para que você possa brincar com ele)

caf
fonte
10
@arasmussen: Sim. Eu vim com uma versão quebrada primeiro, no entanto. As restrições do problema dão uma pista da solução - o fato de que todo valor de matriz válido também é um índice de matriz válido sugere a[a[i]], e a restrição de espaço O (1) indica que a swap()operação é a chave.
caf
2
@caf: Por favor, execute seu código com a matriz, pois {3,4,5,3,4} falha.
NirmalGeo
6
@NirmalGeo: Essa não é uma entrada válida, porque 5não está no intervalo 0..N-1( Nneste caso 5).
caf
2
@caf a saída para {1,2,3,1,3,0,0,0,0,6} é 3 1 0 0 0 ou, em qualquer caso, em que a repetição seja superior a 2. Está correto o / p?
Terminal
3
Isso é incrível! Eu já vi várias variantes nessa questão, geralmente mais restritas, e esta é a maneira mais geral de resolvê-la que eu já vi. Mencionarei simplesmente que alterar a printinstrução para print itransformá-la em uma solução para stackoverflow.com/questions/5249985/… e (assumindo que "bag" seja uma matriz modificável)) Qk de stackoverflow.com/questions/3492302/… .
Jrandom_hacker
35

A resposta brilhante do caf imprime cada número que aparece k vezes na matriz k-1 vezes. Esse é um comportamento útil, mas é indiscutível que a questão exige que cada duplicado seja impresso apenas uma vez, e ele alude à possibilidade de fazer isso sem soprar os limites lineares de tempo / espaço constante. Isso pode ser feito substituindo seu segundo loop pelo seguinte pseudocódigo:

for (i = 0; i < N; ++i) {
    if (A[i] != i && A[A[i]] == A[i]) {
        print A[i];
        A[A[i]] = i;
    }
}

Isso explora a propriedade que após a execução do primeiro loop, se algum valor maparecer mais de uma vez, é garantido que uma dessas aparências esteja na posição correta, a saber A[m]. Se tomarmos cuidado, podemos usar esse local "residencial" para armazenar informações sobre se alguma duplicata foi impressa ou não.

Na versão caf, conforme examinamos a matriz, A[i] != iisso implicava A[i]uma duplicata. Na minha versão, confio em uma invariante ligeiramente diferente: isso A[i] != i && A[A[i]] == A[i]implica que A[i]é uma duplicata que não vimos antes . (Se você soltar a parte "que não vimos antes"), o resto poderá ser implícito na verdade invariável do caf e na garantia de que todas as duplicatas têm uma cópia em um local residencial. o início (após o 1º loop do caf terminar) e mostro abaixo que ele é mantido após cada etapa.

À medida que avançamos na matriz, o sucesso por A[i] != iparte do teste implica que A[i] pode ser uma duplicata que não foi vista antes. Se não vimos isso antes, esperamos que A[i]a localização da casa aponte para si mesma - é isso que é testado na segunda metade da ifcondição. Se for esse o caso, imprimimos e alteramos o local da residência para apontar para essa primeira duplicata encontrada, criando um "ciclo" em duas etapas.

Para ver que essa operação não altera nossa invariante, suponha m = A[i]que uma determinada posição seja isatisfatória A[i] != i && A[A[i]] == A[i]. É óbvio que a alteração que fazemos ( A[A[i]] = i) funcionará para impedir que outras ocorrências não domésticas msejam reproduzidas como duplicatas, causando a iffalha da segunda metade de suas condições, mas funcionará quando ichegar ao local de origem m? Sim, sim, porque agora, embora neste novo iachemos que a 1ª metade da ifcondição A[i] != ié verdadeira, a 2ª metade testa se o local para o qual aponta é um local de origem e acha que não é. Nessa situação, não sabemos mais se foi mou A[m]não o valor duplicado, mas sabemos que, de qualquer maneira,já foi relatado , porque esses 2 ciclos garantem que não apareçam no resultado do 1º loop do caf. (Observe que se m != A[m]exatamente um de me A[m]ocorre mais de uma vez e o outro não ocorre).

j_random_hacker
fonte
1
Sim, é muito parecido com o que eu criei. É interessante como um primeiro loop idêntico é útil para vários problemas diferentes, apenas com um loop de impressão diferente.
caf
22

Aqui está o pseudocódigo

for i <- 0 to n-1:
   if (A[abs(A[i])]) >= 0 :
       (A[abs(A[i])]) = -(A[abs(A[i])])
   else
      print i
end for

Código de exemplo em C ++

Prasoon Saurav
fonte
3
Muito inteligente - codificando a resposta no bit de sinal da entrada indexada!
holtavolt
3
@shang: Não pode ser. Confira a especificação do problema. "Dado um conjunto de n elementos , que contém elementos de 0 a n-1 "
Prasoon Saurav
5
Isso não detectará 0s duplicados e identificará o mesmo número como duplicado várias vezes.
Null Set
1
@ Conjunto Nulo: Você pode simplesmente substituir -por ~para o problema zero.
user541686
26
Esta pode ser a resposta na qual o problema está ocorrendo, mas tecnicamente ele usa O(n)espaço oculto - os nbits do sinal. Se a matriz for definida de modo que cada elemento possa conter apenas valores entre 0e n-1, obviamente não funcionará.
caf
2

Para N relativamente pequeno, podemos usar operações div / mod

n.times do |i|
  e = a[i]%n
  a[e] += n
end

n.times do |i| 
  count = a[i]/n
  puts i if count > 1
end

Não C / C ++, mas mesmo assim

http://ideone.com/GRZPI

hoha
fonte
+1 boa solução. Parar de adicionar n a uma entrada após duas vezes acomodará n maior .
Apshir
1

Não é realmente bonito, mas pelo menos é fácil ver as propriedades O (N) e O (1). Basicamente, examinamos a matriz e, para cada número, vemos se a posição correspondente foi sinalizada já vista uma vez (N) ou já vista várias vezes (N + 1). Se estiver sinalizado já visto uma vez, imprimimos e sinalizamos já visto várias vezes. Se não estiver sinalizado, sinalizamos já visto uma vez e movemos o valor original do índice correspondente para a posição atual (sinalizar é uma operação destrutiva).

for (i=0; i<a.length; i++) {
  value = a[i];
  if (value >= N)
    continue;
  if (a[value] == N)  {
    a[value] = N+1; 
    print value;
  } else if (a[value] < N) {
    if (value > i)
      a[i--] = a[value];
    a[value] = N;
  }
}

ou, melhor ainda (mais rápido, apesar do loop duplo):

for (i=0; i<a.length; i++) {
  value = a[i];
  while (value < N) {
    if (a[value] == N)  {
      a[value] = N+1; 
      print value;
      value = N;
    } else if (a[value] < N) {
      newvalue = value > i ? a[value] : N;
      a[value] = N;
      value = newvalue;
    }
  }
}
CAFxX
fonte
+1, funciona bem, mas demorou um pouco para descobrir exatamente por que if (value > i) a[i--] = a[value];funciona: se value <= ijá processamos o valor em a[value]e podemos substituí-lo com segurança. Também não diria que a natureza O (N) é óbvia! Soletrando: O loop principal executa os Ntempos, além de muitas vezes a a[i--] = a[value];linha. Essa linha pode ser executada apenas se a[value] < N, e toda vez que for executada, imediatamente depois, um valor de matriz que ainda não estava Ndefinido N, para que possa ser executado na maioria das Nvezes, para um total de no máximo 2Niterações de loop.
Jrandom_hacker 14/07/12
1

Uma solução em C é:

#include <stdio.h>

int finddup(int *arr,int len)
{
    int i;
    printf("Duplicate Elements ::");
    for(i = 0; i < len; i++)
    {
        if(arr[abs(arr[i])] > 0)
          arr[abs(arr[i])] = -arr[abs(arr[i])];
        else if(arr[abs(arr[i])] == 0)
        {
             arr[abs(arr[i])] = - len ;
        }
        else
          printf("%d ", abs(arr[i]));
    }

}
int main()
{   
    int arr1[]={0,1,1,2,2,0,2,0,0,5};
    finddup(arr1,sizeof(arr1)/sizeof(arr1[0]));
    return 0;
}

É O (n) tempo e O (1) complexidade do espaço.

Anshul garg
fonte
1
A complexidade do espaço é O (N), porque usa N bits de sinal adicionais. O algoritmo deve funcionar sob a suposição de que o tipo de elemento da matriz pode conter apenas números de 0 a N-1.
caf
sim que a verdadeira mas para perguntou algo a sua perfeita como eles queriam a algo para os números de 0 a n-1 somente e também eu verifiquei sua solução sua vai acima O (n) então eu pensei deste
Anshul Garg
1

Vamos supor que apresentamos essa matriz como uma estrutura de dados de gráfico unidirecional - cada número é um vértice e seu índice na matriz aponta para outro vértice, formando uma aresta do gráfico.

Para ainda mais simplicidade, temos os índices 0 a n-1 e o intervalo de números de 0..n-1. por exemplo

   0  1  2  3  4 
 a[3, 2, 4, 3, 1]

0 (3) -> 3 (3) é um ciclo.

Resposta: Basta percorrer a matriz confiando em índices. se a [x] = a [y], então é um ciclo e, portanto, duplicado. Pule para o próximo índice e continue novamente até o final de uma matriz. Complexidade: O (n) tempo e O (1) espaço.

Ivan Voroshilin
fonte
0

Um pequeno código python para demonstrar o método caf acima:

a = [3, 1, 1, 0, 4, 4, 6] 
n = len(a)
for i in range(0,n):
    if a[ a[i] ] != a[i]: a[a[i]], a[i] = a[i], a[a[i]]
for i in range(0,n):
    if a[i] != i: print( a[i] )
vinha
fonte
Observe que a troca pode ter que acontecer mais de uma vez para um único ivalor - observe whilena minha resposta.
Caf
0

O algoritmo pode ser facilmente visto na seguinte função C. A recuperação da matriz original, embora não seja necessária, será possível usando cada módulo de entrada n .

void print_repeats(unsigned a[], unsigned n)
{
    unsigned i, _2n = 2*n;
    for(i = 0; i < n; ++i) if(a[a[i] % n] < _2n) a[a[i] % n] += n;
    for(i = 0; i < n; ++i) if(a[i] >= _2n) printf("%u ", i);
    putchar('\n');
}

Ideone Link para teste.

Apshir
fonte
Receio que isso seja tecnicamente "trapaça", já que trabalhar com números de até 2 * n requer um bit de espaço extra de armazenamento por entrada da matriz em relação ao necessário para armazenar os números originais. Na verdade, você precisa aproximar-se de log2 (3) = 1,58 bits extras por entrada, porque está armazenando números de até 3 * n-1.
Jrandom_hacker 14/07/12
0
static void findrepeat()
{
    int[] arr = new int[7] {0,2,1,0,0,4,4};

    for (int i = 0; i < arr.Length; i++)
    {
        if (i != arr[i])
        {
            if (arr[i] == arr[arr[i]])
            {
                Console.WriteLine(arr[i] + "!!!");
            }

            int t = arr[i];
            arr[i] = arr[arr[i]];
            arr[t] = t;
        }
    }

    for (int j = 0; j < arr.Length; j++)
    {
        Console.Write(arr[j] + " ");
    }
    Console.WriteLine();

    for (int j = 0; j < arr.Length; j++)
    {
        if (j == arr[j])
        {
            arr[j] = 1;
        }
        else
        {
            arr[arr[j]]++;
            arr[j] = 0;
        }
    }

    for (int j = 0; j < arr.Length; j++)
    {
        Console.Write(arr[j] + " ");
    }
    Console.WriteLine();
}
Eli
fonte
0

Criei um aplicativo de playground de exemplo rapidamente para encontrar duplicatas na complexidade de tempo 0 (n) e no espaço extra constante. Por favor, verifique o URL Encontrando Duplicatas

A solução IMP Above funcionou quando uma matriz contém elementos de 0 a n-1, com qualquer um desses números aparecendo inúmeras vezes.

CrazyPro007
fonte
0
private static void printRepeating(int arr[], int size) {
        int i = 0;
        int j = 1;
        while (i < (size - 1)) {
            if (arr[i] == arr[j]) {
                System.out.println(arr[i] + " repeated at index " + j);
                j = size;
            }
            j++;
            if (j >= (size - 1)) {
                i++;
                j = i + 1;
            }
        }

    }
user12704811
fonte
A solução acima alcançará a mesma complexidade temporal de O (n) e espaço constante.
user12704811 13/01
3
Obrigado por este snippet de código, que pode fornecer ajuda limitada a curto prazo. Uma explicação adequada melhoraria bastante seu valor a longo prazo, mostrando por que essa é uma boa solução para o problema e a tornaria mais útil para futuros leitores com outras perguntas semelhantes. Por favor edite sua resposta para adicionar alguma explicação, incluindo as suposições que você fez.
Toby Speight
3
Aliás, a complexidade do tempo parece ser O (n²) aqui - ocultar o loop interno não muda isso.
Toby Speight
-2

Se a matriz não for muito grande, essa solução é mais simples, cria outra matriz do mesmo tamanho para marcação.

1 Crie um bitmap / matriz do mesmo tamanho que sua matriz de entrada

 int check_list[SIZE_OF_INPUT];
 for(n elements in checklist)
     check_list[i]=0;    //initialize to zero

2 digitalize sua matriz de entrada e aumente sua contagem na matriz acima

for(i=0;i<n;i++) // every element in input array
{
  check_list[a[i]]++; //increment its count  
}  

3 Agora digitalize a matriz check_list e imprima a duplicata uma vez ou quantas vezes foram duplicadas

for(i=0;i<n;i++)
{

    if(check_list[i]>1) // appeared as duplicate
    {
        printf(" ",i);  
    }
}

Obviamente, é necessário o dobro do espaço consumido pela solução fornecida acima, mas a eficiência do tempo é O (2n), que é basicamente O (n).

Pensamento profundo
fonte
Isto não é O(1)espaço.
27575 Daniel Barilowski
oops ...! não percebi que ... meu mal.
Deepthought 07/07
@nikhil como é O (1) ?. Minha matriz check_list cresce linearmente à medida que o tamanho da entrada aumenta, então como é O (1)? Em caso afirmativo, quais são as heurísticas que você está usando para chamá-lo O (1).
Deepthought
Para uma determinada entrada, você precisa de espaço constante, não é O (1)? Eu poderia muito bem ser :) errado
nikhil
Minha solução precisa de mais espaço à medida que a entrada aumenta. A eficiência (espaço / tempo) de um algoritmo não é medida para uma entrada específica (nesse caso, a eficiência de tempo de cada algoritmo de pesquisa seria constante, isto é, elemento encontrado no 1º índice em que pesquisamos). a razão pela qual temos o melhor caso, o pior caso e o caso médio.
Deepthought