Un elemento che differisce in due matrici. Come trovarlo in modo efficiente?


22

Mi sto preparando per un colloquio di programmazione e non riesco davvero a capire il modo più efficace per risolvere questo problema.

Supponiamo di avere due array costituiti da numeri non ordinati. L'array 2 contiene un numero che l'array 1 non contiene. Entrambi gli array hanno numeri casuali, non necessariamente nello stesso ordine o negli stessi indici. Per esempio:

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

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

Qual è l'algoritmo più veloce per trovare il numero che differisce? Qual è il suo tempo di esecuzione? In questo esempio, il numero che cercheremo è 21.

La mia idea era di passare attraverso l'array 1 ed eliminare quel valore dall'array 2. Iterare fino al termine. Dovrebbe essere circa tempo di esecuzione, giusto?O(nlogn)


@Jandvorak Grazie ragazzi per le risposte. Mi sono alzato tardi e mi è capitato di addormentarmi dopo aver pubblicato questo. L'array non è ordinato e tutti gli elementi vengono visualizzati in indici casuali in entrambi gli array.
Konstantino Sparakis,

@KonstantinoSparakis: questo chiarimento invalida le risposte che presuppongono che entrambi gli array contengano gli elementi nelle stesse posizioni.
Mario Cervera,


@Paparazzi Stavo semplicemente cercando una soluzione che ho letto nell'ingegneria del meta-software: dove andare per ottenere una soluzione, ma al momento non sapevo del forum CS. Ho notificato le mod per ripulirlo.
Konstantino Sparakis,

@Paparazzi c'è un meta post che lo supporta? Personalmente non vedo alcun modo per attuare bene questa politica.
Djechlin,

Risposte:


30

