プリム法で 全部の 倉庫を 道で つなぐいちばん安い 費用を求める
平面に
海外で定番の問題リスト 4 本には入っていません
問題
碁盤の[x, y] のpoints で[x1, y1] と [x2, y2] を|x1 - x2| + |y1 - y2| です。
何本かの
- 道は、
倉庫と 倉庫を 直接結ぶ 物だけを作れます pointsの長さは 1 以上 1000 以下、 xとyはどれも -1000000 以上 1000000 以下の 整数で、 同じ 場所の 倉庫は ありません - 倉庫が 1 つなら、
道は 要らないので 0 を返します
入力と出力の例
例 1
[0, 0] と [1, 3] を 4、[0, 0] と [4, 1] を 5、[4, 1] と [5, 5] を 5 で
例 2
一列に
例 3
倉庫が 1 つです。
面接での聞かれ方
面接では、
- 「平面の
点を 全部 つなぐ 最小の 費用を 返してください。 2 点の 費用は マンハッタン距離です」 - 書き終えると
「クラスカル法と プリム法の どちらを 選びましたか? この 問題ではなぜ そちらですか?」と 聞かれます - 「全部の
点の 組が 辺に なる とき、 辺の 数は いくつですか?」と、 辺の 多い グラフでの 計算量を 聞かれます
道が
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、
関わる学び
グラフ探索は、
まず 考える手順
段 1
倉庫を
段 2
プリム法を
段 3
まだの
段 4
始めは 0 番の
解答
Python
Python
def min_cost_connect(points):
n = len(points)
# まとまりまでの安い費用
best = gaps(points, 0)
done = [False] * n
done[0] = True
total = 0
for _ in range(n - 1):
v = pick(best, done)
done[v] = True
total += best[v]
row = gaps(points, v)
for u in range(n):
if row[u] < best[u]:
best[u] = row[u]
return total
def gaps(points, v):
# v からほかの倉庫への費用
x, y = points[v]
out = []
for p, q in points:
dx = abs(x - p)
dy = abs(y - q)
out.append(dx + dy)
return out
def pick(best, done):
# まだの中で最も安い倉庫
v = -1
for i in range(len(best)):
if done[i]:
continue
if v == -1:
v = i
elif best[i] < best[v]:
v = i
return vJavaScript
JavaScript
function minCostConnect(
points,
) {
const n = points.length;
// まとまりまでの安い費用
const best = points.map(
(p) => cost(points[0], p));
const done = best.map(
() => false);
done[0] = true;
let total = 0;
for (let k = 1; k < n; k++) {
const v = pick(best, done);
done[v] = true;
total += best[v];
const pv = points[v];
points.forEach((p, u) => {
const c = cost(pv, p);
if (c < best[u]) {
best[u] = c;
}
});
}
return total;
}
function cost(a, b) {
return Math.abs(a[0] - b[0]) +
Math.abs(a[1] - b[1]);
}
// まだの中で最も安い倉庫
function pick(best, done) {
let v = -1;
best.forEach((b, i) => {
if (done[i]) {
return;
}
if (v === -1 ||
b < best[v]) {
v = i;
}
});
return v;
}Go
Go
func minCostConnect(
points [][]int,
) int {
n := len(points)
cost := func(a, b int) int {
p, q := points[a], points[b]
return abs(p[0]-q[0]) +
abs(p[1]-q[1])
}
// まとまりまでの安い費用
best := make([]int, n)
for i := range best {
best[i] = cost(0, i)
}
done := make([]bool, n)
done[0] = true
total := 0
for k := 1; k < n; k++ {
v := -1
for i := 0; i < n; i++ {
if done[i] {
continue
}
if v == -1 ||
best[i] < best[v] {
v = i
}
}
done[v] = true
total += best[v]
for u := 0; u < n; u++ {
best[u] = min(
best[u], cost(v, u))
}
}
return total
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}つないだ倉庫の
計算量
- 時間は O(n²) です。
n - 1 回の 繰り返しの それぞれで、 いちばん安い 倉庫を 選ぶのと、 値を 書き換えるのが O(n) です - 使う
記憶は O(n) です。 倉庫ごとの 値と、 つない だか どうかの印です
よく ある間違い
- 全部の
道を 並べ替える。 約 50 万本を 並べ替える クラスカル法でも 解けますが、 O(n² log n) の 時間と O(n²) の 記憶を 使います。 点どうしの 全部が 線に なる 問題では、 並びで 持つプリム法が 速く、 記憶も 少なく済みます - いちばん近い
倉庫どうしを 順につなぐ。 「いま見ている 倉庫から いちばん近い 倉庫」 へ進むと、 まとまり 全体から 見て 安い道を 見落とし、 合計が 高くなります。 比べるのは、 まとまり 全体からの費用です - まっすぐの
距離 (平方 根) を使う。 費用は 碁盤の 目に 沿った 距離です。 例 1 で [0, 0]と[1, 3]は 4 です - 倉庫が 1 つの
ときに困る。 繰り返しが 0 回に なるように 書けば、 そのまま 0 を返せます
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「全部の
点を つなぐ 最小の 費用なので、 最小全域木です。 どの 2 点も 線に なり線が 多いので、 並びで 持つプリム法にします。 0 番から 始め、 まだの 点ごとに まとまりまでの いちばん安い 費用を 持ちます。 毎回いちばん安い点を 足し、 その 点からの 費用で 値を 小さく 書き換えます。 時間は O(n²)、 記憶は O(n) です。」
書き終えたら、
「例 1 で、
0 番から 始めると、 ほかの 値は 4、 5、 10 です。 いちばん安い [1, 3] を 4 で 足すと、 [4, 1] は 5 のまま、 [5, 5] は 6 に 下がります。 [4, 1] を 5 で 足すと [5, 5] は 5 に 下がり、 最後に それを 足して、 4 + 5 + 5 で 14 です。」