Um elemento que difere em duas matrizes. Como encontrá-lo eficientemente?


22

Estou me preparando para uma entrevista de codificação e realmente não consigo descobrir a maneira mais eficiente de resolver esse problema.

Digamos que temos duas matrizes que consistem em números que não são classificados. A matriz 2 contém um número que a matriz 1 não. Ambas as matrizes têm números localizados aleatoriamente, não necessariamente na mesma ordem ou nos mesmos índices. Por exemplo:

Matriz 1 [78,11, 143, 84, 77, 1, 26, 35 .... n]

Matriz 2 [11,84, 35, 25, 77, 78, 26, 143 ... 21 ... n + 1]

Qual é o algoritmo mais rápido para encontrar o número que difere? Qual é o seu tempo de execução? Neste exemplo, o número que estaríamos procurando é 21.

Minha ideia era percorrer a Matriz 1 e excluir esse valor da matriz 2. Iterar até terminar. Deve ser em torno de tempo de execução, certo?O(nregistron)


@Jandvorak Obrigado a vocês pelas respostas. Eu estava acordado até tarde e caiu no sono depois de postar isso. A matriz não está classificada e todos os itens aparecem em índices aleatórios nas duas matrizes.
Konstantino Sparakis

@ KonstantinoSparakis: esse esclarecimento invalida as respostas que assumem que ambas as matrizes contêm os elementos nas mesmas posições.
Mario Cervera

A postagem cruzada é desaprovada pelo softwareengineering.stackexchange.com/users/256931/…
paparazzo

A @Paparazzi estava simplesmente procurando uma solução que eu li na engenharia de meta software, onde encontrar uma solução, mas na época eu não sabia sobre o fórum da CS. Eu avisei os mods, para limpá-lo.
21716 Konstantino Sparakis

@Paparazzi, existe uma meta post fazendo backup disso? Pessoalmente, não vejo nenhuma maneira de implementar bem essa política.
djechlin

Respostas:


30