Vedo quattro modi principali per risolvere questo problema, con tempi di esecuzione diversi:

  • soluzione: questa sarebbe la soluzione che proponi. Si noti che, poiché le matrici non sono ordinate, la cancellazione richiede un tempo lineare. Esegui n eliminazioni; pertanto, questo algoritmo richiede tempo quadratico.O(n2)n

  • soluzione: ordinare in anticipo gli array; quindi, eseguire una ricerca lineare per identificare l'elemento distinto. In questa soluzione, il tempo di esecuzione è dominato dall'operazione di ordinamento, quindi O ( nO(nlogn) limite superiore.O(nlogn)

Quando identifichi una soluzione a un problema, dovresti sempre chiederti: posso fare di meglio? In questo caso, puoi, facendo un uso intelligente delle strutture di dati. Nota che tutto ciò che devi fare è iterare un array ed eseguire ricerche ripetute nell'altro array. Quale struttura di dati ti consente di effettuare ricerche in tempo (previsto) costante? Hai indovinato bene: una tabella di hash .

  • soluzione (prevista): itera il primo array e archivia gli elementi in una tabella hash; quindi, eseguire una scansione lineare nel secondo array, cercando ogni elemento nella tabella hash. Restituisce l'elemento che non si trova nella tabella hash. Questa soluzione a tempo lineare funziona per qualsiasi tipo di elemento che è possibile passare a una funzione hash (ad esempio, funzionerebbe in modo simile per le matrici di stringhe).O(n)

Se desideri garanzie con limite superiore e gli array sono rigorosamente composti da numeri interi, probabilmente la soluzione migliore è quella suggerita da Tobi Alafin (anche se questa soluzione non ti darà l'indice dell'elemento che differisce nel secondo array) :

  • soluzione (garantita): riassume gli elementi del primo array. Quindi, riassumi gli elementi del secondo array. Infine, esegui la sottostrazione. Si noti che questa soluzione può effettivamente essere generalizzata a qualsiasi tipo di dati i cui valori possono essere rappresentati come stringhe di bit a lunghezza fissa, grazieall'operatore XOR bitabit. Questo è completamente spiegato nellarisposta diIlmari Karonen. O(n)

Infine, un'altra possibilità (sotto la stessa ipotesi di array di numeri interi) sarebbe quella di utilizzare un algortihm di ordinamento a tempo lineare come il conteggio dell'ordinamento. Ciò ridurrebbe il tempo di esecuzione della soluzione basata sull'ordinamento da a O ( n ) .O(nlogn)O(n)


4
la somma non è lineare se i numeri diventano abbastanza grandi, però.
Sarge Borsch,

9
Una cosa bella dell'algoritmo di somma è che funziona con qualsiasi gruppo abeliano, non solo con numeri interi (In particolare uint64,; cc @sarge).
John Dvorak,

6
@Abdul il fatto è che se i tuoi numeri interi sono molto grandi, non puoi più far finta che aggiungano . Credo che la complessità cresce fino a O ( n ln n ) se si conto per questo. L'uso di XOR invece dell'aggiunta ordinaria risolve ciò, tuttavia, pur consentendo un numero arbitrariamente elevato di input. O(n)O(nlnn)
John Dvorak,

2
@JanDvorak No, non lo è. Stai assumendo che l'operazione definita sul gruppo abeliano richieda un tempo costante. Non si può solo supporre.
UTF-8

2
@ UTF-8 Non lo presumo. Ma lo fa in gruppi finiti (uint64), e l'aggiunta sul posto in termini di cifre (aggiunta in ) ha una dimensione lineare dell'operando fuori posto. Pertanto, calcolare la somma in tali gruppi è un tempo lineare nella dimensione totale degli operandi. Znd
John Dvorak,

16

Il differenza di somme soluzione proposta da Tobi e Mario può infatti essere generalizzato a qualsiasi altro tipo di dati per cui possiamo definire una (costante di tempo) un'operazione binaria che è:Θ(n)

  • totale , tale che per tutti i valori e b , un b è definito e dello stesso tipo (o almeno di alcune appropriata supertype di esso, per cui l'operatore è ancora definita);un'Bun'B
  • associativo , tale che ;un'(Bc)=(un'B)c
  • commutativo , tale che ; eun'B=Bun'
  • cancellativo , tale che esiste un operatore inverso che soddisfa ( a b ) b = a . Tecnicamente, questa operazione inversa non deve necessariamente essere a tempo costante, fintanto che "sottrarre" due somme di n elementi ciascuna non richiede più di O ( n ) tempo.(un'B)B=un'nO(n)

(Se il tipo può assumere solo un numero finito di valori distinti, queste proprietà sono sufficienti per trasformarlo in un gruppo abeliano ; anche se non lo sarà, sarà almeno un semigruppo di annullamento commutativo .)

Usando tale operazione , possiamo definire la "somma" di un array a = ( a 1 , a 2 , ... , a n ) come ( un'=(un'1,un'2,...,un'n) Dato un altro array b = ( b 1 , b 2 , , b n , b n + 1 ) contenente tutti gli elementi di un più un elemento in più x , abbiamo quindi (

(un')=un'1un'2un'n.
B=(B1,B2,...,Bn,Bn+1)un'X , e così possiamo trovare questo elemento in più calcolando: x = ( (B)=(un')X
X=(B)(un').

Ad esempio, se i valori nelle matrici sono numeri interi, l'aggiunta di numeri interi (o l'aggiunta modulare per tipi di numeri interi di lunghezza finita) può essere utilizzata come operatore , con la sottrazione come operazione inversa . In alternativa, per qualsiasi tipo di dati i cui valori possono essere rappresentati come stringhe di bit a lunghezza fissa, possiamo usare XOR bit a bit sia come che .

Più in generale, possiamo persino applicare il metodo XOR bit a bit a stringhe di lunghezza variabile, imbottendole fino alla stessa lunghezza necessaria, purché abbiamo un modo per rimuovere in modo reversibile l'imbottitura alla fine.

In alcuni casi, questo è banale. Ad esempio, le stringhe di byte con terminazione null in stile C codificano implicitamente la loro stessa lunghezza, quindi applicare questo metodo per loro è banale: quando XORing due stringhe, riempire quella più corta con byte null per far corrispondere la loro lunghezza e tagliare eventuali null finali il risultato finale. Tuttavia, le stringhe di somma XOR intermedie possono contenere byte null, quindi dovrai memorizzarne esplicitamente la lunghezza (ma ne avrai bisogno solo uno o due).

1001232byte di lunghezza, potremmo codificare la lunghezza di ogni stringa come numero intero a 32 bit e anteporla alla stringa. Oppure potremmo persino codificare lunghezze di stringa arbitrarie usando un po 'di prefisso e anteporre quelle alle stringhe. Esistono anche altre possibili codifiche.

Θ(n)

L'unica parte potenzialmente complicata è che, affinché la cancellazione funzioni, dobbiamo scegliere un'unica rappresentazione canonica di bitstring per ciascun valore, che potrebbe essere difficile (anzi, potenzialmente persino indecidibile dal punto di vista computazionale) se i valori di input nei due array possono essere dati in diverse rappresentazioni equivalenti. Questa non è una debolezza specifica di questo metodo, tuttavia; qualsiasi altro metodo per risolvere questo problema può anche fallire se l'input può contenere valori la cui equivalenza è indecidibile.


Wow, prendilo molto interessante su questo. Grazie @IlmariKaronen
Konstantino Sparakis il

14

Pubblicherei questo come commento sulla risposta di Tobi, ma non ho ancora la reputazione.

In alternativa al calcolo della somma di ciascun elenco (soprattutto se si tratta di elenchi di grandi dimensioni o contengono numeri molto grandi che potrebbero sommare il tipo di dati quando sommati) è possibile utilizzare invece xor.

Calcola solo la somma xor (ovvero x [0] ^ x [1] ^ x [2] ... x [n]) di ciascun elenco e quindi xo quei due valori. Questo ti darà il valore dell'elemento estraneo (ma non l'indice).

Questo è ancora O (n) ed evita qualsiasi problema di overflow.


3
Userei anche XOR, perché sembra un po 'più ordinato, ma per essere onesti, l'overflow non è davvero un problema fintanto che il linguaggio che stai implementando in questo supporta l'overflow avvolgendolo.
Martin Ender,

14

Element = Sum (Array2) - Sum (Array1)

Io sinceramente dubbio questo è l'algoritmo più ottimale. Ma è un altro modo per risolvere il problema ed è il modo più semplice per risolverlo. Spero che sia d'aiuto.

Se il numero di elementi aggiunti è più di uno, questo non funzionerà.

La mia risposta ha la stessa complessità del tempo di esecuzione per il caso migliore, peggiore e medio,

MODIFICA
Dopo aver riflettuto un po ', penso che la mia risposta sia la tua soluzione.

nn-11=n-12=n+1-1=n

2n-12-1=1

2n-1+1=2n

Θ(n)

EDIT:
A causa di alcuni problemi con i tipi di dati, una somma XOR come suggerito da reffu sarà più adatta.


Si noti che questo metodo potrebbe non fornire una risposta accurata se i valori sono float, poiché la somma dei numeri potrebbe introdurre errori di arrotondamento. Funzionerà con valori interi, tuttavia, a condizione che a) il tuo tipo intero abbia un comportamento avvolgente ben definito in caso di overflow, oppure b) memorizzi le somme in variabili di un tipo sufficientemente ampio da non poter traboccare.
Ilmari Karonen,

