素集合 (Union-Find) で 木に 1 本だけ足された 余分な 道を見つける
輪の
海外で定番の問題リスト 4 本には入っていません
問題
島に n 個の
道のedges をedges[i] = [a, b] は、
- そのような
道が いくつか ある ときは、 edgesでいちばん後ろに ある 道を返します - 道は
edgesに書いてある 通りの 向き ( [a, b]の a と b の順) で返します - n は 3 以上 1000 以下で、
edgesの長さは n です。 同じ 2 つの 村を 結ぶ 道は 2 本ありません。 自分の 村へ 戻る 道も ありません
入力と出力の例
例 1
1、[3, 1] を
例 2
輪は 2、[4, 2] を
例 3
輪は 1、[4, 1] はedges で[4, 5] は
面接での聞かれ方
面接では、
- 「木に
辺を 1 本足した グラフが 辺の 並びで 渡されます。 外すと 木に 戻る 辺を 返してください。 答えが 複数あれば、 入力で 最後の 物を 返します」 - 書き終えると
「find と union は、 それぞれどれだけかかりますか?」と、 素集合の 工夫 (経路の 圧縮) を 聞かれます - 「辺に向きがあったら?」と
広げて 聞かれる こともあります (別の、 もっと 難しい 問題に なります)
答えが
ヒント
まだ開いていません
付箋は 4 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3
ヒント 4答えに近い
アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、
関わる学び
グラフ探索は、
まず 考える手順
段 1
道を 1 本ず
段 2
道をedges で
段 3
「2 つの
段 4
根を
解答
Python
Python
def redundant_edge(edges):
n = len(edges)
p = list(range(n + 1))
def find(x):
while p[x] != x:
p[x] = p[p[x]]
x = p[x]
return x
for a, b in edges:
ra = find(a)
rb = find(b)
if ra == rb:
return [a, b]
p[ra] = rb
return []JavaScript
JavaScript
function redundantEdge(edges) {
const n = edges.length;
const p = [];
for (let i = 0; i <= n; i++) {
p.push(i);
}
const find = (x) => {
while (p[x] !== x) {
p[x] = p[p[x]];
x = p[x];
}
return x;
};
for (const [a, b] of edges) {
const ra = find(a);
const rb = find(b);
if (ra === rb) {
return [a, b];
}
p[ra] = rb;
}
return [];
}Go
Go
func redundantEdge(
edges [][]int,
) []int {
p := make([]int, len(edges)+1)
for i := range p {
p[i] = i
}
find := func(x int) int {
for p[x] != x {
p[x] = p[p[x]]
x = p[x]
}
return x
}
for _, e := range edges {
ra := find(e[0])
rb := find(e[1])
if ra == rb {
return e
}
p[ra] = rb
}
return []int{}
}村のreturn [] には、
計算量
- 時間は O(n α(n)) です。
α は、 n が どれだけ 大きくても 5 を 超えない、 とても 伸びの 遅い 関数で、 ほぼ O(n) と 見てかまいません。 経路の 圧縮だけでも、 実際には ほぼこの 速さで動きます - 使う
記憶は O(n) です。 村ごとの 親の並びです
よく ある間違い
- 輪の
道を 全部探してから選ぶ。 輪を 作った 最初の 道が、 輪の 道の うち edgesでいちばん後ろの 道です。 順に 足して 最初に 見つかった 道を、 そのまま返せます - 根ではなく
村その ものをつなぐ。 p[a] = bとすると、 a の まとまりの ほかの 村が 置き去りに なり、 つながりを 見誤ります。 つなぐのは 2 つの根です - 親の
並びを n 個で作る。 番号が 1 から n なので、 n 番の 村が 並びの 外になります - 道の
向きを 入れ替えて返す。 [3, 1]を[1, 3]で返すと、 決まりと 違います。 受け取った 道を そのまま返します
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「道を
前から 順に 素集合で つないで いきます。 足す前に、 両端の 根が 同じなら、 その 道が 輪を 閉じた道です。 輪は 1 つで、 輪を 閉じるのは 輪の 道の うち入力で いちばん後ろの 物なので、 最初に 見つかった 道を そのまま 返します。 経路の 圧縮を 入れて、 時間は ほぼ O(n) です。」
書き終えたら、
「例 2 では、
2 と 3、 4 と 5、 3 と 4、 1 と 2 を つなぎ、 ここで 1 から 5 が 1 つの まとまりです。 次の 4 と 2 は、 根が 同じなので、 もう つながっています。 この [4, 2] を 返します。」