문제 설명
주어진 항공권을 모두 이용하여 여행경로를 짜려고 합니다. 항상 "ICN" 공항에서 출발합니다.
항공권 정보가 담긴 2차원 배열 tickets가 매개변수로 주어질 때, 방문하는 공항 경로를 배열에 담아 return 하도록 solution 함수를 작성해주세요.
제한사항
모든 공항은 알파벳 대문자 3글자로 이루어집니다.
주어진 공항 수는 3개 이상 10,000개 이하입니다.
tickets의 각 행 [a, b]는 a 공항에서 b 공항으로 가는 항공권이 있다는 의미입니다.
주어진 항공권은 모두 사용해야 합니다.
만일 가능한 경로가 2개 이상일 경우 알파벳 순서가 앞서는 경로를 return 합니다.
모든 도시를 방문할 수 없는 경우는 주어지지 않습니다.
입출력 예
tickets return
[["ICN", "JFK"], ["HND", "IAD"], ["JFK", "HND"]] ["ICN", "JFK", "HND", "IAD"]
[["ICN", "SFO"], ["ICN", "ATL"], ["SFO", "ATL"], ["ATL", "ICN"], ["ATL","SFO"]] ["ICN", "ATL", "ICN", "SFO", "ATL", "SFO"]
입출력 예 설명
예제 #1
["ICN", "JFK", "HND", "IAD"] 순으로 방문할 수 있습니다.
예제 #2
["ICN", "SFO", "ATL", "ICN", "ATL", "SFO"] 순으로 방문할 수도 있지만 ["ICN", "ATL", "ICN", "SFO", "ATL", "SFO"] 가 알파벳 순으로 앞섭니다.
def dfs(d, a, idx, visited, tickets, answer):
## 종료 조건
if 0 not in visited:
answer.append(a)
if (len(answer) == len(tickets)+1): ## 모든 티켓이 사용되었고, 모든 공항을 방문했다면
ans.append(answer)
return
## 다음 지점 후보 선택
for new_idx, (new_d, new_a) in enumerate(tickets):
if visited[new_idx] == 1: continue
if a == new_d:
answer.append(new_d)
visited[new_idx]=1
dfs(new_d, new_a, new_idx, visited, tickets, answer)
visited[new_idx]=0
answer=answer[:-1]
def solution(tickets):
global ans
ans = []
for idx, (d, a) in enumerate(tickets): ## 시작 지점 후보 선택
if d == "ICN":
answer = []
answer.append("ICN")
visited = [0] * len(tickets) ## 항공권을 사용했는지 확인
visited[idx] = 1
dfs(tickets[idx][0], tickets[idx][1], idx, visited, tickets, answer)
visited[idx]=0
return min(ans)
관련된 힌트 / 반례들을 찾았고, 아래의 것들은 통과했는데 왜 1번 테케는 통과가 안 되는지... 찾다가 결국 못찾았다.
1) 힌트 1
tickets result
[["ICN", "AAA"], ["ICN", "CCC"], ["CCC", "DDD"], ["AAA", "BBB"], ["AAA", "BBB"], ["DDD", "ICN"], ["BBB", "AAA"]] ["ICN", "CCC", "DDD", "ICN", "AAA", "BBB", "AAA", "BBB"]
-> 내 코드에서는 이 케이스가 통과가 된다. 도중에 돌아오지 못하는 경우에는 그 경로는 최종적인 ans에 저장되지 못한다. 그리고 분기점으로 다시 돌아가서 새로운 경로를 탐색할 수 있게 하였다.
2) 힌트 2
-> visited를 통해 idx로 항공권을 구분하기 때문에 중복되는 항공권도 구분되고 있다고 생각한다.
3) 힌트3
-> visited와 answer를 돌려놓는 것으로 이 부분을 커버했다고 생각했는데, 이 부분이 문제인 것인가?
def solution(tickets):
routes = {} ## key: 시작지점, value: 도착 지점 / 갈 수 있는 공항이 있는지 없는지 확인하기 위하여 / 중복 경로도 구분이 됨
for t in tickets:
routes[t[0]] = routes.get(t[0], []) + [t[1]]
for r in routes:
routes[r].sort(reverse=True) ## 도착 지점들은 알파벳 거꾸로 순서로 정렬
stack = ["ICN"]
path = []
while len(stack) > 0:
top = stack[-1]
if top not in routes or len(routes[top]) == 0: ## 모든 루트를 잘 따라 갔을 때 / 혹은 길을 찾던 도중 더이상 갈 수 있는 공항이 없을 때
path.append(stack.pop()) ## stack에서 지우기 / path에 경로를 추가하기
else:
stack.append(routes[top][-1]) ## 다음에 갈 수 있는 공항 중 알파벳이 빠른 순서를 stack에 넣기
routes[top] = routes[top][:-1] ## stack에 넣은 공항은 routes에서 제거하기
return path[::-1]
굉장히 깔끔한 코드다! 그러나 작동 원리에 대해서 궁금한 점이 남았다.
1) 더 이상 갈 수 있는 루트가 없을 때 path에 넣어준다. 이후에 다른 갈 수 있는 path들이 stack에 쌓이면 연이어서 path에 넣게 되는데, 이 두 가지의 루트가 서로 연결되는지 어떻게 알 수 있는 것이지?? 그것이 궁금하다.
예를 들어, 위의 힌트1 반례케이스에서의 route는 이렇게 정의된다.
routes: {'ICN': ['CCC', 'AAA'], 'CCC': ['DDD'], 'AAA': ['BBB', 'BBB'], 'DDD': ['ICN'], 'BBB': ['AAA']}
이렇게 'AAA' 먼저 방문을 하면 더이상 다른 공항을 방문할 수 없는 상태가 된다.
***
routes: {'ICN': ['CCC'], 'CCC': ['DDD'], 'AAA': [], 'DDD': ['ICN'], 'BBB': []}
stack: ['ICN', 'AAA', 'BBB', 'AAA', 'BBB']
path: []
그러면 다시 갈 수 있는 상태가 될 수 있을 때 까지 stack에서 pop하고 path에 넣게 되는데,
***
routes: {'ICN': ['CCC'], 'CCC': ['DDD'], 'AAA': [], 'DDD': ['ICN'], 'BBB': []}
stack: ['ICN']
path: ['BBB', 'AAA', 'BBB', 'AAA']
나머지 경로를 모두 찾아서 stack에 넣었을 때, 이 stack을 pop해서 path에 하나씩 넣으면 stack의 마지막인 'ICN'과 path의 마지막인 'AAA'가 서로 연결이 된다. 그러나 연결된다는 보장이 어떻게 생기는 것인지가 궁금하다.
***
routes: {'ICN': [], 'CCC': [], 'AAA': [], 'DDD': [], 'BBB': []}
stack: ['ICN', 'CCC', 'DDD', 'ICN']
path: ['BBB', 'AAA', 'BBB', 'AAA']