import java.util.*;
class Solution {
public long[] solution(long k, long[] room_number) {
Map<Long, Long> map = new HashMap<Long, Long>();
for(int i=0; i<room_number.length; i++){
long start = room_number[i];
while(true){
long jump = map.getOrDefault(start, start-1);
if(jump+1 == start){
break;
}
start = jump+1;
}
map.put(room_number[i] , start);
room_number[i]=start;
}
return room_number;
}
}
while(true){
long jump = map.getOrDefault(start, start-1);
if(jump+1 == start){
break;
}
start = jump+1;
}
문제:
예시:
요청: [1, 1, 1, 1, ..., 1] (1번 방 1000번 요청)
1번째: 1 배정 → map={1:1}
2번째: 1→2 (O(1))
3번째: 1→2→3 (O(2))
4번째: 1→2→3→4 (O(3))
...
1000번째: 1→2→...→1000 (O(999))
총 시간: 1+2+3+...+999 = O(n²)
map.put(room_number[i], start); // 요청 번호 → 배정 번호
문제:
map.getOrDefault(start, start-1)에서 start를 키로 찾는데room_number[i]가 키로 저장되어 있음Path Compression을 활용한 빠른 탐색
map: {방 번호 → 다음 빈 방 번호}
예: 1, 2, 3이 차있으면
map = {1→4, 2→4, 3→4}
4 요청 시:
- map에 4 없음 → 4 배정
- map.put(4, 5)
5 요청 시:
- map에 5 없음 → 5 배정
- map.put(5, 6)
1 다시 요청 시:
- map.get(1) = 4
- map.get(4) = 5
- map.get(5) = 6
- 6 배정!
- Path Compression: map.put(1, 6) ← 최적화!
import java.util.*;
class Solution {
Map<Long, Long> parent = new HashMap<>();
public long[] solution(long k, long[] room_number) {
long[] result = new long[room_number.length];
for (int i = 0; i < room_number.length; i++) {
result[i] = findEmpty(room_number[i]);
}
return result;
}
// 빈 방 찾기 + Path Compression
private long findEmpty(long room) {
// 방이 비어있으면
if (!parent.containsKey(room)) {
parent.put(room, room + 1); // 다음 빈 방은 room+1
return room;
}
// 방이 차있으면 다음 빈 방 찾기
long empty = findEmpty(parent.get(room));
parent.put(room, empty); // Path Compression!
return empty;
}
}
시간복잡도: O(n × α(n)) ≈ O(n) (α는 역 아커만 함수, 거의 상수) 공간복잡도: O(n)
room_number = [1, 3, 4, 1, 3, 1]초기: parent = {}
요청 1:
findEmpty(1)
→ 1 없음 → 1 배정
→ parent = {1→2}
→ result[0] = 1
요청 3:
findEmpty(3)
→ 3 없음 → 3 배정
→ parent = {1→2, 3→4}
→ result[1] = 3
요청 4:
findEmpty(4)
→ 4 없음 → 4 배정
→ parent = {1→2, 3→4, 4→5}
→ result[2] = 4
요청 1:
findEmpty(1)
→ 1 있음 → findEmpty(2)
→ 2 없음 → 2 배정
→ parent = {1→2, 2→3, 3→4, 4→5}
→ Path Compression: parent.put(1, 2)
→ result[3] = 2
요청 3:
findEmpty(3)
→ 3 있음 → findEmpty(4)
→ 4 있음 → findEmpty(5)
→ 5 없음 → 5 배정
→ parent = {1→2, 2→3, 3→5, 4→5, 5→6}
→ Path Compression: parent.put(3, 5)
→ result[4] = 5
요청 1:
findEmpty(1)
→ 1→2 (이미 압축됨)
→ findEmpty(2)
→ 2→3
→ findEmpty(3)
→ 3→5 (이미 압축됨)
→ findEmpty(5)
→ 5→6
→ findEmpty(6)
→ 6 없음 → 6 배정
→ parent = {1→6, 2→6, 3→6, 4→5, 5→6, 6→7}
→ Path Compression!
→ result[5] = 6
최종: [1, 3, 4, 2, 5, 6]
Before (원본 코드):
요청: [1, 1, 1, 1]
배정: [1, 2, 3, 4]
1 → 2 → 3 → 4
체인이 길어짐 → O(n) 탐색
After (Path Compression):
요청: [1, 1, 1, 1]
1차: 1 배정
1 → 2
2차: 2 배정
1 → 2 → 3
압축: 1 → 3
3차: 3 배정
1 → 3 → 4
압축: 1 → 4
3 → 4
4차: 4 배정
1 → 4 → 5
압축: 1 → 5
3 → 5
4 → 5
모든 체인이 최신 빈 방을 직접 가리킴 → O(1) 탐색
| 방법 | 시간복잡도 | 테스트 결과 |
|---|---|---|
| 원본 (순차 탐색) | O(n²) | TLE ⚠️ |
| Union-Find | O(n × α(n)) ≈ O(n) | AC ✔ |
실제 테스트:
n = 200,000
원본: 시간 초과
Union-Find: 약 500ms
집합의 합치기와 찾기를 빠르게
대표적 사용:
- 네트워크 연결성
- 그래프 사이클 감지
- 이 문제: 빈 방 체인 관리
재귀 호출 시 경로 압축
private long findEmpty(long room) {
if (!parent.containsKey(room)) {
parent.put(room, room + 1);
return room;
}
long empty = findEmpty(parent.get(room));
parent.put(room, empty); // ← 이 줄이 핵심!
return empty;
}
효과:
import java.util.*;
class Solution {
Map<Long, Long> next = new HashMap<>();
public long[] solution(long k, long[] room_number) {
long[] result = new long[room_number.length];
for (int i = 0; i < room_number.length; i++) {
result[i] = findEmptyRoom(room_number[i]);
}
return result;
}
/**
* 빈 방 찾기 (Union-Find with Path Compression)
* @param room 원하는 방 번호
* @return 배정된 방 번호
*/
private long findEmptyRoom(long room) {
// 방이 비어있으면 배정
if (!next.containsKey(room)) {
next.put(room, room + 1);
return room;
}
// 방이 차있으면 다음 빈 방 재귀 탐색
long emptyRoom = findEmptyRoom(next.get(room));
// Path Compression: 경로 압축
next.put(room, emptyRoom);
return emptyRoom;
}
}
/**
* 시간복잡도: O(n × α(n)) ≈ O(n)
* 공간복잡도: O(n)
*
* α(n): 역 아커만 함수 (실질적으로 상수)
*/
// 원본
map.put(room_number[i], start); // 요청 → 배정
long jump = map.getOrDefault(start, start-1); // 배정으로 조회?
// 올바른 방법
next.put(room, room + 1); // 현재 방 → 다음 빈 방
long nextRoom = next.get(room); // 현재 방으로 다음 방 조회
// 원본: while로 순차 탐색
while(true){
long jump = map.getOrDefault(start, start-1);
if(jump+1 == start) break;
start = jump+1;
}
// 개선: 재귀 + Path Compression
long empty = findEmpty(next.get(room));
next.put(room, empty); // 압축!
class UnionFind {
Map<Integer, Integer> parent = new HashMap<>();
// Find with Path Compression
int find(int x) {
if (!parent.containsKey(x)) {
parent.put(x, x);
return x;
}
if (parent.get(x) != x) {
parent.put(x, find(parent.get(x))); // 압축
}
return parent.get(x);
}
// Union
void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent.put(rootX, rootY);
}
}
}
✔ 사용해야 할 때:
✘ 불필요한 경우:
없으면: O(n) per query
있으면: O(α(n)) ≈ O(1) per query
n = 100,000일 때:
없으면: 10초
있으면: 0.5초
#UnionFind #PathCompression #DisjointSet #HashMap #프로그래머스 #Level4 #시간최적화 #재귀 #경로압축 #호텔방배정