La classe "BigNum" di Ruby può probabilmente gestirlo.
Tobi Alafin,

Non funziona assolutamente se l'array contiene ad esempio stringhe o qualsiasi cosa che non possa essere aggiunta in modo significativo.
gnasher729,

Sì, ho capito. Che ne dici di usare 'XOR'? Funzionerà per i galleggianti?
Tobi Alafin,

Sì e anche puntatori e in generale tutto ciò che consiste in un numero fisso bit. Molte lingue non lo supportano, ma questo non è un problema fondamentale. L'aggiunta / sottrazione modulari funzionerà negli stessi casi.
Harold

1

Supponendo che l'array 2 sia stato creato prendendo l'array 1 e inserendo un elemento in una posizione casuale, o l'array 1 sia stato creato prendendo l'array 2 e cancellando un elemento casuale.

Se si garantisce che tutti gli elementi dell'array sono distinti, il tempo è O (ln n). Si confrontano gli elementi nella posizione n / 2. Se sono uguali, l'elemento aggiuntivo va da n / 2 + 1 alla fine dell'array, altrimenti va da 0 a n / 2. E così via.

Se gli elementi dell'array non sono garantiti per essere distinti: potresti avere n volte il numero 1 nell'array 1 e il numero 2 inserito ovunque nell'array 2. In tal caso non puoi sapere dove si trova il numero 2 senza guardare tutto elementi dell'array. Pertanto O (n).

PS. Poiché i requisiti sono cambiati, controlla la tua libreria per ciò che è disponibile. Su macOS / iOS, si crea un NSCountedSet, si aggiungono tutti i numeri dall'array 2, si rimuovono tutti i numeri dall'array 1 e ciò che resta è tutto ciò che è nell'array 2 ma non nell'array 1, senza fare affidamento sull'affermazione che esiste un ulteriore articolo.


Questa risposta è stata esatta, ma la domanda è stata modificata con un nuovo requisito che invalida la tua ipotesi.
Mario Cervera,

La tua nuova risposta sembra giusta. Qual è la complessità del tempo.
Tobi Alafin,

Bene, innanzitutto qual è il tempo necessario per scrivere il codice. È banale. NSCountedSet utilizza l'hash, quindi la complessità temporale è "solitamente lineare".
gnasher729,

-1

var più corto, più lungo;

Converti il ​​più breve in una mappa per un rapido riferimento e il ciclo più lungo fino a quando il valore corrente non è nella mappa.

Qualcosa del genere in javascript:

if (arr1.length> arr2.length) {più breve = arr2; più lungo = arr1; } else {shortest = arr1; più lungo = arr2; }

var map = shortest.reduce (funzione (obj, value) {obj [value] = true; return obj;}, {});

var differenza = longest.find (funzione (valore) {return !!! map [valore];});


I codici senza spiegazione non contano come una buona risposta qui. Anche perché dovresti usare !!! ?
Evil

-1

Soluzione O (N) nella complessità temporale O (1) in termini di complessità spaziale

Dichiarazione del problema: supponendo che array2 contenga tutti gli elementi dell'array1 più un altro elemento non presente nell'array1.

La soluzione è: usiamo xor per trovare l'elemento che non è presente in array1, quindi i passaggi sono: 1. Inizia da array1 ed esegui xor di tutti gli elementi e memorizzali in una variabile. 2. Prendi l'array2 ed esegui il xor di tutti gli elementi con la variabile che memorizza il xor di array1. 3. Dopo aver eseguito l'operazione, la nostra variabile conterrà l'elemento presente solo in array2. L'algoritmo sopra funziona a causa della seguente proprietà di xor "a xor a = 0" "a xor 0 = a" Spero che questo risolva il tuo problema. Anche le soluzioni sopra suggerite vanno bene

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.