Carrera y Preparación

Preguntas de Entrevista sobre Dijkstra

A nadie le piden recitar Dijkstra. Te dan un problema cuyos pesos no son distancias, y la prueba es si ves que la forma del algoritmo sigue encajando. Ocho preguntas que aparecen una y otra vez, cada una con la solución, la pregunta de seguimiento que hace después el entrevistador y el error que te cuesta la oferta.

16 Min de lectura Actualizado: Septiembre 2026 Nivel intermedio
Mohammed Islam Hadjoudj
Mohammed Islam Hadjoudj
Expert Operations Research Engineer

1. Qué evalúa realmente una pregunta de Dijkstra

A nadie le piden recitar el algoritmo de Dijkstra. Lo que te dan es un problema cuyos pesos no son distancias, y la entrevista pregunta si ves que la forma del algoritmo sigue encajando.

Esa forma es: una cola de prioridad ordenada por una etiqueta de coste, una regla de relajación que mejora la etiqueta de un vecino y la garantía de que, una vez extraído un vértice, su etiqueta es definitiva. Cambia lo que significa «coste», cambia la comparación, y las mismas doce líneas resuelven mínimo esfuerzo, máxima probabilidad, vuelos más baratos y media docena de preguntas más. La entrevista evalúa si sabes qué partes puedes cambiar y cuál no. La nota original de Dijkstra de 1959 ocupa dos páginas, y la idea no ha necesitado revisión desde entonces.

Los ocho problemas de abajo son los que se repiten, cada uno con la solución, el seguimiento y el error que te cuesta la oferta. Cada ejemplo resuelto se ejecutó con un script.

2. La plantilla y el borrado perezoso

Escribe esto sin pensar. Los comentarios marcan las dos líneas que separan una implementación correcta de una que solo lo parece.

import heapq

def dijkstra(adj, src, n):          # adj[u] = [(v, w), ...] con w >= 0
    dist = [float('inf')] * n
    dist[src] = 0
    heap = [(0, src)]
    while heap:
        d, u = heapq.heappop(heap)
        if d > dist[u]:             # entrada OBSOLETA: se encontró una etiqueta mejor
            continue                # después de insertar esta. Saltarla.
        for v, w in adj[u]:
            if d + w < dist[v]:
                dist[v] = d + w
                heapq.heappush(heap, (dist[v], v))   # insertar, nunca decrease-key
    return dist

La línea if d > dist[u]: continue es toda la respuesta a «¿cómo manejas decrease-key?». Un montículo binario no tiene un decrease-key eficiente, así que en lugar de actualizar una entrada insertas otra e ignoras la extracción obsoleta. Esto es el borrado perezoso, y saber nombrarlo vale más que el código que lo rodea. Por eso el montículo puede contener hasta O(E) entradas en lugar de O(V), y por eso la cota es O((V + E) log V) y no O((V + E) log E): los logaritmos difieren en un factor constante, ya que E < V2.

Dos cosas más que decir en voz alta. Un vértice queda fijado en el momento en que se extrae con una etiqueta vigente, y su distancia no cambia nunca después; ese es el invariante en el que se apoya la elección voraz. Y no necesitas un conjunto visited aparte, porque la comprobación de obsolescencia ya rechaza cualquier segunda extracción.

3. Network delay time y la reconstrucción del camino

La pregunta. Dado un grafo dirigido ponderado y un origen, ¿cuánto tarda en alcanzarse cada vértice? Devuelve -1 si algún vértice no se alcanza nunca. En las entrevistas aparece como propagación de señales, reparto de paquetes o «cuándo se entera el último servidor».

Es Dijkstra sin más, con una línea extra: la respuesta es max(dist), y -1 si alguna entrada sigue siendo infinita.

Un grafo dirigido ponderado de seis vértices con Dijkstra ejecutado desde el vértice 0. El vértice 0 tiene arcos hacia 1 con peso 4 y hacia 2 con peso 1; el vértice 2 llega a 1 con peso 2, así que la etiqueta del vértice 1 mejora de 4 a 3 antes de fijarse. Las distancias finales son 0, 3, 1, 8, 10 y 12, y el orden de fijación es 0, 2, 1, 3, 4, 5. Un panel enumera las nueve inserciones en el montículo y las cuatro extracciones obsoletas que salta la guarda del borrado perezoso.
El grafo de ejemplo. El vértice 1 recibe primero la etiqueta 4 y luego mejora a 3 antes de fijarse, y cuatro de las nueve entradas insertadas se extraen obsoletas y se saltan.

