
프로그래머스: 호텔 방 배정
https://school.programmers.co.kr/learn/courses/30/lessons/64063
문제 설명
한 고객이 원하는 방 번호가 이미 배정되어 있다면,
해당 방 번호보다 큰 방 중 비어 있는 가장 가까운(작은) 번호의 방을 찾아 배정해야 한다.
각 고객은 한 번씩만 방문하고, 방의 개수는 매우 크기 때문에(최대 10¹²) 효율적인 탐색이 필요하다.
1차 시도 (오답)
보자마자 유니온-파인드 방식을 사용하려 했지만, k값이 10의 12제곱으로 매우 커 배열로 관리하기는 힘들어 보였다.
그래서 HashMap을 사용해 필요한 방 번호만 활성화해 관리하고자 했다.
import java.util.*;
class Solution {
public long[] solution(long k, long[] room_number) {
int size = room_number.length;
long[] answer = new long[size];
Map<Long, Long> rooms = new HashMap<>();
Map<Long, ArrayList<Long>> waitings = new HashMap<>();
int idx = 0;
for (long num : room_number) {
if (rooms.get(num) == null) { // 미배정
answer[idx++] = num;
long nextRoom = num + 1;
if (rooms.get(nextRoom) == null)
rooms.put(num, nextRoom);
else
rooms.put(num, rooms.get(nextRoom));
if (waitings.get(nextRoom) == null)
waitings.put(nextRoom, new ArrayList<>());
waitings.get(nextRoom).add(num);
if (waitings.get(num) != null) {
for (long w : waitings.get(num)) {
rooms.put(w, nextRoom);
waitings.get(nextRoom).add(w);
}
waitings.get(num).clear();
}
continue;
}
answer[idx++] = rooms.get(num);
long nextRoom = rooms.get(num) + 1;
if (rooms.get(nextRoom) == null)
rooms.put(num, nextRoom);
else
rooms.put(num, rooms.get(nextRoom));
}
return answer;
}
}
- rooms: key=방번호, value=가장 가까운 다음 방번호
- waitings: key = 방번호, value= 해당 방을 가리키고 있는 다른 방들의 리스트
방 배정 관계를 연결 리스트처럼 갱신하는 접근이었다.
배정된 방 번호를 key로, 다음 후보 방 번호를 value로 두면 된다 생각했다.
그러나 실제로 돌려보니 중복 배정이 발생하거나, 방 번호가 꼬이는 현상이 있었다.
- rooms.put(num, nextRoom)만 갱신 → 요청 번호 기준으로만 갱신됨
- 실제로 배정된 방 번호가 아니라 “희망 번호”를 기준으로 갱신해서 체인 구조가 끊기거나 잘못 연결됨.
- 과도한 복잡성
- waitings 리스트를 매번 돌며 일괄 갱신하다보니 너무 많은 갱신이 필요해 최악의 경우 O(N²)로 커졌고,
코드를 작성하는 것도 너무 복잡했다.
- waitings 리스트를 매번 돌며 일괄 갱신하다보니 너무 많은 갱신이 필요해 최악의 경우 O(N²)로 커졌고,
2차 시도 (성공)
import java.util.*;
class Solution {
public long[] solution(long k, long[] room_number) {
int size = room_number.length;
long[] answer = new long[size];
int idx = 0;
Map<Long, Long> p = new HashMap<Long, Long>();
for (long r : room_number) {
long assigned = find(r,p);
answer[idx++] = assigned;
p.put(assigned, find(assigned+1,p));
}
return answer;
}
public long find(long x, Map<Long, Long> p) {
if (p.get(x)== null) {
return x;
}
long parent = find(p.get(x), p);
p.put(x, parent);
return parent;
}
}
union-find 방식이 맞았다.
parent배열을 관리할 때, 배열로 관리하지 않고 Map으로 관리하면 되었다.
나는 find메서드를 호출할 때 최대 20만개의 방이 있을 수 있으니 스택오버플로우가 발생할거라 생각했다.
하지만 실제로 구현해보니 발생하지 않았다. 테스트케이스가 그렇게까지 극한으로 가진 않은 모양이다.
만약 테스트케이스가 [1, 1, 1, 1, .....] 이렇게 20만 개짜리 배열이 들어온다면,
1 > 2> 3> 4> .... > 200000
까지 경로 압출 없이 스택을 타고 들어가 스택오버플로우가 발생할 수 있을 것이다.
이를 해결하려면, 스택 대신 while문으로 구현해야할 것 같다.
회고
처음엔 단순히 HashMap으로 방 배정 상태를 추적하면 될 줄 알았다.
하지만 이 문제의 본질은 다음 비어있는 노드를 효율적으로 찾는 연결 관계를 만드는 것이다.
불필요한 리스트 관리보다 경로 압축 기반 union-find탐색이 훨씬 명확하고 빠르다.
결론은 너무 복잡하게 생각하지 말자..