Reference: Palindrome Linked List
문제 자체의 난이도는 높지 않았습니다. 연결 리스트로 구현된 자료를 반환하며 리스트에 담아 처리하면 기존의 팰린드롬 문제와 다를게 없기 때문이고, 또한 이러한 풀이가 다른 방식의 풀이와 속도 측면에서 큰 차이가 나지 않기 때문에 이 방법으로도 충분한 풀이입니다. 아래는 제 풀이입니다.
''' 문제 조건
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
'''
# 나의 풀이
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
head_list = []
while head:
head_list.append(head.val)
head = head.next
return head_list == head_list[::-1]
node를 순회하며 값을 head_list에 담아 처리하고, head_list의 palindrome 여부를 판단합니다. 위의 return문의 Boolean은 요소를 하나하나 탐색하며 팰린드롬 여부를 파악하는 것보다 더 빠른 효율을 보여줍니다. 기능적으로는 큰 차이가 없지만 이 문제를 통해 새로운 방식인 런너 기법을 알게 되었습니다.
런너를 이용해 연결리스트의 Palindrome 문제를 풀이하면 아래와 같습니다.
def isPalindrome(self, head: ListNode) -> bool:
rev = None
slow = fast = head
while fast and fast.next:
fast = fast.next.next
rev, rev.next, slow = slow, rev, slow.next
if fast:
slow = slow.next
while rev and rev.val == slow.val:
slow, rev = slow.next, rev.next
return not rev
코드를 해석해보겠습니다.
변수 설명
rev = None -> 느린 런너를 역순으로 담을 연결리스트입니다. 느린 런너의 끝이 None이어야 하므로 rev의 초기값을 None으로 초기화해줍니다.
slow -> 한번에 한칸씩 움직이는 느린 런너입니다. 시작은 head 노드로 합니다.
fast -> 한번에 두칸씩 움직이는 빠른 런너입니다. 느린 런너와 동시점인 head 노드에서 시작합니다.
런너 이동 경로
즉, 두칸씩 움직이는 빠른 런너가 마지막에 도착했을 때, 느린 런너는 연결리스트의 정중앙에 위치하고, rev는 slow가 움직인 기록이 거꾸로 기록되어 있습니다. 이를 예시로 표현하면
1 -> 2 -> 3 -> 4 -> 5 -> 4 -> 3 -> 2 -> 1
에서 빠른 런너(fast)가 1 -> 3 -> 5 -> 3 -> 1의 경로로 이동했을 때, 느린 런너(slow)는 1 -> 2 -> 3 -> 4 -> 5로 이동한 상태이며, rev는 4 -> 3 -> 2 -> 1 -> None 이 연결된 연결리스트가 됩니다. 즉, 여기서 느린 런너가 한칸씩 앞으로 가며 rev와 비교해서 모든 요소가 같다면 이는 Palindrome을 이룬다고 할 수 있습니다.
역순 연결 리스트 구성(rev)
while fast and fast.next: # fast.next가 없다면 fast.next.next에서 에러가 발생하므로 둘다 체크해야합니다.(개수가 짝수인지 홀수인지 모르기 때문)
fast = fast.next.next # 두 칸씩 이동
rev, rev.next, slow = slow, rev, slow.next
slow = slow.next는 slow가 한 칸씩 이동한다는 것입니다.
rev, rev.next = slow, rev는 slow가 1 -> 2로 이동할 때, rev는 2 -> 1이 됩니다. 정확히 말하면 rev라는 것은 slow의 현재보다 한칸 이전 상태이며, rev.next에는 현재 slow의 노드를 할당합니다.
if fast:
slow = slow.next
fast가 남아있다는 뜻은 위의 반복문에서 마지막에 fast는 존재하지만 fast.next는 존재하지 않아서 탈출한다는 뜻이고, fast가 두칸 씩 움직일 때 해당상황이 발생하기 위해서는 홀수개의 런너가 존재해야합니다. 그래서 가운데 값에 slow가 멈추는 상황을 방지하기 위해 한 칸 더 움직입니다. 위의 예시를 기준으로 생각해보면, rev는 4에 있는 상황이지만 slow는 가운데 값인 5에 멈춰있습니다. 그래서 한 칸 더 앞으로 움직여 4의 노드로 움직여주는 작업이 필요한 것입니다.
팰린드롬을 판별합니다.
while rev and rev.val == slow.val:
slow, rev = slow.next, rev.next