Dominando la Técnica de Dos Apuntadores
In this article
La técnica de Dos Apuntadores (Two Pointers) es uno de los patrones más comunes y efectivos para resolver problemas de arreglos y cadenas. En lugar de usar bucles anidados (), utilizamos dos variables (apuntadores) para recorrer la estructura de datos, logrando a menudo una complejidad de tiempo de .
1. Verificando un Diccionario Alienígena
En un lenguaje alienígena, utilizan nuestras letras pero en un orden diferente. Dada una lista de palabras y el orden del alfabeto alienígena, debemos verificar si las palabras están ordenadas lexicográficamente.
/** * Verifica si las palabras están ordenadas según un orden alfabético personalizado. * @param words - Arreglo de cadenas * @param order - El orden del alfabeto alienígena */function isAlienSorted(words: string[], order: string): boolean { const orderMap = new Map<string, number>(); for (let i = 0; i < order.length; i++) { orderMap.set(order[i], i); }
for (let i = 0; i < words.length - 1; i++) { const word1 = words[i]; const word2 = words[i + 1];
if (!compareWords(word1, word2, orderMap)) { return false; } }
return true;}
function compareWords(w1: string, w2: string, map: Map<string, number>): boolean { const minLen = Math.min(w1.length, w2.length);
for (let i = 0; i < minLen; i++) { if (w1[i] !== w2[i]) { return (map.get(w1[i]) || 0) < (map.get(w2[i]) || 0); } }
// Si una es prefijo de la otra, la más corta debe ir primero return w1.length <= w2.length;}[!TIP] Consejo Lexicográfico: Siempre maneja el caso del “prefijo”. Si
word1es “apple” yword2es “app”,word1debería ir después deword2en cualquier diccionario válido.
2. Fusionar Arreglos Ordenados (In-place)
Fusiona dos arreglos ordenados nums1 y nums2 en nums1 como un solo arreglo ordenado. nums1 tiene suficiente espacio (ceros al final) para contener a nums2.
/** * Fusiona nums2 en nums1 in-place. */function merge(nums1: number[], m: number, nums2: number[], n: number): void { let p1 = m - 1; // Último elemento de datos reales en nums1 let p2 = n - 1; // Último elemento en nums2 let p3 = m + n - 1; // Última posición en nums1
while (p2 >= 0) { if (p1 >= 0 && nums1[p1] > nums2[p2]) { nums1[p3] = nums1[p1]; p1--; } else { nums1[p3] = nums2[p2]; p2--; } p3--; }}[!SUCCESS] Truco: Al fusionar in-place donde el objetivo tiene espacio extra al final, itera hacia atrás. ¡Esto evita sobrescribir elementos que aún no has procesado!
3. El Contenedor con más Agua
Encuentra dos líneas que formen un contenedor de tal manera que contenga la mayor cantidad de agua.
export function maxArea(heights: number[]): number { let left = 0; let right = heights.length - 1; let maxAreaValue = 0;
while (left < right) { const width = right - left; const minHeight = Math.min(heights[left], heights[right]); const area = minHeight * width;
maxAreaValue = Math.max(maxAreaValue, area);
// Movimiento codicioso: Mover el puntero que apunta a la línea más corta if (heights[left] < heights[right]) { left++; } else { right--; } }
return maxAreaValue;}[!IMPORTANT] Razonamiento: En el problema del contenedor de agua, movemos el lado más corto porque mover el lado más largo solo puede disminuir el ancho sin ninguna posibilidad de aumentar la altura (la altura está limitada por el lado más corto).
4. Mover Ceros
Mueve todos los 0s al final del arreglo manteniendo el orden relativo de los elementos que no son cero.
function moveZeroes(nums: number[]): void { let lastNonZeroFoundAt = 0;
// Si el elemento actual no es 0, entonces necesitamos // agregarlo frente al último elemento no 0 que encontramos. for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) { nums[lastNonZeroFoundAt++] = nums[i]; } }
// Después de terminar de procesar nuevos elementos, // todos los elementos no cero ya están al principio del arreglo. // Solo necesitamos llenar el resto del arreglo con 0s. for (let i = lastNonZeroFoundAt; i < nums.length; i++) { nums[i] = 0; }}Resumen de Patrones
- Lados Opuestos: Los punteros comienzan en
0ylength - 1(ej., Palíndromo, Contenedor de Agua). - Mismo Lado (Lento/Rápido): Ambos comienzan en
0pero se mueven a diferentes velocidades o condiciones (ej., Mover Ceros, Eliminar Duplicados). - Hacia Atrás: Comenzando desde el final para llenar espacio in-place (ej., Fusionar Arreglos Ordenados).