Qué son las estructuras de datos y los algoritmos | Nicolás Garzón
Las estructuras de datos definen cómo organizas información. Los algoritmos definen los pasos para consultarla, transformarla o tomar decisiones con ella.
No se estudian para memorizar soluciones de plataformas. Se estudian para aprender a traducir un problema en:
Texto
Copiar datos
→ operaciones
→ restricciones
→ representación
→ algoritmo
→ correctitud
→ complejidadUna buena solución no es solamente la que funciona con un ejemplo. Debe respetar el contrato, terminar, manejar casos límite y usar recursos adecuados para el tamaño esperado.
Una estructura de datos combina:
una forma de representar valores;
relaciones entre esos valores;
operaciones permitidas;
invariantes que deben conservarse;
costos asociados a cada operación.
array: posiciones indexadas;
linked list: nodos conectados;
stack: acceso LIFO;
queue: procesamiento FIFO;
hash table: asociación por clave;
tree: jerarquía padre-hijo;
graph: relaciones generales;
heap: acceso al elemento prioritario.
La misma información puede representarse de varias maneras. La representación correcta depende de las operaciones dominantes.
Un algoritmo es un procedimiento finito y preciso que transforma una entrada en una salida.
pasos no ambiguos;
entradas bien definidas;
una salida verificable;
terminación;
un dominio donde sus garantías sean válidas.
TypeScript
Copiar function maximum ( values: readonly number [ ] ) : number | undefined {
if ( values. length === 0 ) return undefined ;
let best = values[ 0 ] ;
for ( let index = 1 ; index < values. length; index += 1 ) {
if ( values[ index] > best) {
best = values[ index] ;
}
}
return best;
} Este algoritmo depende de una definición de orden sobre números, declara qué ocurre con la entrada vacía y recorre cada elemento una vez.
Problema: familia general de preguntas.
Instancia: una entrada concreta.
Solución: resultado para esa instancia.
Texto
Copiar Problema:
Encontrar el valor máximo de una secuencia.
Instancia:
[4, 1, 9, 2]
Solución:
9Probar que el algoritmo funciona para esta instancia no demuestra que funcione para todas.
Antes de programar, escribe qué recibe y qué devuelve.
Dado un array de enteros y un objetivo, devuelve dos índices diferentes cuyos valores sumen el objetivo, o null si no existen.
entrada: number[] y number;
salida: par de índices o null;
las posiciones deben ser distintas;
no se promete que siempre exista respuesta.
Todavía podrían faltar decisiones sobre duplicados, mutación, límites numéricos y si debe devolverse una o todas las parejas.
Las restricciones cambian qué soluciones son viables.
Texto
Copiar n ≤ 20 → enumeración exponencial puede ser posible
n ≤ 10³ → O(n²) puede encajar según constantes
n ≤ 10⁵ → normalmente necesitas O(n log n) u O(n)
n ≤ 10⁷ → memoria y pasadas completas ya importan muchoEstas son orientaciones, no reglas universales. El runtime, límite de tiempo, costo de operaciones y cantidad de casos también influyen.
¿puede estar vacío?
¿hay duplicados?
¿está ordenado?
¿hay negativos?
¿los pesos pueden ser negativos?
¿qué tamaño máximo tiene la entrada?
¿se permiten ciclos?
¿puedo modificar los datos?
¿cuántas consultas habrá?
¿la salida puede ser enorme?
Una invariante es una propiedad que debe mantenerse.
un min-heap conserva parent <= child;
un BST conserva el orden de sus subárboles;
una sliding window puede mantener “todos los caracteres son únicos”;
Union-Find mantiene un bosque de padres;
insertion sort mantiene un prefijo ordenado.
Pensar en invariantes ayuda a diseñar el algoritmo y demostrar que cada operación conserva una estructura válida.
ciudades;
carreteras;
distancia o costo.
una carretera une dos ciudades;
puede tener dirección;
puede tener peso;
pueden existir varias carreteras entre la misma pareja.
agregar conexión;
comprobar reachability;
calcular shortest path;
encontrar una red mínima;
detectar ciclos.
adjacency list para grafos dispersos;
matrix para conectividad directa y grafos densos;
edge list para algoritmos que procesan aristas globalmente.
BFS con adjacency list cuesta O(V + E). Con matrix puede costar O(V²). La representación forma parte de la solución.
Atender solicitudes en el orden en que llegan.
entidad: solicitud;
operación dominante: insertar al final y retirar al inicio;
invariante: la solicitud más antigua pendiente debe salir primero;
estructura: queue FIFO.
TypeScript
Copiar const queue: Request[ ] = [ ] ;
queue. push ( request) ;
const next = queue. shift ( ) ; El comportamiento FIFO es correcto, pero shift() puede desplazar elementos y costar O(n).
circular buffer;
deque de biblioteca;
almacenamiento con índices head y tail.
Una estructura puede ser semánticamente correcta y operativamente inadecuada.
No preguntes solamente “¿qué estructura es más rápida?”. Pregunta:
¿Qué operaciones realiza el sistema y con qué frecuencia?
Necesidad Estructura candidata Costo relevante Acceso frecuente por posición Array O(1) Lookup por clave Hash table O(1) promedio Claves ordenadas y rangos BST balanceado O(log n) Extremo prioritario Heap Peek O(1), update O(log n) Procesar niveles Queue Enqueue/dequeue O(1) Regreso o nesting Stack Push/pop O(1) Conectividad general Graph Depende de V, E y representación
Una estructura optimiza algunas operaciones a cambio de otras.
Un Map puede reducir búsquedas repetidas de O(n) a O(1) promedio, pero usa memoria adicional.
Prefix sums gastan O(n) una vez para responder rangos en O(1).
Hash tables suelen ofrecer lookup promedio constante. Un BST balanceado ofrece O(log n) peor caso y mantiene orden.
Un algoritmo in-place reduce memoria, pero modifica la entrada y puede ser más difícil de verificar.
Counting sort puede superar n log n para enteros en un rango pequeño. No sirve como sorting universal.
Brute force explora la solución más directa.
No siempre es la versión que entregarás, pero permite:
confirmar el contrato;
obtener una referencia correcta;
identificar trabajo repetido;
estimar el límite mínimo de optimización;
diseñar tests.
Para encontrar una pareja con suma:
TypeScript
Copiar function pairBruteForce (
values: readonly number [ ] ,
target: number ,
) : [ number , number ] | null {
for ( let left = 0 ; left < values. length; left += 1 ) {
for ( let right = left + 1 ; right < values. length; right += 1 ) {
if ( values[ left] + values[ right] === target) {
return [ left, right] ;
}
}
}
return null ;
} Tiempo O(n²), espacio O(1).
Observación: para cada valor buscamos repetidamente su complemento. Un Map puede conservar valores anteriores y reducir tiempo promedio a O(n) con O(n) memoria.
La optimización nace de entender el trabajo repetido.
Un patrón conecta una propiedad con una estrategia.
Orden + descarte seguro → two pointers o binary search.
Segmento contiguo + estado actualizable → sliding window.
Consultas repetidas de rango → prefix sum.
Pertenencia o complemento → hashing.
Estructura jerárquica → tree DFS/BFS.
Conectividad → graph traversal o Union-Find.
Decisiones reversibles → backtracking.
Subproblemas repetidos → dynamic programming.
Elección local demostrablemente segura → greedy.
Extremo prioritario dinámico → heap.
No memorices la asociación sin la propiedad. Binary search no funciona porque el enunciado “parece de búsqueda”; funciona cuando existe monotonicidad.
Una solución correcta debe:
producir una salida válida;
no perder soluciones requeridas;
no producir resultados prohibidos;
conservar invariantes;
terminar;
respetar mutación y errores del contrato.
¿qué afirma cada variable?
¿qué parte ya está resuelta?
¿por qué puedo descartar esta región?
¿qué medida disminuye?
¿qué ocurre con vacío, duplicados y extremos?
Analiza la implementación real:
shift() puede cambiar el costo de una queue;
slice() puede crear copias;
recursión consume call stack;
una hash table ofrece promedio, no peor caso constante;
un BST depende de altura;
un loop anidado puede ser lineal amortizado si cada elemento sale una vez.
Una frase completa define variables y supuestos:
Con adjacency list, BFS visita cada vértice y arista una vez: tiempo O(V + E) y memoria adicional O(V).
JavaScript ofrece estructuras integradas:
Array;
Map;
Set;
typed arrays;
strings;
objetos.
No ofrece de forma estándar general una priority queue, deque o balanced BST. Para aprender puedes implementarlas. Para producción suele ser mejor una biblioteca madura o una representación más simple.
TypeScript ayuda a expresar contratos:
TypeScript
Copiar type Comparator< T > = ( a: T , b: T ) => number ;
type WeightedEdge = {
to: number ;
weight: number ;
} ; Los tipos no prueban correctitud algorítmica. Pueden impedir estados inválidos, pero todavía debes analizar índices, invariantes y casos límite.
prioriza visibilidad del algoritmo;
usa nombres descriptivos;
expone invariantes;
evita optimizaciones prematuras;
puede omitir concurrencia, persistencia o tuning.
API estable;
validación adecuada;
manejo de errores;
límites de memoria;
observabilidad;
seguridad;
pruebas extensas;
compatibilidad;
rendimiento medido;
mantenimiento.
Una implementación de Dijkstra para estudiar no es automáticamente un motor de rutas de producción.
El pseudocódigo expresa lógica sin detalles de un lenguaje.
Texto
Copiar for each item in input
update state
return resultAl traducirlo debes decidir:
tipos;
límites de índices;
representación;
mutación;
valores ausentes;
errores;
costos de métodos del lenguaje.
Dos traducciones del mismo pseudocódigo pueden tener complejidades distintas.
Sigue el estado paso a paso:
Texto
Copiar entrada
→ variables iniciales
→ invariante
→ decisión
→ actualización
→ estado siguiente
→ terminaciónPara estructuras mutables, dibuja referencias:
Texto
Copiar head → A → B → C → nullPara recursión, dibuja frames o árbol de llamadas.
Para DP, define exactamente qué representa dp[state].
Para grafos, dibuja vértices, dirección y pesos.
Un caso límite no es algo extraño. Es una entrada válida en el borde del dominio.
colección vacía;
un elemento;
todos iguales;
ordenado o invertido;
valores negativos;
estructura degenerada;
componentes desconectadas;
ciclo;
peso cero o negativo;
capacidad llena;
respuesta inexistente;
salida máxima;
Unicode y normalización.
Diseñarlos antes del código obliga a completar el contrato.
Reformula el enunciado.
Define entrada y salida.
Pregunta restricciones relevantes.
Diseña ejemplos mínimos y adversariales.
Propón brute force.
Analiza su tiempo y espacio.
Identifica el trabajo repetido o la propiedad disponible.
Elige una estructura o patrón.
Declara el invariante.
Implementa con límites claros.
Recorre manualmente un caso.
Demuestra correctitud y terminación.
Analiza la implementación real.
Prueba casos límite.
Compara con alternativas y trade-offs.
Aprenderás a representar problemas, demostrar correctitud, analizar complejidad y distinguir benchmarks de crecimiento asintótico.
Trabajarás acceso por índice, mutación, Unicode, prefix sums, two pointers, sliding window y algoritmos in-place.
Comprenderás nodos, referencias, sentinels, ciclos, LIFO, FIFO, deques y estructuras monotónicas.
Verás hash functions, collisions, load factor, rehashing, Map, Set, frecuencias y memoization.
Modelarás call stack, recurrence relations, divide and conquer y árboles de decisiones.
Compararás búsqueda lineal y binaria, algoritmos educativos, sorting eficiente, estabilidad y límites por comparación.
Estudiarás traversals, BST, balance, priority queues, tries y consultas de rango.
Aprenderás representación, BFS, DFS, ciclos, topological sort, shortest paths, MST y Union-Find.
Distinguirás decisiones locales demostrables de reutilización de subproblemas.
Conectarás propiedades con técnicas transferibles sin memorizar respuestas.
Practicarás lectura, comunicación, testing, correctitud y una ruta deliberada de estudio.
Texto
Copiar comprender la idea
→ dibujar un ejemplo
→ implementar sin copiar
→ explicar el invariante
→ analizar tiempo y espacio
→ modificar una condición
→ comparar otra estructura
→ resolver una varianteNo avances solo porque el código compila. Debes poder explicar:
por qué termina;
qué garantiza;
qué caso la rompe;
qué representa cada variable;
cuándo no usarla.
Clasifica errores por causa:
off-by-one;
caso base;
estado insuficiente;
mutación accidental;
duplicados;
estructura incorrecta;
complejidad mal analizada;
propiedad inexistente;
prueba incompleta.
Resolver cien ejercicios sin revisar causas puede reforzar hábitos. Resolver menos y reconstruir el razonamiento produce aprendizaje transferible.
una plantilla de binary search sin entender límites;
O(1) para hashing sin calificativos;
O(log n) para cualquier árbol;
sliding window para toda suma;
greedy porque “toma lo mejor”;
una tabla de DP sin significado de estado;
una solución de plataforma sin propiedad general.
Memoriza modelos e invariantes después de comprenderlos.
Input size: medida relevante de entrada.
Invariant: propiedad conservada.
Traversal: orden de recorrido.
Mutation: modificación de datos existentes.
Stable: conserva orden relativo de claves iguales.
In-place: usa poca memoria auxiliar según una convención.
Amortized: costo distribuido sobre una secuencia.
Monotonic: cambia en una sola dirección útil para descarte.
State: información suficiente para decidir el futuro.
Representation: forma concreta de almacenar relaciones.
Empezar por código sin definir salida.
Elegir una estructura por nombre y no por operaciones.
Optimizar antes de tener una referencia correcta.
Confundir ejemplos con garantía.
Ignorar restricciones y tamaño.
Declarar complejidad de memoria sin call stack.
Copiar métodos de librería que ocultan el algoritmo estudiado.
Forzar patrones por semejanza superficial.
Tratar una implementación educativa como lista para producción.
Medir rendimiento sin comprobar correctitud.
DSA conecta representación, operaciones, invariantes y costos.
La estructura correcta depende de lo que necesitas hacer con los datos.
Las restricciones determinan qué solución es viable.
Brute force establece una base antes de optimizar.
Un patrón funciona por una propiedad demostrable.
Correctitud y complejidad forman parte del resultado.
El objetivo final es razonar, no coleccionar soluciones.
Necesitas almacenar usuarios por id, hacer miles de búsquedas exactas y nunca consultar rangos ordenados. ¿Qué estructura considerarías primero y qué garantía debes aclarar?
Respuesta Un Map o hash table, porque modela asociación por clave y ofrece lookup promedio cercano a O(1). Debes aclarar que no es una garantía de peor caso constante y que la igualdad de claves, memoria y comportamiento del runtime también forman parte del contrato.