오늘 lv3 경주로 건설이랑 이걸 풀었는데, 사실 나는 이것보다 lv3의 '경주로 건설'이 더 어려웠다. 이건 뭔가 컴퓨터 시스템 중에 비슷한 과정이 일어나는 부분이 있었던 것 같은데... 자세히 기억이 안난다. 컴구 아님 시프인데 기억이 안나는 거 보니 아마 컴구에서 배웠거나... 스스로 기억을 조작한 듯 하다... 아무튼 그래서 아이디어를 빨리 떠올릴 수 있었고 푸는데 거의 30분도 걸리지 않은 듯 하다....
내가 그래프를 잘 못해서 그런것도 있지만 거의 대부분이 이게 왜 레벨4? 라고 느꼈을 만한 문제였다. 정작 푼 사람은 경주로 건설보다 호텔 방 배정이 더 적어서, 아닐 수도 있지만... 다들 너무 쉽기도 하고 어떤 알고리즘! 이렇게 분명히 쓰이는 알고리즘이 모호해서 안풀었을 수도 있을 것 같다. 중요도를 따지자면 경주로 건설(BFS 변형)이 더 중요한 문제 아닐까...
문제
https://school.programmers.co.kr/learn/courses/30/lessons/64063
[본 문제는 정확성과 효율성 테스트 각각 점수가 있는 문제입니다.]
"스노우타운"에서 호텔을 운영하고 있는 "스카피"는 호텔에 투숙하려는 고객들에게 방을 배정하려 합니다. 호텔에는 방이 총 k개 있으며, 각각의 방은 1번부터 k번까지 번호로 구분하고 있습니다. 처음에는 모든 방이 비어 있으며 "스카피"는 다음과 같은 규칙에 따라 고객에게 방을 배정하려고 합니다.
- 한 번에 한 명씩 신청한 순서대로 방을 배정합니다.
- 고객은 투숙하기 원하는 방 번호를 제출합니다.
- 고객이 원하는 방이 비어 있다면 즉시 배정합니다.
- 고객이 원하는 방이 이미 배정되어 있으면 원하는 방보다 번호가 크면서 비어있는 방 중 가장 번호가 작은 방을 배정합니다.
예를 들어, 방이 총 10개이고, 고객들이 원하는 방 번호가 순서대로 [1, 3, 4, 1, 3, 1] 일 경우 다음([1,3,4,2,5,6])과 같이 방을 배정받게 됩니다. 전체 방 개수 k와 고객들이 원하는 방 번호가 순서대로 들어있는 배열 room_number가 매개변수로 주어질 때, 각 고객에게 배정되는 방 번호를 순서대로 배열에 담아 return 하도록 solution 함수를 완성해주세요.
[제한사항]
- k는 1 이상 10^12 이하인 자연수입니다.
- room_number 배열의 크기는 1 이상 200,000 이하입니다.
- room_number 배열 각 원소들의 값은 1 이상 k 이하인 자연수입니다.
결론부터 말하자면 딕셔너리를 잘 쓰면 되는 문제였다. 다음과 같이 접근했다.
[필요한 것]
1. 어떤 방이 '원하는 방보다 번호가 크면서 비어있는 방 중 가장 작은 방'인지를 계산해야 한다.
2. 빠른 속도로
3. k가 무진장 크니까 occupied/unoccupied 관리하는 변수를 방마다 할당할 수가 없다.
[첫 번째 아이디어]
1. 딕셔너리를 사용해서, 이미 occupy된 방의 번호를 '원호는 방보다 번호가 크면서 비어있는 방 중 가장 작은 방 번호'에 매핑한다.
2. 손님이 원하는 그 방이 비어 있는지 아닌지를 판단해야 한다. -> 딕셔너리가 nil이면 비어 있는 것으로 판단
3. 손님이 i방을 원했고 dict[i]가 nil이면, 손님에게 i방을 주고 dict[i]에 i+1을 넣는다.
4. 손님이 i방을 원했고 dict[i]가 nil이 아니면, dict[dict[i]]가 nil인지 확인한다. nil일 때까지 계속 chaining하며 따라간다. nil이 나오면 그 방을 원한 상황처럼 해결한다. (dict[chaining결과] = 결과 + 1)
[예시]
[1, 3, 4, 1, 3, 1] 의 경우,
1번 손님이 1번 방을 원함 -> dict[1] == nil이므로 1번방을 주고, dict[1] = 2
2번 손님이 3번 방을 원함 -> dict[3] == nil이므로 3번방을 주고, dict[3] = 4
3번 손님이 4번 방을 원함 -> dict[4] == nil이므로 4번방을 주고, dict[4] = 5
4번 손님이 1번 방을 원함 -> dict[1] == 2이고 dict[2]==nil 이므로 2번방을 주고, dict[2] = 3
5번 손님이 3번 방을 원함 -> dict[3] == 4이고 dict[4]==5이고 dict[5]==nil이므로 5번방을 주고, dict[5] = 6
6번 손님이 1번 방을 원함 -> dict[1] == 2이고 dict[2]==3이고 dict[3] == 4이고 dict[4]==5이고 dict[5]==6이고 dict[6]==nil이므로 6번방을 주고, dict[6] = 7
[두 번째 아이디어]
위 과정을 처음에 생각하고 구현해서 넣었는데 정확성은 빨랐지만 효율성에서 시간 초과가 났다. chaining에 한번 걸릴 때마다 걸린 chain을 압축하기로 했다.
[예시]
[1, 3, 4, 1, 3, 1] 의 경우,
1번 손님이 1번 방을 원함 -> dict[1] == nil이므로 1번방을 주고, dict[1] = 2
2번 손님이 3번 방을 원함 -> dict[3] == nil이므로 3번방을 주고, dict[3] = 4
3번 손님이 4번 방을 원함 -> dict[4] == nil이므로 4번방을 주고, dict[4] = 5
4번 손님이 1번 방을 원함 -> dict[1] == 2이고 dict[2]==nil 이므로 2번방을 주고, dict[2] = 3, dict[1] = 3
5번 손님이 3번 방을 원함 -> dict[3] == 4이고 dict[4]==5이고 dict[5]==nil이므로 5번방을 주고, dict[5] = 6, dict[3] = 6, dict[4] = 6
6번 손님이 1번 방을 원함 -> dict[1] == 3이고 dict[3] == 6이고 dict[6]==nil이므로 6번방을 주고, dict[6] = 7, dict[1] = 7, dict[3] = 7
* chaining 과정이 줄었다~~ miss가 많이 나는 경우에 유리하다. 효율성도 통과했다. 효율성 통과는 모든 chain을 압축하지 않아도, 일부 chain만 압축해도 나왔다. 아래에 코드
import Foundation
func solution(_ k:Int64, _ room_number:[Int64]) -> [Int64] {
var dict: [Int64:Int64] = [:]
var answer : [Int64] = []
for i in room_number {
var num = dict[i]
if num == nil {
answer.append(i)
dict[i] = i + 1
}
else {
var revise = [i, num!]
while(dict[num!] != nil) {
num = dict[num!]
revise.append(num!)
}
for revise in revise {
dict[revise] = num! + 1
}
answer.append(num!)
}
}
return answer
}
[실제 해설]
카카오에서 공개한 실제 해설을 보면, 좀 더 개념적인 차원에서 설명했다 뿐이지 거의 유사하게 나와 있다. 노드라는 개념을 써서 설명했는데 괜찮은 생각 방식인 것 같다. 난 딕셔너리부터 떠올렸는데 앞으로는 부모-자식 노드 구조라는 개념을 사용해서 생각해 보는 것도 좋을 듯. 가장 부모 노드를 찾아가는 과정이 내가 chaining이라고 생각한 부분이고, hashmap 등을 사용해 메모리를 절약하라는 부분도 충족! 오랜만에 마음에 드는 풀이다.
더 나은 풀이가 있으면 알려주시라,,
'PS문제들' 카테고리의 다른 글
[백준] 14500: 테트로미노 (with C++) (0) | 2022.08.23 |
---|---|
[백준] 14499: 주사위 굴리기(with C++) (0) | 2022.08.22 |
[백준] 12100: 2048(Easy) (with C++) (0) | 2022.08.22 |
[백준] 13460: 구슬 탐험 (with C++) (0) | 2022.08.21 |
[프로그래머스] lv3. 경주로 건설(with Swift) (0) | 2022.08.19 |
댓글