En el grafo de ejemplo, Dijkstra desde 0 fija los vértices en el orden 0, 2, 1, 3, 4, 5 y devuelve las distancias 0, 3, 1, 8, 10, 12. Fíjate en el vértice 1: el arco 0 → 1 le da la etiqueta 4, luego 0 → 2 → 1 la mejora a 3 antes de que llegue a extraerse. Eso es el algoritmo funcionando exactamente como debe, y es la razón por la que no debes comprometerte con una etiqueta al insertar.

El seguimiento: devuelve el camino, no solo la longitud. Mantén un array parent, asigna parent[v] = u en la misma rama que mejora dist[v] y luego recórrelo hacia atrás desde el destino e inviértelo. En este grafo da 0 → 2 → 1 → 4 → 5, coste 12. Di «un camino más corto» en lugar de «el»: aquí hay dos de coste 12, como muestra la sección 7.

La trampa. Asignar parent[v] fuera de la rama de mejora, de modo que registra el último vértice que lo intentó en lugar del que tuvo éxito. Las distancias siguen bien y el camino reconstruido está mal, que es el peor tipo de error que encontrar en una revisión.

4. Vuelos más baratos con K escalas como máximo

La pregunta. La ruta más barata del origen al destino con como mucho K escalas intermedias.

Esta es la pregunta que atrapa a la gente, porque el Dijkstra simple es incorrecto aquí. Su corrección se basa en que un vértice tiene una única etiqueta definitiva, pero con un límite de escalas un vértice tiene un mejor coste distinto para cada número de escalas usadas, y una ruta barata que gasta demasiados saltos puede ser peor que una cara y corta. Fijar un vértice una sola vez descarta justo la alternativa que necesitas.

Dos respuestas correctas, y conocer ambas es la clave.

Amplía el estado. Mantén Dijkstra, pero haz que el vértice sea un par (node, stops_used). La etiqueta ahora es definitiva por par, así que el invariante vuelve a cumplirse.

def cheapest(adj, src, dst, K, n):
    best = [[float('inf')] * (K + 2) for _ in range(n)]
    best[src][0] = 0
    heap = [(0, src, 0)]                     # (coste, nodo, escalas)
    while heap:
        c, u, k = heapq.heappop(heap)
        if u == dst: return c                # la primera extracción de dst es óptima
        if k > K or c > best[u][k]: continue
        for v, w in adj[u]:
            if c + w < best[v][k + 1]:
                best[v][k + 1] = c + w
                heapq.heappush(heap, (c + w, v, k + 1))
    return -1

O usa Bellman-Ford, que es la respuesta más limpia. Relajar cada arista exactamente K + 1 veces, cada ronda a partir de una copia de la ronda anterior, da directamente la ruta más barata con como mucho K + 1 aristas. Es la formulación de Bellman de 1958, y cuesta O(K × E) sin ningún montículo. Ofrecerla sin que te la pidan causa muy buena impresión.

La trampa. La versión de Bellman-Ford debe relajar a partir de una copia de las distancias de la ronda anterior. Relajar en el sitio deja que una sola ronda se propague por varias aristas, lo que en silencio permite más de K escalas y devuelve una respuesta demasiado barata que parece plausible.

5. Camino de mínimo esfuerzo: sustituir la suma

La pregunta. Minimiza la arista individual más grande de la ruta en lugar del total. Formulaciones: camino de mínimo esfuerzo, nadar con el agua subiendo, el peso máximo que debes poder cargar.

La idea clave es que Dijkstra nunca necesitó realmente la suma. Necesita que alargar un camino no pueda mejorar su coste, para que una etiqueta fijada siga siendo definitiva. max cumple eso igual de bien que +, así que cambia una línea:

        cand = max(d, w)          # en lugar de d + w
        if cand < best[v]:
            best[v] = cand
            heapq.heappush(heap, (cand, v))

