ダイクストラ法で 合図が 全部の 中継所に 届くまでの 時間を求める
一方
海外で定番の問題リスト 4 本には入っていません
問題
山あいに n か所のlinks をlinks[i] = [a, b, t] は、
中継所 src から
- それぞれの
中継所に 届く 時刻は、 届き方の うちいちばん早い 時刻です。 全部に 届くまでの 秒数は、 その 中の いちばん遅い時刻です - n は 1 以上 100 以下、
linksの長さは 0 以上 6000 以下、 t は 0 以上 100 以下の整数です - 同じ a から
同じ b への 回線は 2 本ありません。 自分へ 戻る 回線も ありません
入力と出力の例
例 1
1 から 2 へ
例 2
回線は 2 から 1 への
例 3
中継所は 1 か所で、
面接での聞かれ方
面接では、
- 「重み
付きの 有向グラフで、 ある 節から 全部の 節へ 信号が 届くまでの 時間を 返してください。 届かない 節が あれば -1 です」 - 幅優先探索 (BFS) で
書くと 「辺の 重さが 違う とき、 BFS の 順で 正しいですか?」と 聞かれます - 「重さが
負の 辺が あったら、 ダイクストラ法は 使えますか?」と 広げて 聞かれる ことがあります
回線が
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、
関わる学び
グラフ探索は、
まず 考える手順
段 1
求めるのは、src から
段 2
回線ごとに
段 3
ダイクストラ法を
段 4
「いちばん早い候補」を(時刻, 中継所) を
解答
Python
Python
from heapq import heappop
from heapq import heappush
def network_delay(
links, n, src
):
# 中継所ごとの出ている回線
adj = [[]]
for _ in range(n):
adj.append([])
for a, b, t in links:
adj[a].append((b, t))
done = {}
h = [(0, src)]
while h:
d, v = heappop(h)
if v in done:
continue
done[v] = d
for u, t in adj[v]:
nd = d + t
heappush(h, (nd, u))
if len(done) < n:
return -1
return max(done.values())JavaScript
JavaScript
function networkDelay(
links, n, src,
) {
// 中継所ごとの出ている回線
const adj = [];
for (let v = 0; v <= n; v++) {
adj.push([]);
}
links.forEach(([a, b, t]) => {
adj[a].push([b, t]);
});
const done = new Map();
const h = [[0, src]];
while (h.length) {
const [d, v] = pop(h);
if (done.has(v)) {
continue;
}
done.set(v, d);
for (const e of adj[v]) {
const [u, t] = e;
push(h, [d + t, u]);
}
}
if (done.size < n) {
return -1;
}
const ds = [...done.values()];
return Math.max(...ds);
}
// [時刻, 中継所] の小さい順のヒープ
function push(h, x) {
h.push(x);
let i = h.length - 1;
while (i > 0) {
const p = (i - 1) >> 1;
if (h[p][0] <= h[i][0]) {
return;
}
[h[p], h[i]] = [h[i], h[p]];
i = p;
}
}
function pop(h) {
const top = h[0];
const last = h.pop();
if (h.length === 0) {
return top;
}
h[0] = last;
const n = h.length;
let i = 0;
while (true) {
let s = i;
const l = 2 * i + 1;
const r = l + 1;
if (l < n &&
h[l][0] < h[s][0]) {
s = l;
}
if (r < n &&
h[r][0] < h[s][0]) {
s = r;
}
if (s === i) {
return top;
}
[h[s], h[i]] = [h[i], h[s]];
i = s;
}
}Go
Go
import "container/heap"
// {時刻, 中継所} の小さい順のヒープ
type pq [][2]int
func (h pq) Len() int {
return len(h)
}
func (h pq) Less(
i, j int,
) bool {
return h[i][0] < h[j][0]
}
func (h pq) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h *pq) Push(x any) {
*h = append(*h, x.([2]int))
}
func (h *pq) Pop() any {
old := *h
x := old[len(old)-1]
*h = old[:len(old)-1]
return x
}
func networkDelay(
links [][]int, n, src int,
) int {
adj := map[int][][2]int{}
for _, e := range links {
a := e[0]
adj[a] = append(adj[a],
[2]int{e[1], e[2]})
}
done := map[int]int{}
h := &pq{{0, src}}
for h.Len() > 0 {
top := heap.Pop(h).([2]int)
d, v := top[0], top[1]
if _, ok := done[v]; ok {
continue
}
done[v] = d
for _, e := range adj[v] {
heap.Push(h,
[2]int{d + e[1], e[0]})
}
}
if len(done) < n {
return -1
}
best := 0
for _, d := range done {
best = max(best, d)
}
return best
}done は、done に
計算量
- 時間は O(E log E) です。
E は 回線の 数です。 どの 回線も 候補を 1 つヒープへ 入れ、 出し入れが それぞれ O(log E) です - 使う
記憶は O(n + E) です。 回線の 並びを 中継所ごとに 分けた 表と、 ヒープに 入る候補です
よく ある間違い
- 幅優先探索の
順で 確定する。 回線の 時間が 違うと、 歩数の 少ない 道が 早いとは 限りません。 例 1 で 2 への 時刻を 5 に してしまいます - 確定した
中継所を、 取り出した ときに 捨てない。 古い 候補で もう 一度 回線を 広げ、 遅い 時刻で 上書きする ことがあります - 届かない
中継所を 見落とす。 確定した 数が n に 足りなければ -1 です。 届いた 所の いちばん遅い 時刻だけを 返すと、 例 2 で 0 を 返してしまいます - 回線を
両向きにする。 回線は 一方 通行です。 両向きに すると、 例 2 で 2 に 届いてしまいます
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「回線の
時間が 違う 最短経路なので、 ダイクストラ法を 使います。 時刻の 小さい 順の ヒープに src を 0 秒で 入れ、 取り出した 中継所が まだなら 時刻を 確定し、 出ている 回線の 先へ 確定した 時刻 + 回線の 時間を 候補として 入れます。 全部が 確定したら いちばん遅い 時刻を、 足りなければ -1 を 返します。 時間は O(E log E) です。」
書き終えたら、
「例 1 では、
1 を 0 秒で 確定し、 2 に 5 秒、 3 に 1 秒の 候補を 入れます。 いちばん早い 3 を 1 秒で 確定し、 2 に 3 秒、 4 に 5 秒の 候補を 入れます。 次は 2 の 3 秒で 確定、 その あと 4 の 5 秒と 2 の 5 秒が 出ますが、 2 は もう 確定しているので 捨てます。 いちばん遅い 5 秒を 返します。」