LeetCode/NeetCode
[Trees: Union Find] 721. Accounts Merge ★★★★★★★★
hyunkookim
2025. 4. 7. 10:23
이 문제는 Union-Find(Disjoint Set) 을 사용하는 대표적인 그래프 문제입니다.
각 이메일을 노드로 보고, 같은 사람의 이메일끼리 연결(merge) 해주는 방식입니다.
✅ 문제 핵심 정리
- accounts[i][0]: 이름 (예: "John")
- accounts[i][1:]: 이메일 주소들
- 공통 이메일이 있으면 같은 사람 → 이메일들을 하나의 집합으로 merge
- 결과는 [이름, 정렬된 이메일 목록] 형식으로 반환
✅ 전략
- Union-Find 구조 생성
- 이메일 간 연결을 Union-Find로 묶기
- 각 이메일의 루트를 기준으로 그룹핑
- 이름을 이메일과 매핑해 최종 결과 구성
✅ 전체 코드 (풀 구현 + 설명 주석)
class Solution:
def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]:
# 1. Union-Find 초기화
# 각 계정(리스트 index)을 자기 자신이 부모로 시작
par = [i for i in range(len(accounts) + 1)] # 부모 노드 배열
rank = [1] * (len(accounts) + 1) # 트리의 깊이 또는 크기 추정치
# 2. Find 함수 (경로 압축 적용)
def find(n):
p = par[n] # 현재 노드의 부모
# 루트 노드가 아니면 계속 따라 올라감
while p != par[p]:
par[p] = par[par[p]] # 중간 부모를 루트의 손자 노드로 압축
p = par[p] # 루트로 향하는 방향으로 계속 이동
return p # 루트 노드 반환
# 3. Union 함수 (Rank 기반으로 병합)
def union(n1, n2):
p1, p2 = find(n1), find(n2) # 두 노드의 루트를 찾음
if p1 == p2:
# 이미 같은 집합이면 병합하지 않음 (사이클 방지)
return False
# 랭크가 더 큰 루트 쪽으로 작은 쪽을 병합
if rank[p1] > rank[p2]:
par[p2] = p1 # p2의 루트를 p1으로
rank[p1] += rank[p2] # p1의 랭크에 p2의 랭크를 더함
else:
par[p1] = p2 # p1의 루트를 p2로
rank[p2] += rank[p1] # p2의 랭크에 p1의 랭크를 더함
return True # 새로운 병합이 일어남을 표시
# 4. 이메일 → 계정 index 매핑 / 이메일 → 이름 매핑
email_to_id = {} # 각 이메일이 속한 최초의 계정 index 저장
email_to_name = {} # 각 이메일의 주인 이름 저장
for i, acc in enumerate(accounts): # 계정 리스트 순회
name = acc[0] # 첫 번째 항목은 이름
for email in acc[1:]: # 이메일들만 순회
email_to_name[email] = name # 이메일의 이름 저장
if email in email_to_id:
# 이미 등장한 이메일이면, 현재 계정 i와 기존 계정 union
union(i, email_to_id[email])
else:
# 처음 본 이메일이면 현재 계정 index를 저장
email_to_id[email] = i
# 5. 루트 기준으로 이메일들을 묶어 그룹핑
from collections import defaultdict
groups = defaultdict(list) # 루트 index → 이메일 리스트
for email, idx in email_to_id.items(): # 모든 이메일과 계정 index 순회
root_id = find(idx) # 해당 계정의 대표 루트 찾기
groups[root_id].append(email) # 루트 기준으로 이메일을 묶음
# 6. 최종 결과 구성
res = []
for idx, emails in groups.items(): # 루트 index별로 그룹 순회
name = email_to_name[emails[0]] # 아무 이메일에서 이름 가져오기
res.append([name] + sorted(emails)) # 이름 + 정렬된 이메일 리스트
return res # 병합된 결과 리스트 반환
✅ 예시 설명
입력:
["John","johnsmith@mail.com","john_newyork@mail.com"], ["John","johnsmith@mail.com","john00@mail.com"]
- 두 리스트는 "johnsmith@mail.com"을 공유하므로 → 같은 사람
- Union-Find로 같은 집합으로 묶음
- 결과:
["John","john00@mail.com","john_newyork@mail.com","johnsmith@mail.com"]
✅ 시간 복잡도
- 초기화 + union: O(N·α(N))
- 그룹핑 및 정렬: O(N·logN)
- 전체적으로 매우 빠름