En el grafo de ejemplo, los valores minimax desde el vértice 0 son 0, 2, 1, 5, 5, 5, así que el mejor cuello de botella hasta el vértice 5 es 5: probar todas las rutas lo confirma. Que es un objetivo realmente distinto se ve en las dos rutas más baratas, que cuestan 12 ambas pero tienen arcos máximos de 5 y 7. Minimizar el total y minimizar el arco más grande no son la misma pregunta, y en general la ruta óptima en cuello de botella no tiene por qué ser una ruta más corta.

El mismo grafo ponderado de seis vértices resuelto dos veces desde el vértice 0. A la izquierda, el objetivo de suma habitual da las distancias 0, 3, 1, 8, 10, 12 con un camino más corto de 0 a 2 a 1 a 4 a 5 de coste 12. A la derecha, el objetivo minimax, que sustituye la suma por el máximo, da los valores 0, 2, 1, 5, 5, 5, así que el mejor cuello de botella hasta el vértice 5 es 5. Una nota indica que solo difiere una línea del algoritmo.
Mismo grafo, mismo código, una línea cambiada. Cambiar + por max convierte el camino más corto en el camino más ancho.

El seguimiento: ¿qué más puede sustituir a la suma? Cualquier operación monótona, es decir, en la que alargar un camino nunca reduce su coste. max funciona, la multiplicación de probabilidades en [0,1] funciona si maximizas, y la suma ordinaria de pesos no negativos funciona. La resta no, y es por la misma razón por la que se prohíben las aristas negativas.

La trampa. En una versión en cuadrícula también se acepta una solución con union-find o con búsqueda binaria más BFS, que a veces es más rápida. Si ofreces Dijkstra, prepárate para decir por qué lo elegiste: no hay parámetro sobre el que hacer búsqueda binaria y basta una pasada.

6. Camino de máxima probabilidad

La pregunta. Cada arista tiene una probabilidad de éxito; encuentra la ruta del origen al destino con la mayor probabilidad de que todas las aristas tengan éxito.

Los costes se multiplican en lugar de sumarse, y quieres el producto más grande, así que conviertes la cola en un montículo de máximos y relajas con ×. Las probabilidades están en [0,1], así que alargar un camino solo puede reducir el producto, que es exactamente la monotonía que Dijkstra necesita.

        cand = p * pw                     # en lugar de d + w
        if cand > best[v]:                # > porque maximizamos
            best[v] = cand
            heapq.heappush(heap, (-cand, v))   # negar: heapq es un montículo de MÍNIMOS

En un grafo con un arco directo de 0,30 de 0 a 3 y la ruta de dos arcos 0 → 1 → 3 con 0,9 y 0,8, la mejor probabilidad es 0.72 por la ruta de dos arcos, que supera al arco único. Esa es la frase que hay que decir: aquí más aristas pueden ser mejores, algo que nunca ocurre en los caminos más cortos ordinarios con pesos positivos.

El seguimiento: ¿por qué no usar logaritmos? Puedes, y es una buena respuesta. Como log(ab) = log a + log b, maximizar un producto de probabilidades equivale a minimizar una suma de -log p, que son no negativos, así que se aplica Dijkstra sin cambios. Menciona la salvedad: el log en coma flotante de una probabilidad cercana a cero pierde precisión, y una arista de probabilidad 0 da un infinito que tienes que tratar aparte.

7. Contar caminos más cortos

La pregunta. ¿Cuántas rutas más cortas distintas hay del origen al destino? Normalmente se pide módulo 109+7.

Un array extra y una rama extra. Junto a dist mantén ways, el número de rutas más cortas hasta cada vértice. Cuando una relajación mejora una etiqueta, el conteo se sustituye. Cuando empata, el conteo se suma.

        if d + w < dist[v]:
            dist[v] = d + w
            ways[v] = ways[u]              # estrictamente mejor: sustituir
            heapq.heappush(heap, (dist[v], v))
        elif d + w == dist[v]:
            ways[v] = (ways[v] + ways[u]) % MOD   # empate: SUMAR

En el grafo de ejemplo, los conteos son 1, 1, 1, 1, 2, 2. La fuerza bruta coincide: de las 9 rutas de 0 a 5, dos cuestan 12, que son 0→2→1→3→4→5 y 0→2→1→4→5. Fíjate en que la ruta más corta en número de aristas no es la única mejor, justo el tipo de detalle que conviene señalar.

