XX산은 n
개의 지점으로 이루어져 있습니다. 각 지점은 1부터 n
까지 번호가 붙어있으며, 출입구, 쉼터, 혹은 산봉우리입니다. 각 지점은 양방향 통행이 가능한 등산로로 연결되어 있으며, 서로 다른 지점을 이동할 때 이 등산로를 이용해야 합니다. 이때, 등산로별로 이동하는데 일정 시간이 소요됩니다.
등산코스는 방문할 지점 번호들을 순서대로 나열하여 표현할 수 있습니다.
예를 들어 1-2-3-2-1
으로 표현하는 등산코스는 1번지점에서 출발하여 2번, 3번, 2번, 1번 지점을 순서대로 방문한다는 뜻입니다.
등산코스를 따라 이동하는 중 쉼터 혹은 산봉우리를 방문할 때마다 휴식을 취할 수 있으며, 휴식 없이 이동해야 하는 시간 중 가장 긴 시간을 해당 등산코스의 intensity
라고 부르기로 합니다.
당신은 XX산의 출입구 중 한 곳에서 출발하여 산봉우리 중 한 곳만 방문한 뒤 다시 원래의 출입구로 돌아오는 등산코스를 정하려고 합니다. 다시 말해, 등산코스에서 출입구는 처음과 끝에 한 번씩, 산봉우리는 한 번만 포함되어야 합니다.
당신은 이러한 규칙을 지키면서 intensity
가 최소가 되도록 등산코스를 정하려고 합니다.
다음은 XX산의 지점과 등산로를 그림으로 표현한 예시입니다.
위의 예시에서 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
가 최소가 되는 등산코스가 여러 개라면 그중 산봉우리의 번호가 가장 낮은 등산코스를 선택합니다.
n
≤ 50,000n
- 1 ≤ paths
의 길이 ≤ 200,000paths
의 원소는 [i, j, w]
형태입니다.i
번 지점과 j
번 지점을 연결하는 등산로가 있다는 뜻입니다.w
는 두 지점 사이를 이동하는 데 걸리는 시간입니다.i
< j
≤ n
w
≤ 10,000,000gates
의 길이 ≤ n
gates
의 원소 ≤ n
gates
의 원소는 해당 지점이 출입구임을 나타냅니다.summits
의 길이 ≤ n
summits
의 원소 ≤ n
gates
와 summits
에 등장하지 않은 지점은 모두 쉼터입니다.[산봉우리의 번호, intensity의 최솟값]
순서여야 합니다.n | paths | gates | summits | result |
---|---|---|---|---|
6 | [[1, 2, 3], [2, 3, 5], [2, 4, 2], [2, 5, 4], [3, 4, 4], [4, 5, 3], [4, 6, 1], [5, 6, 1]] | [1, 3] | [5] | [5, 3] |
7 | [[1, 4, 4], [1, 6, 1], [1, 7, 3], [2, 5, 2], [3, 7, 4], [5, 6, 6]] | [1] | [2, 3, 4] | [3, 4] |
7 | [[1, 2, 5], [1, 4, 1], [2, 3, 1], [2, 6, 7], [4, 5, 1], [5, 6, 1], [6, 7, 1]] | [3, 7] | [1, 5] | [5, 1] |
5 | [[1, 3, 10], [1, 4, 20], [2, 3, 4], [2, 4, 6], [3, 5, 20], [4, 5, 6]] | [1, 2] | [5] | [5, 6] |
입출력 예 #1
문제 예시와 같습니다. 등산코스의 intensity
가 최소가 되는 산봉우리 번호는 5, intensity
의 최솟값은 3이므로 [5, 3]을 return 해야 합니다.
입출력 예 #2
XX산의 지점과 등산로를 그림으로 표현하면 아래와 같습니다.
가능한 intensity
의 최솟값은 4이며, intensity
가 4가 되는 등산코스는 1-4-1
과 1-7-3-7-1
이 있습니다. intensity
가 최소가 되는 등산코스가 여러 개이므로 둘 중 산봉우리의 번호가 낮은 1-7-3-7-1
을 선택합니다. 따라서 [3, 4]
를 return 해야 합니다.
입출력 예 #3
XX산의 지점과 등산로를 그림으로 표현하면 아래와 같습니다.
가능한 intensity
의 최솟값은 1이며, 그때의 등산코스는 7-6-5-6-7
입니다. 따라서 [5, 1]
를 return 해야 합니다.
7-6-5-4-1-4-5-6-7
과 같은 등산코스는 산봉우리를 여러 번 방문하기 때문에 잘못된 등산코스입니다.입출력 예 #4
XX산의 지점과 등산로를 그림으로 표현하면 아래와 같습니다.
가능한 intensity
의 최솟값은 6, 그때의 등산코스는 2-4-5-4-2
입니다. 따라서 [5, 6]
을 return 해야 합니다.
우선순위 큐
, 다익스트라 알고리즘
등 다양한 방법이 제시되었기에 질문하기를 많이 참고하여 해결한 문제이다.
class Queue {
constructor() {
this.queue = [];
this.front = 0;
this.rear = 0;
}
// 큐에 값을 추가하는 메서드
enqueue(val) {
this.queue[this.rear++] = val;
}
// 큐에서 값을 추출하는 메서드
dequeue() {
const val = this.queue[this.front];
delete this.queue[this.front++];
return val;
}
// 큐가 비어있는지 확인하는 메서드
isEmpty() {
return this.front === this.rear;
}
}
function solution(n, paths, gates, summits) {
// 산봉우리의 번호를 오름차순으로 정렬합니다. (산봉우리는 기본 오름차 순으로 정렬되어있지 않음)
summits.sort((a, b) => a - b);
const heap = new Queue();
// 각 지점과 그 정보를 저장할 Map 객체들을 생성합니다.
const pathMap = new Map();
const summitMap = new Map();
const gateMap = new Map();
// 각 지점까지의 최대 이동 시간을 저장할 배열을 생성하고 초기화합니다.
const intensity = Array.from({length: n + 1}, () => 10_000_001);
intensity[0] = 0;
// 산봉우리 지점 초기화
for (const summit of summits) {
summitMap.set(summit, true);
}
// 출입구 지점 초기화 / 큐에 입력
for (const gate of gates) {
gateMap.set(gate, true);
heap.enqueue({node: gate, intensity: 0});
intensity[gate] = 0;
}
// 방문 기록 확인 node1 -> node2, node2 -> node1
for (const [node1, node2, w] of paths) {
if (pathMap.has(node1)) {
const nexts = pathMap.get(node1);
nexts[nexts.length] = [node2, w];
pathMap.set(node1, nexts);
} else {
pathMap.set(node1, [[node2, w]]);
}
if(pathMap.has(node2)) {
const nexts = pathMap.get(node2);
nexts[nexts.length] = [node1, w];
pathMap.set(node2, nexts);
}else{
pathMap.set(node2,[[node1, w]]);
}
}
while (!heap.isEmpty()) {
/* 현재 위치와 그때까지의 최대 이동시간 정보가 담긴 객체(current와 currIntensity)
를 heap에서 꺼내옵니다. */
let current = heap.dequeue();
/* 현재 위치가 산봉우리(summit)인 경우 continue로 건너뛰고,
아닌 경우 연결된 등산로(nextNodes)들 중에서 아직 방문하지 않았거나
기존에 알려진 경로보다 더 짧은 경로인 경우 intensity 값을 업데이트하고 heap에 추가합니다. */
if(summitMap.has(current.node)) continue;
let nextNodes = pathMap.get(current.node);
for(let [nextNode, nextIntensity] of nextNodes) {
const maxIntensity = Math.max(current.intensity, nextIntensity);
if(maxIntensity < intensity[nextNode]) {
intensity[nextNode] = maxIntensity;
heap.enqueue({node: nextNode, intensity: maxIntensity});
}
}
}
/* 마지막으로 intensity 배열에서 산봉우리만 필터링하여 [산봉우리 번호, intensity]
형태의 2차원 배열 생성 후 정렬하여 가장 작은 값을 반환합니다. */
return intensity
.filter((v, i) => summitMap.has(i))
.map((v, i) => [summits[i], v])
.sort((a,b) => a[1] - b[1] || a[0] - b[0])[0];
}