Un patrón de resolución no es una plantilla que se copia. Es una relación recurrente entre las restricciones del problema, las operaciones necesarias y una propiedad que permite evitar trabajo.
La pregunta útil no es “¿qué patrón toca?”, sino “¿qué información puedo mantener mientras avanzo para no recomputar o retroceder innecesariamente?”.
Puede usar extremos opuestos o punteros que avanzan a distinta velocidad.
Usa extremos cuando el orden permite decidir qué lado mover. Usa fast and slow pointers cuando necesitas detectar ciclos, compactar datos o separar lectura de escritura.
Sirve para segmentos contiguos cuando puedes actualizar el estado incrementalmente.
Ventana fija: entra uno y sale uno.
Ventana variable: expandes y contraes mientras una condición lo permita.
No funciona automáticamente si quitar un elemento no permite restaurar el estado de forma eficiente o si la condición no es monotónica respecto al tamaño de la ventana.
En una linked list, un puntero avanza un paso y otro dos. Si hay ciclo, terminan coincidiendo. El patrón no depende de “magia”; depende de que, dentro del ciclo, la distancia relativa cambia en una unidad por iteración.
También aparece al compactar arrays: read explora y write marca dónde colocar el siguiente valor válido.
Mantén un min-heap de tamaño k para conservar los k elementos más grandes vistos. El menor de esos candidatos está en la raíz y puede descartarse cuando aparece uno mejor.
Busca una pregunta repetida: “¿cuál es la mejor respuesta desde este estado?”. Si dos caminos llegan al mismo estado y el futuro ya no depende de cómo llegaron, puedes reutilizar el resultado.
Excelente para preguntas de conectividad después de uniones. No sirve para recuperar el camino entre dos nodos ni para eliminar aristas de forma sencilla.
Mantiene candidatos en orden creciente o decreciente. Cuando llega un elemento que domina a los del tope, esos elementos se resuelven y salen definitivamente.
TypeScript
functionnextGreater(values:number[]):number[]{const result =Array(values.length).fill(-1);const stack:number[]=[];for(let i =0; i < values.length; i +=1){while(stack.length >0&& values[i]> values[stack.at(-1)!]){
result[stack.pop()!]= values[i];}
stack.push(i);}return result;}
Cada índice entra y sale una vez: tiempo O(n), aunque exista un while anidado.
Un patrón es válido por una propiedad del problema. Si no puedes explicar qué opciones descarta o qué trabajo reutiliza, todavía no has justificado su uso.
Un problema pide el segmento contiguo más corto cuya suma sea al menos k. ¿Sliding window funciona siempre?
Respuesta
No. Con números no negativos, al expandir la suma no disminuye y al contraer no aumenta, lo que produce una condición monotónica útil. Con números negativos, quitar o agregar un valor puede cambiar la suma en cualquier dirección; se necesita otra técnica, como prefix sums con una deque monotónica en ciertas variantes.