문제 설명
XX산은 n
개의 지점으로 이루어져 있습니다. 각 지점은 1부터 n
까지 번호가 붙어있으며, 출입구, 쉼터, 혹은 산봉우리입니다. 각 지점은 양방향 통행이 가능한 등산로로 연결되어 있으며, 서로 다른 지점을 이동할 때 이 등산로를 이용해야 합니다. 이때, 등산로별로 이동하는데 일정 시간이 소요됩니다.
등산코스는 방문할 지점 번호들을 순서대로 나열하여 표현할 수 있습니다.
예를 들어 1-2-3-2-1
으로 표현하는 등산코스는 1번지점에서 출발하여 2번, 3번, 2번, 1번 지점을 순서대로 방문한다는 뜻입니다.
등산코스를 따라 이동하는 중 쉼터 혹은 산봉우리를 방문할 때마다 휴식을 취할 수 있으며, 휴식 없이 이동해야 하는 시간 중 가장 긴 시간을 해당 등산코스의 intensity
라고 부르기로 합니다.
당신은 XX산의 출입구 중 한 곳에서 출발하여 산봉우리 중 한 곳만 방문한 뒤 다시 원래의 출입구로 돌아오는 등산코스를 정하려고 합니다. 다시 말해, 등산코스에서 출입구는 처음과 끝에 한 번씩, 산봉우리는 한 번만 포함되어야 합니다.
당신은 이러한 규칙을 지키면서 intensity
가 최소가 되도록 등산코스를 정하려고 합니다.
다음은 XX산의 지점과 등산로를 그림으로 표현한 예시입니다.
위 그림에서 원에 적힌 숫자는 지점의 번호를 나타내며, 1, 3번 지점에 출입구, 5번 지점에 산봉우리가 있습니다. 각 선분은 등산로를 나타내며, 각 선분에 적힌 수는 이동 시간을 나타냅니다. 예를 들어 1번 지점에서 2번 지점으로 이동할 때는 3시간이 소요됩니다.
위의 예시에서 1-2-5-4-3
과 같은 등산코스는 처음 출발한 원래의 출입구로 돌아오지 않기 때문에 잘못된 등산코스입니다. 또한 1-2-5-6-4-3-2-1
과 같은 등산코스는 코스의 처음과 끝 외에 3번 출입구를 방문하기 때문에 잘못된 등산코스입니다.
등산코스를 3-2-5-4-3
과 같이 정했을 때의 이동경로를 그림으로 나타내면 아래와 같습니다.
이때, 휴식 없이 이동해야 하는 시간 중 가장 긴 시간은 5시간입니다. 따라서 이 등산코스의 intensity
는 5입니다.
등산코스를 1-2-4-5-6-4-2-1
과 같이 정했을 때의 이동경로를 그림으로 나타내면 아래와 같습니다.
이때, 휴식 없이 이동해야 하는 시간 중 가장 긴 시간은 3시간입니다. 따라서 이 등산코스의 intensity는 3이며, 이 보다 intensity가 낮은 등산코스는 없습니다.
XX산의 지점 수 n
, 각 등산로의 정보를 담은 2차원 정수 배열 paths
, 출입구들의 번호가 담긴 정수 배열 gates
, 산봉우리들의 번호가 담긴 정수 배열 summits
가 매개변수로 주어집니다. 이때, intensity
가 최소가 되는 등산코스에 포함된 산봉우리 번호와 intensity
의 최솟값을 차례대로 정수 배열에 담아 return 하도록 solution 함수를 완성해주세요. intensity
가 최소가 되는 등산코스가 여러 개라면 그중 산봉우리의 번호가 가장 낮은 등산코스를 선택합니다.
제한사항
- 2 ≤
n
≤ 50,000 n
- 1 ≤paths의
길이 ≤ 200,000paths
의 원소는[i, j, w]
형태입니다.i
번 지점과j
번 지점을 연결하는 등산로가 있다는 뜻입니다.w
는 두 지점 사이를 이동하는 데 걸리는 시간입니다.- 1 ≤
i
<j
≤ n - 1 ≤
w
≤ 10,000,000 - 서로 다른 두 지점을 직접 연결하는 등산로는 최대 1개입니다.
- 1 ≤
gates
의 길이 ≤n
- 1 ≤
gates
의 원소 ≤n
gates
의 원소는 해당 지점이 출입구임을 나타냅니다.
- 1 ≤
- 1 ≤
summits
의 길이 ≤n
- 1 ≤
summits
의 원소 ≤n
summits
의 원소는 해당 지점이 산봉우리임을 나타냅니다.
- 1 ≤
- 출입구이면서 동시에 산봉우리인 지점은 없습니다.
gates
와summits
에 등장하지 않은 지점은 모두 쉼터입니다.- 임의의 두 지점 사이에 이동 가능한 경로가 항상 존재합니다.
- return 하는 배열은
[산봉우리의 번호, intensity의 최솟값]
순서여야 합니다.
풀이
최근 카카오 코딩테스트에서 유행을 타고있는 기존 그래프 문제 변형하기 유형입니다. 이러한 문제들의 특징은 어디서 많이 본 듯 하면서 생소한 조건이 하나씩 붙어있다는 점입니다.
거두절미하고 이번 문제에서의 키 포인트는 다음과 같이 3가지 입니다.
- 노드 간의 거리는
edge
들의 누적값이 아닌 최댓값, 즉intensity
- (
gate
에서summit
까지의 편도 거리) <= (gate
에서summit
까지의 왕복 거리)- 매
gate
마다 독립적인summit
까지의 거리를 저장할 필요가 없다
이 3가지 포인트들을 깨닫는 과정을 살펴봅시다.
첫 번째 키 포인트의 경우, 별도의 사고가 필요없이 문제에서 바로 주어지는 조건입니다. 이 조건을 통해서 전반적인 구현 방법을 떠올릴 수 있어야 하는데, 이 경우 다익스트라가 떠올라야 합니다. 매 edge
의 가중치
가 다르고, 최솟값
을 찾아야 하므로 다익스트라가 알맞습니다. 다만 일반적인 다익스트라와는 다르게, 가중치
의 갱신을 누적합이 아닌 최댓값 비교를 이용해야 합니다.
두 번째 키 포인트의 경우, 문제에서 주어지지 않고 직접 깨달아야 하는 부분이었습니다. 이는 문제를 조금만 째려보면 직관적으로 알 수 있는 부분인데, 하나의 출입구에서 산봉우리를 찍고 내려오고 있다고 가정합시다. 이 때 내려오는 길의 intensity
가 올라온 길에 비해 아무리 작다고 해도, 이 문제에서 노드 간의 거리는 edge
의 최댓값이기 때문에 전체 경로의 intensity
는 바뀌지 않습니다. 따라서 두 번째 키 포인트가 성립함을 알 수 있습니다.
여기까지 깨닫고 각 gate
별로 다익스트라를 이용해 가장 빨리 만나는 산봉우리까지의 경로의 intensity
를 비교하는 방식으로 구현하면 4개 정도의 테스트케이스에서 시간 초과가 발생함을 알 수 있습니다. 분명 잘 풀이한 것 같은데, 어서 시간복잡도를 계산해봅시다.
- vertex의 갯수
|V|
의 최댓값 =50,000
- edge의 갯수
|E|
의 최댓값 =200,000
- 우선순위 큐를 사용한 dijkstra의 시간복잡도 =
O(|E|log|V|)
이 때 각 gate
별로 다익스트라를 수행하므로, 지금까지의 풀이의 총 시간복잡도는 |V| * O(|E|log|V|)
로, 최악의 경우 약 50,000,000,000
회의 연산을 수행하게 됩니다. 이는 흔하게 사용하는 기준인 1억번 = 1초
를 적용하면 500
초 정도가 소요되므로 시간 초과를 피할 수 없음을 알 수 있습니다.
그렇다면 어디서 개선을 이루어내야 할까요? 바로 세 번째 키 포인트가 해결책입니다. 이 문제는 매 gate
별로 산봉우리까지의 거리를 구할 것을 요구하지 않습니다. 어디서 출발하던 상관없이, 하나의 최솟값만을 찾으면 됩니다. 그렇다면 모든 gate
에서 동시에 출발하여 방문한 노드 마다의 가중치를 저장하는 배열을 공유시켜도 될 것입니다. 따라서 매 gate
마다 다익스트라를 수행할 필요 없이, 단 한 번의 다익스트라로 문제를 해결할 수 있습니다. 실제로 이 방식으로 구현했을 때, 초기에 모든 gate
마다 방문 가중치가 0
으로 초기화되므로 별도로 gate
를 만났을 때의 예외 처리를 구현할 필요도 없어집니다.
따라서 총 시간복잡도는 다익스트라의 시간복잡도와 일치하는 O(|E|log|V|)
로, 최악의 경우에도 약 백만 번의 연산만으로 문제를 해결할 수 있습니다.
전체 코드
1 |
|