El seguimiento: ¿es segura la rama del empate? Lo es, pero solo porque ways[u] es definitivo cuando u se extrae, y toda relajación se hace desde un vértice extraído. Sumar conteos desde un vértice que no se ha fijado contaría de más. Es el ejemplo más claro de por qué «fijado significa definitivo» es el invariante que importa, no el código.

La trampa. Olvidar que el elif debe ir en la igualdad, no dentro de la rama de mejora. Escrito como un único if d + w <= dist[v], los conteos se sustituyen en los empates en lugar de sumarse, y la respuesta es 1 para todos los vértices.

8. El segundo camino más corto

La pregunta. Encuentra la segunda ruta más corta del origen al destino. Aclara de inmediato si «segunda» significa estrictamente más larga que la mejor o simplemente la siguiente ruta de una lista en la que los empates cuentan por separado. Las dos respuestas difieren y los entrevistadores lo preguntan a propósito.

La técnica consiste en relajar la regla de fijar una sola vez: guarda las dos mejores etiquetas de cada vértice y deja que un vértice se extraiga dos veces.

best1 = [inf] * n; best2 = [inf] * n
best1[src] = 0
heap = [(0, src)]
while heap:
    d, u = heapq.heappop(heap)
    if d > best2[u]: continue          # peor que las dos etiquetas que guardamos
    for v, w in adj[u]:
        nd = d + w
        if nd < best1[v]:
            best1[v], nd = nd, best1[v]     # degradar el antiguo mejor a candidato
            heapq.heappush(heap, (best1[v], v))   # el NUEVO mejor también debe propagarse
        if best1[v] < nd < best2[v]:        # estrictamente peor que el mejor
            best2[v] = nd
            heapq.heappush(heap, (nd, v))

En el grafo de ejemplo, los costes de ruta distintos de 0 a 5 son 12, 13, 14, 15, así que el segundo mejor en sentido estricto es 13. Si en cambio los empates cuentan por separado, la respuesta vuelve a ser 12, porque dos rutas distintas la alcanzan. Pregunta antes de programar.

El seguimiento: generaliza a K. Guarda una lista de las K mejores etiquetas por vértice, o usa el algoritmo de Yen para los K caminos más cortos sin ciclos, que es un problema realmente distinto y mucho más pesado. Decir «sin ciclos lo cambia todo» es el instinto correcto: sin esa restricción, un recorrido más corto puede repetir para siempre un ciclo de peso cero.

9. Por qué los pesos no negativos no son negociables

Todos los entrevistadores lo preguntan, y la mayoría de los candidatos responde «porque Dijkstra es voraz», lo cual es cierto y no explica nada. La razón precisa: el algoritmo supone que, cuando un vértice se extrae con la menor etiqueta de la cola, ninguna ruta aún en construcción puede llegar a él más barato. Una arista negativa lo rompe, porque alargar un camino puede reducir su coste.

Ten preparado un contraejemplo concreto. Toma cuatro vértices con los arcos 0→1 (1), 0→2 (2), 2→1 (−2) y 1→3 (1).

Un grafo de cuatro vértices con los arcos 0 a 1 de peso 1, 0 a 2 de peso 2, 2 a 1 de peso menos 2 y 1 a 3 de peso 1. Dijkstra devuelve las distancias 0, 0, 2, 2, mientras que Bellman-Ford devuelve las correctas 0, 0, 2, 1. Una anotación explica que el vértice 1 se expande cuando su etiqueta aún es 1, así que la mejora posterior a 0 llega después de que el vértice 3 ya haya recibido su valor.
Un solo arco negativo. El daño no aparece donde está la arista negativa, sino un paso más adelante, en el vértice 3.

Dijkstra devuelve 0, 0, 2, 2; la respuesta correcta es 0, 0, 2, 1. Vale la pena precisar la sutileza, porque es más interesante que la respuesta habitual: la etiqueta del vértice 1 acaba siendo correcta. Se expande cuando su etiqueta aún es 1, y la mejora posterior a 0 sí se escribe. Pero después nada vuelve a relajar 1 → 3, así que el vértice 3 se queda con 2 en lugar de 1. Un candidato que dice «el valor incorrecto aparece después de la arista negativa, no en ella» demuestra claramente que lo ha probado.

