Union-Find 사용 시 주의점에 대해 다시 생각해보는 계기가 되었기에 기록을 남겨둔다.
https://school.programmers.co.kr/learn/courses/30/lessons/43162
위 문제를 풀면서 놓치고 있던 점을 알게됐다.
유니온 파인드는 리프노드를 먼저 Find하고 그 후에 중간위치 노드를 Find하면 리프노드는 Parent값이 갱신되지 않는다.
물론 중간위치 노드를 먼저 Find한 후 리프노드를 Find하면 다 갱신되겠지만 항상 이렇지는 않으므로 마지막에 모든 Parent[]에 대해 Find를 수행해줘야 최종적으로 값이 갱신된다.
이게 보통 크루스칼 알고리즘에서 Union-Find를 많이 쓰다보니까 최소신장트리 특성상 간선이 V - 1개 선택되면 더는 진행할 필요가 없어서 거기서 break를 해왔었고 그래서 Parent가 최신상태로 갱신되지 않아도 거리만 맞게 계산되면 그만이었기에 생각하지 않았던 부분이다.
하지만 Union-Find로 부분집합이 몇개로 나누어져있는지 파악한다면 최신상태로 Parent를 업데이트해야하므로 인접배열을 모두 Union한 후 마지막에 한번 더 Find를 해줘야 한다.
아래 코드에서는 set에 넣으면서 Find해줬다.
정답 코드)
#include <bits/stdc++.h>
using namespace std;
vector<int> Parent;
int Find(int x)
{
if (Parent[x] == x) return x;
return Parent[x] = Find(Parent[x]);
}
void Union(int x, int y)
{
x = Find(x);
y = Find(y);
if (x < y) Parent[y] = x;
else if (x > y) Parent[x] = y;
}
int solution(int n, vector<vector<int>> computers) {
Parent.resize(n);
for (int i = 0; i < n; i++) {
Parent[i] = i;
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j) continue;
if (computers[i][j]) Union(i, j);
}
}
set<int> s;
for (int p : Parent) {
s.insert(Find(p));
}
return s.size();
}