Salta al contenuto
Note per Studenti Esercizio 20 · duplicati vicini con una mappa

Esercizio 20duplicati vicini con una mappa

In questa pagina 6

Testo (scritto del 10/09/2026, seconda parte, esercizio 1, 5 punti). Sia AA un array di nn interi e sia kk un intero positivo. Si vuole progettare un algoritmo iterativo efficiente hasNearbyDuplicate(A, k) che restituisca true se esistono due indici distinti ii e jj tali che A[i]=A[j]A[i] = A[j] e ∣i−j∣≤k\lvert i - j \rvert \le k, e false altrimenti.

(a) Spiegare a parole l'idea alla base della soluzione proposta (suggerimento: l'utilizzo di una mappa permette di semplificare l'algoritmo e migliorare l'efficienza nel tempo), illustrandone il funzionamento mediante l'esempio A=[4,7,2,5,7,9]A = [4, 7, 2, 5, 7, 9], k=3k = 3. (b) Scrivere lo pseudocodice di hasNearbyDuplicate(A, k), specificando chiaramente input e output. (c) Analizzare la complessità asintotica al caso pessimo e/o al caso medio in funzione di nn e/o kk, giustificando la risposta. (d) Discutere come cambierebbe la complessità utilizzando diverse implementazioni della mappa.


Soluzione banale

Per ogni ii confrontare A[i]A[i] con A[i+1],…,A[i+k]A[i+1], \dots, A[i+k]: Θ(nk)\Theta(nk) confronti, quindi Θ(n2)\Theta(n^2) se kk è vicino a nn. Si può fare meglio ricordando, per ogni valore, dove è stato visto per ultimo.

(a) Idea

Si scorre l'array da sinistra a destra con una mappa (vedi Mappe e dizionariADT mappa (chiavi distinte) con get, put, remove, keySet/values/entrySet; famiglia mappa/mappa ordinata/dizionario (multimappa); applicazioni; implementazioni semplici ma poco efficienti (lista, array indicizzato dalle chiavi); dizionario realizzato con una mappa di liste.Mappe e dizionari →) che associa a ogni valore già incontrato l'indice della sua ultima occorrenza. All'indice ii:

  • se A[i]A[i] non è nella mappa, lo si inserisce con valore ii;
  • se A[i]A[i] è nella mappa con ultimo indice jj: se i−j≤ki - j \le k si restituisce true; altrimenti si aggiorna l'indice a ii (le occorrenze future saranno più vicine a ii che a jj, quindi jj non serve più).

Basta guardare l'ultima occorrenza: se esiste una coppia vicina con valore xx, in particolare lo sono due occorrenze consecutive di xx.

Esempio A=[4,7,2,5,7,9]A = [4, 7, 2, 5, 7, 9] (indici da 00), k=3k = 3. Le prime quattro iterazioni inseriscono 4↦04 \mapsto 0, 7↦17 \mapsto 1, 2↦22 \mapsto 2, 5↦35 \mapsto 3. All'indice 44 il valore 77 è già nella mappa con indice 11 e 4−1=3≤34 - 1 = 3 \le 3: l'algoritmo restituisce true. Con k=2k = 2 la stessa istanza restituirebbe false: all'indice 44, 4−1=3>24 - 1 = 3 > 2, si aggiorna 7↦47 \mapsto 4 e l'indice 55 (99) non è nella mappa.

(b) Pseudocodice

