문제 출처: 프로그래머스 (https://school.programmers.co.kr/learn/courses/30/lessons/42861)
문제 설명
🔎n개의 섬 사이에 다리를 건설하는 비용(costs)이 주어질 때, 최소의 비용으로 모든 섬이 서로 통행 가능하도록 만들 때 필요한 최소 비용을 return 하도록 solution을 완성하세요.
다리를 여러 번 건너더라도, 도달할 수만 있으면 통행 가능하다고 봅니다. 예를 들어 A 섬과 B 섬 사이에 다리가 있고, B 섬과 C 섬 사이에 다리가 있으면 A 섬과 C 섬은 서로 통행 가능합니다.
제한사항
1. 섬의 개수 n은 1 이상 100 이하입니다.
2. costs의 길이는 ((n-1) * n) / 2이하입니다.
3. 임의의 i에 대해, costs[i][0] 와 costs[i] [1]에는 다리가 연결되는 두 섬의 번호가 들어있고, costs[i] [2]에는 이 두 섬을 연결하는 다리를 건설할 때 드는 비용입니다.
4.같은 연결은 두 번 주어지지 않습니다. 또한 순서가 바뀌더라도 같은 연결로 봅니다. 즉 0과 1 사이를 연결하는 비용이 주어졌을 때, 1과 0의 비용이 주어지지 않습니다.
5. 모든 섬 사이의 다리 건설 비용이 주어지지 않습니다. 이 경우, 두 섬 사이의 건설이 불가능한 것으로 봅니다.
6. 연결할 수 없는 섬은 주어지지 않습니다.
해당 문제는 프로그래머스의 코딩 테스트 고득점 kit에 있는 문제다.
섬, 연결, 최소 비용이라는 단어를 보자마자 bfs, 다익스트라 등을 생각했고 실제 바로 그리디한 방법이 떠오르지 않아 다익스트라로 먼저 시도했었다.
<틀린 코드 보기>
from queue import PriorityQueue
# 다익스트라 x
def solution(n, costs):
answer = 0
island = [[] for _ in range(n)]
for e1, e2, w in costs:
island[e1].append((e2, w))
island[e2].append((e1, w))
visited = [False] * (n+1)
dist = [float('inf')] * (n+1)
q = PriorityQueue()
q.put((0, 0)) # 최단거리, 노드번호
dist[0] = 0
bridge = [0] * (n+1)
while q.qsize()>0:
cur_node = q.get()[1]
if visited[cur_node]: continue
visited[cur_node] = True
for nx, val in island[cur_node]:
if dist[nx] > dist[cur_node] + val:
dist[nx] = dist[cur_node] + val
bridge[nx] = val
q.put((dist[nx], nx))
print(dist)
print(bridge)
다익스트라를 통한 구현의 문제점은 아래와 같다.
1. 결국은 모든 costs에 대한 점검을 해야한다.
2. 다익스트라로 구현된 dist 리스트는 시작점부터 해당 노드까지의 비용으로 산정되기 때문에 sum으로 답을 구할 수 없다.
제한 사항에서 섬의 개수(n)은 100이하라고 했고, costs의 길이는 ((n-1) * n) / 2 이라 규정하였으므로, 실제 costs를 전체 도는데만 해도 O(n²) 만큼의 시간이 들기 때문에 1번 문제점은 꽤나 크게 작용하였다.
이후 이 문제가 그리디로 분류되었다는 사실을 다시 한번 깨닫고 아래와 같은 코드를 작성하였다.
<정답 코드>
from collections import deque
def solution(n, costs):
answer = 0
costs = sorted(costs, key=lambda x: x[2])
isl = [[] for _ in range(n)]
for start, end, cost in costs:
q = deque()
flag = False
q.append(start)
visit = [False] * n
visit[start] = True
while q:
a = q.popleft()
if a == end:
visit[a] = True
flag = True
break
visit[a] = True
for i in isl[a]:
if not visit[i]:
q.append(i)
if sum(visit) == n:
break
if not flag:
isl[start].append(end)
isl[end].append(start)
answer += cost
return answer
접근 방법은 아래와 같다.
1. 가장 낮은 비용의 다리부터 우선적으로 고려한다. (비용을 기준으로 costs를 정렬한다.)
2. 비용을 기준으로 정렬된 costs를 순회하면서, 연결되지 않은 섬에 대해서만 다리를 건설한다.
3. 모든 섬을 방문할 수 있게 되었으면 반복문을 탈출하게 한다.
연결되지 않은 섬을 판단하는 알고리즘으로는 bfs를 사용하였다.
bfs를 채택할 수 있었던 것은, 다리를 하나씩 건설하기 때문에 bfs를 통한 연산 횟수가 0에서 부터 지수적으로 상승한다는 사실 때문이다.
해당 섬과 연결이 되었는가에 대한 판단은 flag를 통해 진행하였고, 만약 모든 섬을 방문할 수 있게 되었다면 반복문을 탈출하는 것으로 마무리 지었다. 이 부분이 다익스트라에서 모든 costs를 점검해야한다는 부분을 해소할 수 있게 해주었다.
사실 이 문제는 수개월 전에 먼저 도전했다가 실패한 문제이다. 최근에는 그래프나 트리 문제를 많이 풀지 않고 DP와 브루트포스 문제를 많이 풀어 해당 문제를 푸는데 도움이 되었던 것 같다.
'알고리즘 > 그리디' 카테고리의 다른 글
[프로그래머스] 단속카메라 (0) | 2023.01.13 |
---|