티스토리 뷰

728x90
반응형

문제

[from, to]로 구성된 항공권 목록을 이용해 JFK에서 출발하는 여행 일정을 구성하라. 여러 일정이 있는 경우 사전 어휘순으로 방문한다.

입력

[["MUC", "LHR"], ["JFK", "MUC"], ["SF0", "SJC"], ["LHR", "SF0"]]

출력

["JFK", "MUC", "SF0", "SJC"]

입력

[["JFK", "SF0"], ["JFK", "ATL"], ["SF0", "ATL"], ["ATL", "JFK"], ["ATL", "SF0"]]

출력

["JFK", "ATL", "JFK", "SF0", "ATL", "SF0"]

코드

DFS

from typing import List
import collections

class Solution:
    def findItinerary(self, tickets: List[List[int]]) -> List[str]:
        graph = collections.defaultdict(list)

        for a, b in sorted(tickets, reverse=True):
            graph[a].append(b)

        route = []
        def dfs(a):
            while graph[a]:
                dfs(graph[a].pop())
            route.append(a)

        dfs('JFK')
        return route[::-1]

stack

from typing import List
import collections

class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        graph = collections.defaultdict(list)

        for a, b in sorted(tickets):
            graph[a].append(b)

        route, stack = [], ['JFK']
        while stack:
            while graph[stack[-1]]:
                # print(f'graph[stack[-1]: {graph[stack[-1]]}')
                stack.append(graph[stack[-1]].pop(0))
                # print(f'stack : {stack}')
            # print(f'route : {route}')
            route.append(stack.pop())

        return route[::-1]

풀이

DFS

여행 일정을 그래프로 구성하고 DFS로 풀이한다. 중복된 일정일 경우 어휘 순으로 방문하므로 그래프를 정렬해서 구성해야 한다.

graph = collections.defaultdict(list)
for a, b in sorted(tickets): # reverse=True가 정답 코드인 이유는 밑에..
    graph[a].append(b)

이제 그래프에서 하나씩 꺼낼 차례다. pop()으로 재귀 호출하면서 모두 꺼내 결과에 추가한다. 결과 리스트에는 역순으로 담기게 될 것이며, pop()으로 처리했기 때문에 드래프에서는 해당 경로는 사라지게 되어 재방문하게 되지는 않을 것이다. 여기서 중요한 점은 어휘순으로 방문해야 하기 때문에 pop(0)으로 첫 번째 값을 읽어야 한다.

def dfs(a):
    while graph[a]:
        dfs(graph[a].pop(0)) # pop()이 정답 코드인 이유는 밑에..
    route.append(a)

파이썬의 리스트의 경우 파라미터를 지정하지 않은 값, 그러니까 마지막 값을 꺼내는 pop()은 O(1)이지만, 첫 번째 값을 꺼내는 pop(0)은 O(n)이다. 따라서 좀 더 효율적인 구현을 위해서는 pop()으로, 즉 스택의 연산으로 처리될 수 있도록 개선이 필요하다.

for a, b in sorted(tickets, reverse=True):
    graph[a].append(b)
...
def dfs(a):
    while graph[a]:
        dfs(graph[a].pop())
    route.append(a)

애당초 그래프를 역순으로 구성하면 추출 시에는 pop()으로 처리가 가능하다. 이렇게 하면 추출 시 시간 복잡도를 O(1)로 개선할 수 있다.

stack

동일한 형태로 그래프를 구성하되, 끄집어 낼 때 재귀가 아닌 스택을 이용한 반복으로 처리한다.

graph = collections.defaultdict(list)
for a, b in sorted(tickets):
    graph[a].append(b)
...
stack = []
while stack:
    while graph[stack[-1]]:
        stack.append(graph[stack[-1]].pop(0))
...

위 형태의 초기코드는 이해하기 쉽다. 밑에 헷갈렸던 부분에 이어서..

헷갈렸던 부분

stack

맨 위의 예제 2개는 끊어지는 경우가 없기 때문에 스택에 모든 경로가 한 번에 담긴다. 그런데 만약 경로가 끊어지는 경우가 있다면 스택에 모든 경로가 한 번에 담길 수 없다. 예를 들어 입력값이 [["JFK", "KUL"], ["JFK", "NRT"], ["NRT", "JFK"]]라면 그래프는 다음과 같은 형태가 된다.

defaultdict(list, {'JFK': ['KUL', 'NRT'], 'NRT': ['JFK']})

이 경우 JFK → KUL에서 더 이상 진행할 수 없게 된다. 따라서 스택의 값을 다시 pop()하여 다음이 진행될 수 있게끔 해줘야 한다.

route, stack = [], ['JFK']
while stack:
    while graph[stack[-1]]:
        stack.append(graph[stack[-1]].pop(0))
    route.append(stack.pop())

DFS 재귀 호출과 달리 반복으로 풀이하려면 이처럼 한 번 더 풀어낼 수 있는 변수가 필요하다. 여기서는 최종 결과가 되는 변수이므로 DFS 재귀 풀이와 동일한 route로 변수명을 정했고, 여기에 스택을 pop()하여 담게 했다. 즉 경로가 풀리면서 거꾸로 담기게 될것이다. 따라서 최종 결과는 다시 뒤집어야 한다.



출처

파이썬 알고리즘 인터뷰 (글 : 박상길 그림 : 정진호) [책만]

728x90
반응형
댓글
반응형
250x250
글 보관함
최근에 달린 댓글
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Total
Today
Yesterday
링크