Algoritmo hasNearbyDuplicate(A, k)
Input: array A[0..n-1] di interi, intero k >= 1
Output: true se esistono i != j con A[i] = A[j] e |i - j| <= k, false altrimenti
M <- mappa vuota                                 (chiave: valore di A; valore: ultimo indice)
for i <- 0 to n-1 do
    j <- M.get(A[i])                             (null se A[i] non è in M)
    if j != null AND i - j <= k then return true
    M.put(A[i], i)                               (inserisce o aggiorna con l'indice più recente)
return false

Correttezza. Invariante: all'inizio dell'iterazione ii, per ogni valore xx comparso in A[0..i−1]A[0..i-1], M[x]M[x] è l'indice dell'ultima occorrenza di xx in A[0..i−1]A[0..i-1], e nessuna coppia di occorrenze vicine esiste in A[0..i−1]A[0..i-1]. Se esiste una coppia vicina (j,i)(j, i) con A[j]=A[i]A[j] = A[i] e j<ij < i minima, l'occorrenza di A[i]A[i] immediatamente precedente ii è anche vicina (è a distanza ≤i−j≤k\le i - j \le k) e M[A[i]]M[A[i]] la contiene: viene trovata.

(c) Complessità

Il ciclo ha nn iterazioni, ciascuna con un get e un put sulla mappa. Con una tabella hash (vedi Tabelle hashTabella hash per implementare una mappa: funzione hash = hash code + compression function, bucket array, separate chaining; hash code per numeri e stringhe (polynomial, cyclic shift), division e MAD; load factor, complessità al caso pessimo Theta(n) e medio O(1+lambda), rehashing; esempio svolto con inserimenti e collisioni.Tabelle hash →) ogni operazione costa Θ(1)\Theta(1) in media (hashing uniforme, λ\lambda costante), quindi:

  • caso medio: Θ(n)\Theta(n);
  • caso pessimo: ogni operazione può costare O(m)O(m) con m≤nm \le n chiavi presenti (collisioni), quindi O(n2)O(n^2).

Lo spazio è O(n)O(n) (al più un'entry per valore distinto). Il tempo non dipende da kk.

(d) Altre implementazioni della mappa

Mappa get/put Tempo totale
tabella hash (concatenamento) Θ(1)\Theta(1) medio, O(n)O(n) pessimo Θ(n)\Theta(n) medio, O(n2)O(n^2) pessimo
albero binario di ricerca bilanciato o (2,4) Θ(log⁡m)\Theta(\log m) garantito, m≤nm \le n O(nlog⁡n)O(n \log n) anche al caso pessimo
albero binario di ricerca non bilanciato Θ(h)\Theta(h), fino a Θ(m)\Theta(m) O(n2)O(n^2) al caso pessimo
lista non ordinata Θ(m)\Theta(m) O(n2)O(n^2)

L'albero bilanciato dà una garanzia deterministica O(nlog⁡n)O(n \log n) (utile se non ci si fida delle collisioni); la hash è più veloce in pratica. Un'alternativa che non usa la mappa, per kk piccolo, è mantenere una finestra degli ultimi kk elementi. (Algoritmo verificato su 3000 coppie casuali (A,k)(A, k) confrontandolo con il controllo di tutte le coppie.)

Errori comuni

  • Controllare solo l'uguaglianza dei valori senza confrontare gli indici: restituirebbe true per qualunque duplicato.
  • Non aggiornare l'indice quando la distanza supera kk: poi le occorrenze successive vengono confrontate con un indice troppo vecchio. Esempio: A=[5,8,9,5,5]A = [5, 8, 9, 5, 5] con k=1k = 1 deve dare true (gli ultimi due 55 sono adiacenti); senza aggiornare, il 55 in posizione 33 non sostituisce quello in posizione 00 e in posizione 44 si confronta 4−0=4>14 - 0 = 4 > 1: risposta sbagliata false.
  • Dichiarare Θ(n)\Theta(n) al caso pessimo con la hash: il caso pessimo è quadratico, lineare in media.
  • Usare una struttura ordinata (albero) senza dire che le chiavi, interi, sono ordinabili.

Versione ripasso

Testo. hasNearbyDuplicate(A, k): true se esistono i≠ji \ne j con A[i]=A[j]A[i] = A[j] e ∣i−j∣≤k\lvert i - j \rvert \le k. Idea con esempio [4,7,2,5,7,9][4,7,2,5,7,9], k=3k = 3, pseudocodice, complessità, altre mappe.

Teoria collegata