El seguimiento: ¿qué usas en su lugar? Bellman-Ford, que relaja cada arista V - 1 veces en O(VE) y detecta ciclos negativos en la pasada número V. Si necesitas todos los pares y hay aristas negativas pero ningún ciclo negativo, el algoritmo de Johnson repondera con una ejecución de Bellman-Ford para que todos los pesos pasen a ser no negativos y luego ejecuta Dijkstra desde cada vértice. Cormen, Leiserson, Rivest y Stein dan la prueba completa de corrección de la elección voraz.

La trampa. «Basta con sumar una constante a cada peso para que sean positivos.» No funciona, y poder decir por qué en una frase es una señal fuerte: sumar c a cada arista suma c × (number of edges) a una ruta, lo que penaliza las rutas con más aristas y, por tanto, cambia cuál es la más corta.

10. Las respuestas de complejidad

Ten preparadas la cota y la razón, y nombra la estructura de datos. Decir «Dijkstra es O(E log V)» sin nombrar el montículo invita a una pregunta de seguimiento que luego no sabrás responder.

Cola de prioridadTiempoLa razón que debes dar
Montículo binario, borrado perezosoO((V + E) log V)Hasta E entradas insertadas, cada extracción e inserción es logarítmica
Montículo de FibonacciO(E + V log V)decrease-key es O(1) amortizado, así que las aristas no cuestan ningún logaritmo
Array sin ordenarO(V2 + E)Buscar el mínimo en cada ronda; lo mejor en grafos densos
Espacio, cualquier varianteO(V + E)El grafo, más un montículo que nunca supera E entradas

La cota basada en montículo es el resultado de Johnson de 1977; la mejora con montículos de Fibonacci es de Fredman y Tarjan, 1987. El montículo binario en sí es la construcción de Williams de 1964. La variante de Fibonacci es mejor en teoría y casi siempre más lenta en la práctica, porque sus constantes son grandes, y decirlo demuestra criterio en lugar de memoria. Sedgewick y Wayne ofrecen la explicación clara más breve de la alternativa con cola de prioridad indexada, que sí admite decrease-key.

Dos números que conviene tener. En un grafo disperso con V = 105 y E = 5 × 105, la cota del montículo binario sale en unos 10 millones de operaciones frente a unos 2,2 millones con el montículo de Fibonacci: una diferencia real sobre el papel que las constantes borran en la práctica. Y en un grafo denso donde E se acerca a V2, el array simple en O(V2) supera al O(V2 log V) del montículo binario, que es el único caso en que la implementación «ingenua» es la decisión correcta.

11. Errores que suspenden la entrevista

Ordenados por frecuencia; los tres primeros explican la mayoría de las soluciones rechazadas.

El hábito que evita la mayoría de estos errores: antes de escribir, di qué significa la etiqueta y por qué alargar una ruta nunca puede mejorarla. Si no puedes decir esa frase, el problema no es de Dijkstra, y acabas de ahorrarte veinte minutos. McDowell defiende lo mismo para los problemas de entrevista en general.

12. Preguntas frecuentes

¿Por qué Dijkstra no puede manejar pesos negativos?

+

Porque supone que, una vez que un vértice se extrae con la menor etiqueta de la cola, ninguna ruta aún en construcción puede llegar a él más barato. Una arista negativa lo rompe, ya que alargar un camino puede reducir su coste. En el ejemplo de cuatro vértices con arcos 0 a 1 de peso 1, 0 a 2 de peso 2, 2 a 1 de peso menos 2 y 1 a 3 de peso 1, Dijkstra devuelve 0, 0, 2, 2 cuando lo correcto es 0, 0, 2, 1. Usa Bellman-Ford en su lugar.

¿Qué es el borrado perezoso y por qué lo necesito?

+

Un montículo binario no tiene un decrease-key eficiente, así que en lugar de actualizar la entrada de un vértice insertas otra con la mejor etiqueta e ignoras la entrada obsoleta cuando sale. La guarda es una línea: si la distancia extraída supera la mejor actual de ese vértice, sáltala. El coste es que el montículo puede contener hasta E entradas en lugar de V, y por eso la cota es O((V + E) log V).

¿Puedo usar Dijkstra cuando el camino tiene un límite de aristas?

+

