Questa sezione contiene problemi comuni sulle Hash Tables. Ogni problema include la strategia risolutiva e l’implementazione completa.
Item In Common
Problema: Scrivere una funzione che verifica se due array hanno almeno un elemento in comune.
Strategia - Hash Table per lookup veloce:
- Crea una hash table (oggetto o Map) con gli elementi del primo array
- Itera attraverso il secondo array e verifica se ogni elemento esiste nella hash table
- Se trovi un elemento comune, ritorna
trueimmediatamente - Se completi l’iterazione senza trovare elementi comuni, ritorna
false
Esempio:
itemInCommon([1, 3, 5], [2, 4, 5])→true(entrambi contengono5)itemInCommon([1, 3, 5], [2, 4, 6])→false(nessun elemento comune)
Complessità: O(n) tempo, O(n) spazio (molto migliore di O(n²) con nested loops)
Mostra implementazione con Map
function itemInCommon(array1, array2) { // Crea una Map per memorizzare gli elementi del primo array const myMap = new Map();
// Itera attraverso il primo array e aggiungi ogni elemento alla Map for (let i of array1) { // Imposta il valore a true (il valore non è importante, solo la chiave) myMap.set(i, true); }
// Itera attraverso il secondo array for (let j of array2) { // Se l'elemento esiste nella Map, abbiamo trovato un elemento comune if (myMap.has(j)) { return true; } }
// Se non abbiamo trovato elementi comuni, ritorna false return false;}Mostra implementazione con Object
function itemInCommon(arr1, arr2) { // Crea un oggetto per memorizzare gli elementi del primo array let obj = {};
// Itera attraverso il primo array e aggiungi ogni elemento come chiave for (let i = 0; i < arr1.length; i++) { // Imposta il valore a true (il valore non è importante, solo la chiave) obj[arr1[i]] = true; }
// Itera attraverso il secondo array for (let j = 0; j < arr2.length; j++) { // Se l'elemento esiste come chiave nell'oggetto, abbiamo trovato un elemento comune if (obj[arr2[j]]) { return true; } }
// Se non abbiamo trovato elementi comuni, ritorna false return false;}Nota: Questo problema dimostra come le hash table possono ridurre la complessità da O(n²) a O(n) trasformando nested loops in due loop sequenziali.
Find Duplicates
Problema: Trovare tutti i numeri che appaiono più di una volta in un array.
Strategia - Conteggio con Hash Table:
- Crea una hash table per contare le occorrenze di ogni numero
- Itera attraverso l’array e incrementa il conteggio per ogni numero
- Itera attraverso la hash table e aggiungi alla lista dei duplicati i numeri con conteggio > 1
Esempio:
findDuplicates([1, 2, 3, 4, 4, 5, 6, 6])→[4, 6]findDuplicates([1, 2, 3])→[]
Complessità: O(n) tempo, O(n) spazio
Mostra implementazione con Map
function findDuplicates(nums) { // Crea una Map per contare le occorrenze di ogni numero const numCounts = new Map();
// Itera attraverso l'array e conta le occorrenze for (let num of nums) { // Se il numero esiste già, incrementa il conteggio // Altrimenti, inizializza a 0 e aggiungi 1 numCounts.set(num, (numCounts.get(num) || 0) + 1); }
// Array per memorizzare i duplicati const duplicates = [];
// Itera attraverso la Map e trova i numeri con conteggio > 1 for (let [key, value] of numCounts.entries()) { // Se il conteggio è maggiore di 1, è un duplicato if (value > 1) { duplicates.push(key); } }
// Ritorna l'array dei duplicati return duplicates;}Mostra implementazione con Object
function findDuplicates(nums) { // Crea un oggetto per contare le occorrenze di ogni numero const numCounts = {};
// Itera attraverso l'array e conta le occorrenze for (let num of nums) { // Se il numero esiste già, incrementa il conteggio // Altrimenti, inizializza a 0 e aggiungi 1 numCounts[num] = (numCounts[num] || 0) + 1; }
// Array per memorizzare i duplicati const duplicates = [];
// Itera attraverso l'oggetto e trova i numeri con conteggio > 1 for (let key in numCounts) { // Se il conteggio è maggiore di 1, è un duplicato if (numCounts[key] > 1) { // Converti la chiave in numero (le chiavi degli oggetti sono stringhe) duplicates.push(Number(key)); } }
// Ritorna l'array dei duplicati return duplicates;}Pattern: Usa una hash table per contare occorrenze, poi filtra gli elementi che compaiono più di una volta.
First Non-Repeating Character
Problema: Trovare il primo carattere in una stringa che non si ripete.
Strategia - Doppio passaggio:
- Primo passaggio: conta le occorrenze di ogni carattere usando una hash table
- Secondo passaggio: itera attraverso la stringa e trova il primo carattere con conteggio = 1
Esempio:
firstNonRepeatingChar("aabbcc")→null(tutti i caratteri si ripetono)firstNonRepeatingChar("aabbcde")→'c'(primo carattere non ripetuto)
Complessità: O(n) tempo, O(n) spazio
Mostra implementazione con Map
function firstNonRepeatingChar(string) { // Crea una Map per contare le occorrenze di ogni carattere const charCounts = new Map();
// Primo passaggio: conta le occorrenze for (let i = 0; i < string.length; i++) { const c = string.charAt(i); // Incrementa il conteggio per il carattere corrente charCounts.set(c, (charCounts.get(c) || 0) + 1); }
// Secondo passaggio: trova il primo carattere con conteggio = 1 for (let i = 0; i < string.length; i++) { const c = string.charAt(i); // Se il carattere appare solo una volta, è quello che cerchiamo if (charCounts.get(c) === 1) { return c; } }
// Se non troviamo caratteri non ripetuti, ritorna null return null;}Mostra implementazione con Object
function firstNonRepeatingChar(string) { // Crea un oggetto per contare le occorrenze di ogni carattere const charCounts = {};
// Primo passaggio: conta le occorrenze for (let i = 0; i < string.length; i++) { const c = string.charAt(i); // Incrementa il conteggio per il carattere corrente charCounts[c] = (charCounts[c] || 0) + 1; }
// Secondo passaggio: trova il primo carattere con conteggio = 1 for (let i = 0; i < string.length; i++) { const c = string.charAt(i); // Se il carattere appare solo una volta, è quello che cerchiamo if (charCounts[c] === 1) { return c; } }
// Se non troviamo caratteri non ripetuti, ritorna null return null;}Nota: Il doppio passaggio è necessario perché dobbiamo mantenere l’ordine originale della stringa per trovare il “primo” carattere non ripetuto.
Group Anagrams
Problema: Raggruppare stringhe che sono anagrammi tra loro.
Strategia - Chiave canonica:
- Per ogni stringa, crea una “chiave canonica” ordinando i caratteri
- Usa questa chiave come chiave nella hash table
- Raggruppa tutte le stringhe con la stessa chiave canonica
Esempio:
groupAnagrams(['eat', 'tea', 'tan', 'ate', 'nat', 'bat'])→[['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]
Complessità: O(n * k log k) tempo dove k è la lunghezza media delle stringhe, O(n) spazio
Mostra implementazione con Map
function groupAnagrams(strings) { // Crea una Map per raggruppare gli anagrammi const anagramGroups = new Map();
// Itera attraverso ogni stringa for (const string of strings) { // Converti la stringa in array di caratteri const chars = Array.from(string); // Ordina i caratteri alfabeticamente chars.sort(); // Ricrea la stringa ordinata (chiave canonica) const canonical = chars.join('');
// Se la chiave canonica esiste già nella Map if (anagramGroups.has(canonical)) { // Aggiungi la stringa originale al gruppo esistente anagramGroups.get(canonical).push(string); } else { // Altrimenti, crea un nuovo gruppo con questa stringa const group = [string]; anagramGroups.set(canonical, group); } }
// Ritorna tutti i gruppi come array di array return Array.from(anagramGroups.values());}Mostra implementazione con Object
function groupAnagrams(strings) { // Crea un oggetto per raggruppare gli anagrammi const anagramGroups = {};
// Itera attraverso ogni stringa for (const string of strings) { // Converti la stringa in array di caratteri const chars = Array.from(string); // Ordina i caratteri alfabeticamente chars.sort(); // Ricrea la stringa ordinata (chiave canonica) const canonical = chars.join('');
// Se la chiave canonica esiste già nell'oggetto if (anagramGroups.hasOwnProperty(canonical)) { // Aggiungi la stringa originale al gruppo esistente anagramGroups[canonical].push(string); } else { // Altrimenti, crea un nuovo gruppo con questa stringa anagramGroups[canonical] = [string]; } }
// Ritorna tutti i gruppi come array di array return Object.values(anagramGroups);}Pattern chiave: La “chiave canonica” (stringa ordinata) identifica univocamente tutti gli anagrammi di un gruppo.
Two Sum
Problema: Trovare due numeri in un array che sommano a un target specifico. Ritorna gli indici dei due numeri.
Strategia - Complemento con Hash Table:
- Itera attraverso l’array una sola volta
- Per ogni numero, calcola il complemento (target - numero corrente)
- Se il complemento esiste nella hash table, abbiamo trovato la coppia
- Altrimenti, aggiungi il numero corrente e il suo indice alla hash table
Esempio:
twoSum([2, 7, 11, 15], 9)→[0, 1](2 + 7 = 9)twoSum([3, 2, 4], 6)→[1, 2](2 + 4 = 6)
Complessità: O(n) tempo, O(n) spazio
Mostra implementazione con Map
function twoSum(nums, target) { // Crea una Map per memorizzare numeri e i loro indici const numMap = new Map();
// Itera attraverso l'array for (let i = 0; i < nums.length; i++) { const num = nums[i]; // Calcola il complemento necessario per raggiungere il target const complement = target - num;
// Se il complemento esiste nella Map, abbiamo trovato la coppia if (numMap.has(complement)) { // Ritorna gli indici: indice del complemento e indice corrente return [numMap.get(complement), i]; }
// Altrimenti, salva il numero corrente e il suo indice nella Map numMap.set(num, i); }
// Se non troviamo una coppia, ritorna array vuoto return [];}Mostra implementazione con Object
function twoSum(nums, target) { // Crea un oggetto per memorizzare numeri e i loro indici const numObject = {};
// Itera attraverso l'array for (let i = 0; i < nums.length; i++) { const num = nums[i]; // Calcola il complemento necessario per raggiungere il target const complement = target - num;
// Se il complemento esiste nell'oggetto, abbiamo trovato la coppia if (numObject.hasOwnProperty(complement)) { // Ritorna gli indici: indice del complemento e indice corrente return [numObject[complement], i]; }
// Altrimenti, salva il numero corrente e il suo indice nell'oggetto numObject[num] = i; }
// Se non troviamo una coppia, ritorna array vuoto return [];}Pattern fondamentale: Invece di cercare due numeri con nested loops O(n²), usiamo una hash table per cercare il complemento in O(1).
Subarray Sum
Problema: Trovare un subarray contiguo la cui somma è uguale al target. Ritorna gli indici di inizio e fine del subarray.
Strategia - Prefix Sum con Hash Table:
- Usa una hash table per memorizzare le somme cumulative e i loro indici
- Per ogni elemento, calcola la somma cumulativa
- Se
somma_corrente - targetesiste nella hash table, abbiamo trovato il subarray - Il subarray inizia dall’indice successivo a quello memorizzato
Esempio:
subarraySum([1, 4, 20, 3, 10, 5], 33)→[2, 4](20 + 3 + 10 = 33)subarraySum([1, 2, 3], 3)→[0, 1](1 + 2 = 3)
Complessità: O(n) tempo, O(n) spazio
Mostra implementazione con Map
function subarraySum(nums, target) { // Crea una Map per memorizzare somme cumulative e i loro indici const sumIndex = new Map();
// Inizializza con somma 0 e indice -1 // Questo gestisce il caso in cui il subarray inizia dall'indice 0 sumIndex.set(0, -1);
// Variabile per tenere traccia della somma cumulativa corrente let currentSum = 0;
// Itera attraverso l'array for (let i = 0; i < nums.length; i++) { // Aggiungi l'elemento corrente alla somma cumulativa currentSum += nums[i];
// Se abbiamo visto una somma che, quando sottratta da currentSum, dà il target if (sumIndex.has(currentSum - target)) { // Ritorna gli indici del subarray // Inizio: indice successivo a quello memorizzato // Fine: indice corrente return [sumIndex.get(currentSum - target) + 1, i]; }
// Salva la somma cumulativa corrente e il suo indice nella Map sumIndex.set(currentSum, i); }
// Se non troviamo un subarray, ritorna array vuoto return [];}Mostra implementazione con Object
function subarraySum(nums, target) { // Crea un oggetto per memorizzare somme cumulative e i loro indici const sumIndex = {};
// Inizializza con somma 0 e indice -1 // Questo gestisce il caso in cui il subarray inizia dall'indice 0 sumIndex[0] = -1;
// Variabile per tenere traccia della somma cumulativa corrente let currentSum = 0;
// Itera attraverso l'array for (let i = 0; i < nums.length; i++) { // Aggiungi l'elemento corrente alla somma cumulativa currentSum += nums[i];
// Se abbiamo visto una somma che, quando sottratta da currentSum, dà il target if (sumIndex.hasOwnProperty(currentSum - target)) { // Ritorna gli indici del subarray // Inizio: indice successivo a quello memorizzato // Fine: indice corrente return [sumIndex[currentSum - target] + 1, i]; }
// Salva la somma cumulativa corrente e il suo indice nell'oggetto sumIndex[currentSum] = i; }
// Se non troviamo un subarray, ritorna array vuoto return [];}Pattern avanzato: Usa prefix sum per trasformare il problema di trovare un subarray in un problema di cercare una differenza nella hash table.
Esercizi con Set
Remove Duplicates
Problema: Rimuovere tutti i duplicati da un array, ritornando un nuovo array con solo valori unici.
Strategia - Set per unicità:
- Crea un Set dall’array (i Set rimuovono automaticamente i duplicati)
- Converti il Set in un array
Esempio:
removeDuplicates([1, 2, 2, 3, 4, 4, 4])→[1, 2, 3, 4]
Complessità: O(n) tempo, O(n) spazio
Mostra implementazione
function removeDuplicates(myList) { // Crea un Set dall'array // I Set rimuovono automaticamente i duplicati const uniqueSet = new Set(myList);
// Converti il Set in un array e ritorna return Array.from(uniqueSet);}Nota: I Set sono perfetti per questo problema perché garantiscono automaticamente l’unicità degli elementi.
Has Unique Chars
Problema: Verificare se tutti i caratteri in una stringa sono unici (nessun carattere si ripete).
Strategia - Set per tracking:
- Itera attraverso ogni carattere della stringa
- Se il carattere è già nel Set, ritorna
false - Altrimenti, aggiungi il carattere al Set
- Se completi l’iterazione, ritorna
true
Esempio:
hasUniqueChars("hello")→false(la ‘l’ si ripete)hasUniqueChars("world")→true(tutti i caratteri sono unici)
Complessità: O(n) tempo, O(n) spazio
Mostra implementazione
function hasUniqueChars(string) { // Crea un Set vuoto per memorizzare i caratteri visti const charSet = new Set();
// Itera attraverso ogni carattere della stringa for (const ch of string) { // Se il carattere è già nel Set, non è unico if (charSet.has(ch)) { return false; }
// Altrimenti, aggiungi il carattere al Set charSet.add(ch); }
// Se completiamo l'iterazione, tutti i caratteri sono unici return true;}Pattern: Usa un Set per verificare rapidamente se un elemento è già stato visto.
Find Pairs
Problema: Trovare tutte le coppie di numeri dove un numero proviene da arr1 e l’altro da arr2, e la loro somma è uguale al target.
Strategia - Set per lookup veloce:
- Aggiungi tutti gli elementi di
arr1in un Set - Itera attraverso
arr2e per ogni elemento calcola il complemento (target - elemento) - Se il complemento esiste nel Set, abbiamo trovato una coppia valida
Esempio:
findPairs([1, 2, 3], [4, 5, 6], 7)→[[1, 6], [2, 5], [3, 4]]
Complessità: O(n + m) tempo dove n e m sono le lunghezze degli array, O(n) spazio
Mostra implementazione
function findPairs(arr1, arr2, target) { // Crea un Set per memorizzare gli elementi del primo array const mySet = new Set();
// Array per memorizzare le coppie trovate const pairs = [];
// Aggiungi tutti gli elementi di arr1 al Set for (const num of arr1) { mySet.add(num); }
// Itera attraverso arr2 for (const num of arr2) { // Calcola il complemento necessario per raggiungere il target const complement = target - num;
// Se il complemento esiste nel Set, abbiamo trovato una coppia if (mySet.has(complement)) { // Aggiungi la coppia all'array (complemento da arr1, num da arr2) pairs.push([complement, num]); } }
// Ritorna tutte le coppie trovate return pairs;}Pattern: Simile a Two Sum, ma con due array separati invece di uno solo.
Longest Consecutive Sequence
Problema: Trovare la lunghezza della sequenza consecutiva più lunga in un array di numeri.
Strategia - Set per lookup O(1):
- Aggiungi tutti i numeri in un Set per lookup O(1)
- Per ogni numero, verifica se è l’inizio di una sequenza (il numero - 1 non esiste)
- Se è l’inizio, conta quanti numeri consecutivi seguono
- Tieni traccia della sequenza più lunga trovata
Esempio:
longestConsecutiveSequence([100, 4, 200, 1, 3, 2])→4(sequenza: 1, 2, 3, 4)longestConsecutiveSequence([1, 3, 5, 2, 4])→5(sequenza: 1, 2, 3, 4, 5)
Complessità: O(n) tempo, O(n) spazio
Mostra implementazione
function longestConsecutiveSequence(nums) { // Crea un Set per memorizzare tutti i numeri const numSet = new Set();
// Aggiungi tutti i numeri al Set for (const num of nums) { numSet.add(num); }
// Variabile per tenere traccia della sequenza più lunga let longestStreak = 0;
// Itera attraverso ogni numero nel Set for (const num of numSet) { // Verifica se questo numero è l'inizio di una sequenza // Un numero è l'inizio se il numero precedente (num - 1) non esiste if (!numSet.has(num - 1)) { // Inizia a contare la sequenza da questo numero let currentNum = num; let currentStreak = 1;
// Continua a contare finché troviamo numeri consecutivi while (numSet.has(currentNum + 1)) { currentNum++; currentStreak++; }
// Aggiorna la sequenza più lunga se necessario longestStreak = Math.max(longestStreak, currentStreak); } }
// Ritorna la lunghezza della sequenza più lunga return longestStreak;}Pattern chiave: Verifica solo i numeri che sono l’inizio di una sequenza (num - 1 non esiste) per evitare di contare la stessa sequenza più volte.
Pattern comuni
Questi problemi condividono pattern ricorrenti:
-
Hash Table per lookup O(1): Item In Common, Two Sum, Find Pairs
- Trasforma problemi O(n²) in O(n)
- Memorizza elementi visti per verifiche rapide
-
Conteggio con Hash Table: Find Duplicates, First Non-Repeating Character
- Usa la hash table per contare occorrenze
- Poi filtra o cerca elementi con caratteristiche specifiche
-
Chiave canonica: Group Anagrams
- Crea una rappresentazione standardizzata per raggruppare elementi simili
- Utile per problemi di raggruppamento
-
Prefix Sum: Subarray Sum
- Usa somme cumulative per trasformare problemi di subarray
- Cerca differenze nella hash table invece di iterare tutti i subarray
-
Set per unicità: Remove Duplicates, Has Unique Chars
- I Set garantiscono automaticamente l’unicità
- Perfetti per verifiche di duplicati
-
Complemento: Two Sum, Find Pairs
- Invece di cercare due numeri, cerca il complemento di uno
- Riduce la complessità da O(n²) a O(n)
Pratica questi pattern: compaiono spesso nei problemi di interviste e sono fondamentali per risolvere problemi complessi in modo efficiente.