Vejo quatro maneiras principais de resolver esse problema, com diferentes tempos de execução:

  • Solução O ( n 2 ) : essa seria a solução que você propõe. Observe que, como as matrizes não são classificadas, a exclusão leva tempo linear. Você realiza n exclusões; portanto, esse algoritmo leva tempo quadrático.O(n2)n

  • solução: classifique as matrizes antecipadamente; em seguida, execute uma pesquisa linear para identificar o elemento distinto. Nesta solução, o tempo de execução é dominado pela operação de classificação, portanto, o O ( nO(neuogn) limite superior.O(neuogn)

Ao identificar uma solução para um problema, você deve sempre se perguntar: posso fazer melhor? Nesse caso, você pode fazer um uso inteligente das estruturas de dados. Observe que tudo que você precisa fazer é iterar uma matriz e executar pesquisas repetidas na outra matriz. Que estrutura de dados permite que você faça pesquisas em tempo constante (esperado)? Você adivinhou certo: uma tabela de hash .

  • solução (esperada): itere a primeira matriz e armazena os elementos em uma tabela de hash; em seguida, execute uma varredura linear na segunda matriz, procurando cada elemento na tabela de hash. Retorne o elemento que não foi encontrado na tabela de hash. Essa solução de tempo linear funciona para qualquer tipo de elemento que você possa passar para uma função hash (por exemplo, funcionaria de maneira semelhante para matrizes de strings).O(n)

Se você deseja garantias de limite superior e as matrizes são estritamente compostas por números inteiros, a melhor solução é, provavelmente, a sugerida por Tobi Alafin (embora essa solução não forneça o índice do elemento que difere na segunda matriz) :

  • Solução O ( n ) (garantida): some os elementos da primeira matriz. Em seguida, resuma os elementos da segunda matriz. Finalmente, execute a subtração. Observe que esta solução pode realmente ser generalizada para qualquer tipo de dados cujos valores possam ser representados como cadeias de bits de comprimento fixo, graças aooperador XOR bitabit. Isso é completamente explicado naresposta deIlmari Karonen. O(n)

Finalmente, outra possibilidade (sob a mesma suposição de matrizes inteiras) seria usar um algoritmo de ordenação em tempo linear, como ordenação por contagem. Isso reduziria o tempo de execução da solução baseada em classificação de a O ( n ) .O(neuogn)O(n)


4
a soma não é linear se os números forem grandes o suficiente.
Sarge Borsch

9
Uma coisa interessante sobre o algoritmo de soma é que ele funciona com qualquer grupo abeliano, não apenas com números inteiros (principalmente uint64: cc @sarge).
John Dvorak

6
@ Abdul, o problema é que, se seus números inteiros forem muito grandes, você não poderá mais fingir que eles usam para adicionar. Acredito que a complexidade cresça para O ( n ln n ) se você responder por isso. O uso do XOR em vez da adição comum resolve isso, embora ainda permita um número arbitrariamente grande na entrada. O(n)O(nemn)
John Dvorak

2
@JanDvorak Não, não. Você está assumindo que a operação definida no grupo abeliano leva tempo constante. Isso não pode ser assumido.
UTF-8

2
@ UTF-8 Não estou assumindo isso. Mas o faz em grupos finitos (uint64) e a adição digitada no local (adição em ) é linear no tamanho do operando fora do lugar. Portanto, calcular a soma nesses grupos é de tempo linear no tamanho total dos operandos. Znd
John Dvorak

16

O diferença-de-somas solução proposta por Tobi e Mario pode na verdade ser generalizada para qualquer outro tipo de dados para os quais podemos definir um (constante de tempo) operação binária que é:Θ(n)

  • total , de modo que, para quaisquer valores e b , a b seja definido e do mesmo tipo (ou pelo menos de algum supertipo apropriado, para o qual o operador ainda esteja definido);umabumab
  • associativo , de modo que ;uma(bc)=(umab)c
  • comutativo , tal que ; eumab=buma
  • cancelável , de modo que exista um operador inverso que satisfaça ( a b ) b = a . Tecnicamente, essa operação inversa nem precisa necessariamente ser de tempo constante, desde que "subtraindo" duas somas de n elementos cada uma não demore mais que O ( n ) tempo.(umab)b=umanO(n)

(Se o tipo só pode receber um número finito de valores distintos, essas propriedades são suficientes para transformá-lo em um grupo abeliano ; mesmo se não, será pelo menos um semigrupo comutativo de cancelamento .)

Usando essa operação , podemos definir a "soma" de uma matriz a = ( a 1 , a 2 , , a n ) como ( uma=(uma1,uma2,...,uman) Dada outra matriz b = ( b 1 , b 2 , , b n , b n + 1 ) contendo todos os elementos de a mais um elemento extra x , temos, portanto, (

(uma)=uma1uma2uman.
b=(b1,b2,...,bn,bn+1)umax , e assim podemos encontrar esse elemento extra computando: x = ( (b)=(uma)x
x=(b)(uma).

Por exemplo, se os valores nas matrizes são números inteiros, em seguida, adição inteiro (ou adição modular para tipos inteiros finito de comprimento) pode ser usado como o operador , com subtracção como a operação inversa . Como alternativa, para qualquer tipo de dados cujos valores possam ser representados como cadeias de bits de comprimento fixo, podemos usar o XOR bit a bit como e .

De maneira mais geral, podemos aplicar o método XOR bit a bit em seqüências de comprimento variável, preenchendo-as com o mesmo comprimento necessário, desde que tenhamos alguma maneira de remover reversivelmente o preenchimento no final.

Em alguns casos, isso é trivial. Por exemplo, cadeias de bytes terminadas nulas no estilo C codificam implicitamente seu próprio comprimento, portanto, aplicar esse método a elas é trivial: ao XORing duas cadeias, preencha a mais curta com bytes nulos para fazer com que seu comprimento corresponda e apare quaisquer nulos à direita extra de o resultado final. Observe que as cadeias intermediárias de soma XOR podem conter bytes nulos; portanto, você precisará armazenar explicitamente seu comprimento (mas precisará apenas de um ou dois deles no máximo).

10 00 01232.bytes, poderíamos codificar o comprimento de cada string como um número inteiro de 32 bits e anexá-lo à string. Ou podemos até codificar comprimentos arbitrários de cadeias usando algum código de prefixo e anexá-los às cadeias. Também existem outras codificações possíveis.

Θ(n)

A única parte potencialmente complicada é que, para que o cancelamento funcione, precisamos escolher uma representação de cadeia de bits canônica exclusiva para cada valor, o que pode ser difícil (de fato, potencialmente até indecidível computacionalmente) se os valores de entrada nas duas matrizes puderem ser dados em diferentes representações equivalentes. Esta não é uma fraqueza específica deste método, no entanto; qualquer outro método para resolver esse problema também poderá falhar se for permitido que a entrada contenha valores cuja equivalência seja indecidível.


Uau, muito interessante. Obrigado @IlmariKaronen
Konstantino Sparakis

14

Eu postaria isso como um comentário na resposta de Tobi, mas ainda não tenho reputação.

Como alternativa ao cálculo da soma de cada lista (especialmente se forem listas grandes ou contiverem números muito grandes que podem sobrecarregar seu tipo de dados quando somados), você pode usar xor.

Apenas calcule a soma xor (ou seja, x [0] ^ x [1] ^ x [2] ... x [n]) de cada lista e, em seguida, xe esses dois valores. Isso fornecerá o valor do item estranho (mas não o índice).

Ainda é O (n) e evita problemas com o estouro.


3
Eu também usaria o XOR, porque parece um pouco mais limpo, mas, para ser justo, o estouro não é realmente um problema, desde que a linguagem que você está implementando no suporte ao estouro envolva.
Martin Ender

14

Elemento = Soma (matriz2) - Soma (matriz1)

Eu sinceramente duvido que este é o algoritmo mais ideal. Mas é outra maneira de resolver o problema e é a maneira mais simples de resolvê-lo. Espero que ajude.

Se o número de elementos adicionados for mais de um, isso não funcionará.

Minha resposta tem a mesma complexidade de tempo de execução para o melhor, o pior e o médio,

EDIT
Após pensar um pouco, acho que minha resposta é sua solução.

nn11=n12=n+11=n

2n121=1

2n-1+1=2n

Θ(n)

EDIT:
Devido a alguns problemas com os tipos de dados, uma soma XOR conforme sugerida por reffu será mais adequada.


Observe que esse método pode não fornecer uma resposta precisa se seus valores forem flutuantes, pois a soma dos números pode introduzir erros de arredondamento. Ele funcionará para valores inteiros, no entanto, desde que a) seu tipo inteiro tenha um comportamento de contorno bem definido em caso de transbordamento, ou b) você armazene as somas em variáveis ​​de um tipo amplo o suficiente para que não possam transbordar.
Ilmari Karonen

A classe "BigNum" do Ruby provavelmente pode lidar com isso.
precisa

Absolutamente não funciona se sua matriz contiver, por exemplo, seqüências de caracteres ou praticamente qualquer coisa que não possa ser adicionada de forma significativa.
gnasher729

Sim, eu percebi. Que tal usar o 'XOR'? Funcionará para carros alegóricos?
precisa

Sim e também ponteiros e, em geral, qualquer coisa que consista em um número fixo de bits. Muitos idiomas não suportam isso, mas isso não é um problema fundamental. A adição / subtração modular funcionará nos mesmos casos.
Harold

1

Supondo que a matriz 2 foi criada tomando a matriz 1 e inserindo um elemento em uma posição aleatória, ou a matriz 1 foi criada tomando a matriz 2 e excluindo um elemento aleatório.

Se é garantido que todos os elementos da matriz sejam distintos, o tempo é O (ln n). Você compara os elementos no local n / 2. Se forem iguais, o elemento extra será de n / 2 + 1 até o final da matriz, caso contrário, será de 0 a n / 2. E assim por diante.

Se não for garantido que os elementos da matriz sejam distintos: você pode ter n vezes o número 1 na matriz 1 e o número 2 inserido em qualquer lugar da matriz 2. Nesse caso, você não pode saber onde está o número 2 sem olhar para todos. elementos da matriz. Portanto O (n).

PS. Como os requisitos foram alterados, verifique sua biblioteca para saber o que está disponível. No macOS / iOS, você cria um NSCountedSet, adiciona todos os números da matriz 2, remove todos os números da matriz 1, e o que resta é tudo o que está na matriz 2, mas não na matriz 1, sem confiar na alegação de que há um adicional item.


Esta resposta foi imediata, mas a pergunta foi editada com um novo requisito que invalida sua suposição.
Mario Cervera

Sua nova resposta parece certa. Qual é a complexidade do tempo.
precisa

Bem, primeiro qual é o tempo necessário para escrever o código. É trivial. NSCountedSet usa hash, portanto, a complexidade do tempo é "geralmente linear".
gnasher729

-1

var mais curto, mais longo;

Converta o mais curto em um mapa para referência rápida e o loop pelo mais longo até que o valor atual não esteja no mapa.

Algo assim em javascript:

if (arr1.length> arr2.length) {menor = arr2; mais longo = arr1; } mais {shortest = arr1; mais longo = arr2; }

var map = shortest.reduce (função (obj, valor) {obj [valor] = verdadeiro; retorna obj;}, {});

var diferença = longest.find (função (valor) {return !!! mapa [valor];});


Códigos sem explicação não contam como uma boa resposta aqui. Também por que você usaria !!! ?
Mal

-1

Solução O (N) em complexidade de tempo O (1) em termos de complexidade de espaço

Declaração do problema: Supondo que a matriz2 contenha todos os elementos da matriz1 mais um outro elemento não presente na matriz1.

A solução é: Usamos xor para encontrar o elemento que não está presente no array1, portanto, as etapas são: 1. Inicie no array1 e execute xor de todos os elementos e os armazene em uma variável. 2. Pegue o array2 e execute o xor de todos os elementos com a variável que armazena o xor do array1. 3. Depois de fazer a operação, nossa variável conterá o elemento que está presente apenas no array2. O algoritmo acima funciona devido às seguintes propriedades de xor "a xor a = 0" "a xor 0 = a" Espero que isso resolva seu problema. Também as soluções sugeridas acima também são boas

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.