diff --git a/week10/seoyoung.md b/week10/seoyoung.md new file mode 100644 index 0000000..455c906 --- /dev/null +++ b/week10/seoyoung.md @@ -0,0 +1,154 @@ +# 10주차 문제 풀이 인증 + +## 기본 정보 + +- 이름: 이서영 +- 목표 문제 수: 3 +- 실제 풀이 문제 수: 3 + +## 푼 문제 목록 + +| 번호 | 문제 이름 | 난이도 | 링크 | 풀이 여부 | +| -- | ----- | --- | --------------------------------------------------------------- | ----- | +| 1 | 게임 맵 최단거리 | Lv.2 | https://school.programmers.co.kr/learn/courses/30/lessons/1844 | o | +| 2 | 합승 택시 요금 | Lv.3 | https://school.programmers.co.kr/learn/courses/30/lessons/72413 | o | +| 3 | 등산코스 정하기 | Lv.3 | https://school.programmers.co.kr/learn/courses/30/lessons/118669 | o | + +--- + +# 오답노트 + +> 오답노트는 AI 답변이나 블로그 풀이를 그대로 복사하지 않고, 본인의 언어로 직접 작성해주세요. +> 문제별로 아래 양식을 복사해서 필요한 만큼 반복 작성하시면 됩니다. + +## 문제 1 + +### 문제 정보 + +* 문제명: 게임 맵 최단거리 +* 문제 링크: https://school.programmers.co.kr/learn/courses/30/lessons/1844 +* 사용한 알고리즘 / 자료구조: BFS(가중치가 모두 1인 다익스트라), 큐, 2차원 배열 + +### 처음 접근 방식 + +지난주에 배달 문제를 다익스트라로 풀었던 기억이 남아서, 이 문제도 우선순위 큐를 써서 풀어야 한다고 생각했다. 칸마다 `(거리, 좌표)`를 넣고 거리가 짧은 것부터 꺼내는 방식으로 구현했는데, 답은 맞았지만 굳이 우선순위 큐가 필요한지 확신이 서지 않았다. + +### 풀이 방법 + +1. 맵과 같은 크기의 `dist` 배열을 만들고 전부 0(=아직 방문 안 함)으로 둔다. 시작 칸 `(0, 0)`은 1로 초기화한다. +2. 시작 칸을 큐에 넣고, 큐가 빌 때까지 앞에서 하나씩 꺼낸다. +3. 꺼낸 칸에서 상하좌우 네 방향을 확인한다. +4. 맵 범위를 벗어나거나, 벽(`0`)이거나, 이미 `dist` 값이 채워진 칸이면 건너뛴다. +5. 조건을 통과한 칸은 `dist[현재] + 1`로 값을 채우고 큐에 넣는다. +6. 탐색이 끝난 뒤 `dist[n-1][m-1]`이 0이면 도달하지 못한 것이므로 `-1`, 아니면 그 값을 반환한다. + +### 어려웠던 지점 + +우선순위 큐를 쓴 풀이와 일반 큐를 쓴 풀이의 결과가 똑같이 나와서, 왜 둘 다 맞는지 설명하지 못했다. 또 방문 처리를 큐에서 꺼낼 때 하도록 구현했다가 같은 칸이 큐에 여러 번 들어가면서 불필요하게 느려졌다. + +### 해결 방법 + +이 문제는 모든 칸 사이의 이동 비용이 1로 똑같다는 걸 다시 확인했다. 가중치가 전부 같으면 큐에 들어간 순서가 곧 거리 순서라서, 우선순위 큐로 정렬할 필요 없이 일반 큐만으로도 가장 짧은 거리부터 확정된다. 방문 처리는 큐에 넣는 시점으로 옮겨서 같은 칸이 중복으로 들어가지 않게 고쳤다. + +### 시간복잡도 / 공간복잡도 + +* 시간복잡도: O(N * M) +* 공간복잡도: O(N * M) + +`N`과 `M`은 맵의 세로, 가로 길이이다. 각 칸을 한 번씩만 방문한다. + +### 새롭게 알게 된 점 + +BFS는 가중치가 전부 1인 그래프에서의 다익스트라와 같다는 걸 이해했다. 우선순위 큐는 간선마다 비용이 다를 때 "다음으로 가장 싼 노드"를 골라내기 위해 필요한 것이지, 최단거리 문제라고 무조건 써야 하는 게 아니었다. 가중치가 같으면 일반 큐가 더 빠르다. + +--- + +## 문제 2 + +### 문제 정보 + +* 문제명: 합승 택시 요금 +* 문제 링크: https://school.programmers.co.kr/learn/courses/30/lessons/72413 +* 사용한 알고리즘 / 자료구조: 다익스트라, 우선순위 큐, 인접 리스트 + +### 처음 접근 방식 + +A와 B가 합승할 구간을 직접 정해야 한다고 생각해서, 출발지 `s`에서 시작하는 경로를 하나씩 따라가며 "여기까지 같이 가고 여기서 헤어진다"를 전부 시도하려고 했다. 그런데 헤어지는 지점을 경로 위에서 찾으려니 경우의 수가 폭발했고, 구현 자체가 막혔다. + +### 풀이 방법 + +1. 요금 정보를 양방향 인접 리스트로 저장한다. +2. 시작 정점을 받아 모든 정점까지의 최단 요금을 구하는 다익스트라 함수를 만든다. +3. `s`, `a`, `b`에서 각각 다익스트라를 한 번씩 돌려 `distS`, `distA`, `distB` 세 개의 배열을 얻는다. +4. 모든 정점 `k`를 헤어지는 지점 후보로 놓고, `distS[k] + distA[k] + distB[k]` 값을 계산한다. +5. 그중 가장 작은 값을 답으로 반환한다. + +### 어려웠던 지점 + +헤어지는 지점을 경로 위에서 직접 추적하려고 한 게 가장 큰 실수였다. 그리고 `k`를 `s`와 다른 정점으로만 제한해야 하는지 헷갈렸다. 처음부터 각자 가는 경우(합승을 아예 안 하는 경우)를 따로 처리해야 한다고 생각해서 코드가 복잡해졌다. + +### 해결 방법 + +"경로를 추적한다"가 아니라 "헤어지는 지점을 고른다"로 문제를 바꿔서 생각했다. 어떤 정점 `k`에서 헤어진다고 가정하면 총 요금은 `s→k` + `k→a` + `k→b`로 딱 나뉘고, 이 세 값은 전부 최단 요금이면 된다. `k = s`인 경우가 곧 처음부터 각자 가는 경우이므로, `k`를 모든 정점으로 두고 돌리면 예외 처리 없이 자연스럽게 포함됐다. + +### 시간복잡도 / 공간복잡도 + +* 시간복잡도: O(E log V) +* 공간복잡도: O(V + E) + +`V`는 지점 수(최대 200), `E`는 요금 정보의 개수이다. 다익스트라를 세 번만 돌리므로 상수 배만 늘어난다. + +### 새롭게 알게 된 점 + +시작점이 여러 개인 것처럼 보이는 문제는, 각 시작점에서 다익스트라를 따로 돌려 거리 배열을 만들어두고 그걸 조합하는 방식으로 풀 수 있다. 정점 수가 작으면 플로이드-워셜로 한 번에 모든 쌍을 구해도 되지만, 정점이 많아지면 필요한 시작점에서만 다익스트라를 돌리는 쪽이 훨씬 유리하다. + +--- + +## 문제 3 + +### 문제 정보 + +* 문제명: 등산코스 정하기 +* 문제 링크: https://school.programmers.co.kr/learn/courses/30/lessons/118669 +* 사용한 알고리즘 / 자료구조: 변형 다익스트라(minimax), 우선순위 큐, 다중 시작점 + +### 처음 접근 방식 + +출입구에서 출발해 산봉우리를 찍고 돌아오는 왕복 경로라서, 갈 때와 올 때를 따로 계산해야 한다고 생각했다. 게다가 intensity가 "경로 중 가장 긴 구간"이라 거리를 더하는 평범한 다익스트라를 그대로 쓸 수 없어서 어떻게 손대야 할지 감이 안 왔다. + +### 풀이 방법 + +1. 경로 정보를 양방향 인접 리스트로 저장하고, 출입구와 산봉우리를 각각 따로 표시해둔다. +2. `dist[v]`를 "출입구에서 `v`까지 가는 경로들 중, 경로 안의 최대 구간 시간이 가장 작은 값"으로 정의하고 무한대로 초기화한다. +3. 모든 출입구를 `dist = 0` 상태로 우선순위 큐에 한꺼번에 넣는다. +4. 큐에서 intensity가 가장 작은 노드를 꺼낸다. 저장된 `dist` 값보다 크면 지나간 정보이므로 무시한다. +5. 꺼낸 노드가 산봉우리면 거기서 코스가 끝나므로 더 이상 뻗어나가지 않는다. +6. 인접 노드로 갈 때의 새 intensity는 `max(현재 intensity, 간선 소요시간)`으로 계산하고, 기존 `dist`보다 작을 때만 갱신해 큐에 넣는다. 이때 다음 노드가 출입구면 지나갈 수 없으므로 건너뛴다. +7. 모든 산봉우리를 돌면서 `dist` 값이 가장 작은 봉우리를 찾고, 값이 같으면 번호가 작은 쪽을 선택해 `[봉우리 번호, intensity]`로 반환한다. + +### 어려웠던 지점 + +출입구와 산봉우리를 중간에 지나갈 수 없다는 조건을 빼먹어서 계속 틀렸다. 또 왕복 경로라는 말에 갇혀서 거리를 두 번 더하려고 했고, 출입구가 여러 개라서 출입구마다 다익스트라를 돌렸다가 시간초과가 났다. + +### 해결 방법 + +intensity는 경로 위 구간 중 **최댓값** 하나로 결정되기 때문에, 갔던 길로 그대로 돌아오면 왕복해도 최댓값이 변하지 않는다. 그래서 편도만 계산해도 충분했다. 출입구가 여러 개인 문제는 가상의 출발점 하나에서 모든 출입구로 0짜리 간선이 있다고 보면 되므로, 모든 출입구를 `dist = 0`으로 큐에 한 번에 넣는 다중 시작점 방식으로 바꿔 한 번의 탐색으로 끝냈다. 통과 금지 조건은 산봉우리에 도착하면 확장을 멈추고, 출입구로는 이동하지 않도록 처리해서 해결했다. + +### 시간복잡도 / 공간복잡도 + +* 시간복잡도: O(E log V) +* 공간복잡도: O(V + E) + +`V`는 지점 수(최대 50,000), `E`는 경로의 개수(최대 200,000)이다. + +### 새롭게 알게 된 점 + +다익스트라에서 거리를 누적하는 부분을 `dist + w` 대신 `max(dist, w)`로 바꾸면 "경로의 최대 간선을 최소화하는" 문제를 풀 수 있다. 갱신 기준이 단조롭기만 하면 다익스트라의 골격은 그대로 쓸 수 있다는 걸 알게 됐다. 시작점이 여러 개일 때 전부 큐에 넣고 한 번만 돌리는 다중 시작점 기법도 앞으로 자주 쓸 것 같다. + +--- + +# 다음 주 목표 + +* 목표 문제 수: 3 +* 집중할 유형: 최단경로 심화 / 실전 모의고사 +* 세부 키워드: 다익스트라 변형, 0-1 BFS, 벨만-포드, 플로이드-워셜 비교