Code › algorithm-study
LeetCode 21 - Merge Two Sorted Lists
dummy node와 포인터 이동으로 두 정렬 연결 리스트를 합친 Python 풀이
오름차순으로 정렬된 두 연결 리스트를 하나의 정렬된 리스트로 합치는 Easy 문제다. 두 리스트의 현재 노드만 비교하면서 더 작은 노드를 결과 리스트에 연결했고, 첫 노드를 따로 처리하지 않도록 dummy node를 사용했다.
문제 링크 & 설명
- 문제 링크: 21. Merge Two Sorted Lists
- 요약: 두 정렬 연결 리스트의 head가 주어졌을 때, 기존 노드들을 오름차순으로 연결한 리스트의 head를 반환하는 문제다.
두 리스트가 이미 정렬되어 있으므로 각 리스트의 앞쪽 노드만 비교하면 된다. 값이 더 작은 노드를 결과에 붙인 뒤 해당 리스트의 포인터를 다음 노드로 옮기면, 매 단계에서 아직 처리하지 않은 값 중 최솟값이 선택된다.
접근 방법
결과 리스트의 시작 지점을 만들기 위해 값 자체에는 의미가 없는 dummy node를 하나 생성했다. curr는 결과 리스트의 마지막 노드를 가리키며, 선택한 노드를 curr.next에 연결한 뒤 함께 앞으로 이동한다.
dummy = ListNode()
curr = dummy
두 리스트에 노드가 모두 남아 있는 동안 현재 값을 비교한다. list1의 값이 더 작으면 list1의 노드를 연결하고 list1을 다음으로 옮기며, 그렇지 않으면 list2에 같은 작업을 한다.
while list1 and list2:
if list1.val < list2.val:
curr.next = list1
list1 = list1.next
else:
curr.next = list2
list2 = list2.next
curr = curr.next
반복문이 끝났다면 한쪽 리스트는 모두 소진된 상태다. 다른 리스트에 남은 노드들은 이미 정렬되어 있으므로 꼬리 전체를 그대로 연결하면 된다.
curr.next = list1 if list1 else list2
dummy는 구현을 위한 임시 시작점이므로 실제 결과의 head인 dummy.next를 반환한다.
트러블 슈팅
입력 중 하나가 처음부터 비어 있어도 별도 분기가 필요하지 않다. while 조건을 통과하지 않고 남은 리스트가 곧바로 curr.next에 연결되기 때문이다. 두 리스트가 모두 비어 있다면 curr.next에는 None이 연결되고, 반환값도 None이 된다.
dummy node를 결과에 포함하지 않는 것도 확인해야 한다. dummy는 첫 노드를 연결할 때 생기는 예외 처리를 없애기 위한 노드이므로 dummy가 아니라 dummy.next를 반환해야 한다.
복잡도 분석
- 시간 복잡도: O(n + m)
- 두 리스트의 각 노드를 최대 한 번씩 확인하고 연결한다.
- 공간 복잡도: O(1)
- 기존 노드의 next 포인터를 다시 연결하며, 입력 크기에 따라 늘어나는 별도 자료구조를 만들지 않는다.
구현 코드
from typing import Optional
class Solution:
def mergeTwoLists(
self,
list1: Optional[ListNode],
list2: Optional[ListNode],
) -> Optional[ListNode]:
dummy = ListNode()
curr = dummy
while list1 and list2:
if list1.val < list2.val:
curr.next = list1
list1 = list1.next
else:
curr.next = list2
list2 = list2.next
curr = curr.next
curr.next = list1 if list1 else list2
return dummy.next
요약 및 회고
정렬된 두 리스트에서는 현재 노드 중 작은 값을 선택하는 것만으로 전체 정렬 순서를 유지할 수 있다. dummy node를 두면 결과의 첫 노드를 연결하는 경우와 이후 노드를 연결하는 경우를 같은 코드로 처리할 수 있고, 한 리스트가 끝난 뒤에는 남은 꼬리를 한 번에 붙여 순회를 마칠 수 있다.