No tal cual, porque un vértice ya no tiene una única etiqueta definitiva: su mejor coste cambia según el número de saltos usados. O amplías el estado para que la cola contenga pares de vértice y saltos usados, lo que restaura el invariante, o usas Bellman-Ford y relajas cada arista exactamente K más una veces a partir de una copia de la ronda anterior. La segunda suele ser la respuesta más limpia.

¿Con qué puedo sustituir la suma?

+

Por cualquier cosa monótona, es decir, en la que alargar una ruta nunca reduce su coste. Usar el máximo en lugar de la suma resuelve problemas de cuello de botella o de mínimo esfuerzo. Multiplicar probabilidades entre cero y uno y maximizar también funciona, y equivale a minimizar la suma de logaritmos negativos. La resta es justo lo que falla, por la misma razón por la que se prohíben las aristas negativas.

¿Cómo cuento el número de caminos más cortos?

+

Lleva un segundo array con el número de rutas más cortas hasta cada vértice. Cuando una relajación mejora estrictamente una etiqueta, sustituye ese conteo por el del predecesor. Cuando empata exactamente con la etiqueta existente, suma en su lugar el conteo del predecesor. Solo es correcto porque el conteo de un vértice es definitivo cuando se extrae, y toda relajación se hace desde un vértice extraído.

¿Dijkstra o A* en una entrevista?

+

A* es Dijkstra con una heurística añadida a la prioridad, la formulación de 1968 de Hart, Nilsson y Raphael, y se reduce a Dijkstra cuando esa heurística es cero. Recurre a él solo cuando hay un único destino y una heurística admisible de verdad, como la distancia en línea recta en un mapa o una cuadrícula. Sin ella no hay nada que guíe la búsqueda, y ofrecer A* sobre un grafo abstracto indica que estás reconociendo patrones en lugar de pensar.

¿Qué montículo debería decir que usaría?

+

Un montículo binario con borrado perezoso, que da O((V + E) log V), porque es lo que ofrece toda biblioteca estándar y sus constantes son pequeñas. Menciona que un montículo de Fibonacci mejora la cota a O(E + V log V) pero es más lento en la práctica, y que en un grafo denso un simple recorrido de array en O(V al cuadrado) supera a ambos. Nombrar el compromiso importa más que nombrar el más rápido.

13. Referencias

Los artículos que introdujeron estas técnicas y los libros que las analizan, en orden cronológico.

  1. Bellman, R. (1958). “On a routing problem.” Quarterly of Applied Mathematics, 16(1), 87–90.
  2. Dijkstra, E. W. (1959). “A note on two problems in connexion with graphs.” Numerische Mathematik, 1, 269–271.
  3. Williams, J. W. J. (1964). “Algorithm 232: Heapsort.” Communications of the ACM, 7(6), 347–348.
  4. Hart, P. E., Nilsson, N. J. y Raphael, B. (1968). “A formal basis for the heuristic determination of minimum cost paths.” IEEE Transactions on Systems Science and Cybernetics, 4(2), 100–107.
  5. Johnson, D. B. (1977). “Efficient algorithms for shortest paths in sparse networks.” Journal of the ACM, 24(1), 1–13.
  6. Fredman, M. L. y Tarjan, R. E. (1987). “Fibonacci heaps and their uses in improved network optimization algorithms.” Journal of the ACM, 34(3), 596–615.
  7. Cormen, T. H., Leiserson, C. E., Rivest, R. L. y Stein, C. (2009). Introduction to Algorithms, 3.ª edición, sección 24.3. MIT Press.
  8. Sedgewick, R. y Wayne, K. (2011). Algorithms, 4.ª edición, sección 4.4. Addison-Wesley.
  9. McDowell, G. L. (2015). Cracking the Coding Interview, 6.ª edición. CareerCup.
  10. Skiena, S. S. (2020). The Algorithm Design Manual, 3.ª edición, capítulo 8. Springer.

Mira cómo se supera una etiqueta

Construye el grafo de seis vértices de la sección 3 y recórrelo paso a paso. Ver cómo el vértice 1 recibe la etiqueta 4 y luego mejora a 3 antes de fijarse es la forma más rápida de entender por qué nunca debes comprometerte con una distancia al insertar.

Abrir el visualizador de Dijkstra