참고: 책 <<파이썬 알고리즘 인터뷰>> pp.199-210
연결 리스트에 대해 다시 공부했다.
처음 연결 리스트를 접했을 때는 잘 이해가 되지 않았었다. 참고한 책에서는 연결 리스트를 클래스로 구현하여 관련 문제를 풀이했는데, 당시 내가 '참조'에 익숙하지 않아 연결 리스트의 구조를 직관적으로 받아들이지 못했기 때문이다.
보통 알고리즘 문제를 풀 때 클래스 구현을 잘 안 하는데, LeetCode의 자료구조 문제는 클래스를 활용하여 풀도록 문제가 구성되어 있어서 자료구조 이해에 크게 도움이 되는 것 같다. 자료구조 문제는 LeetCode에서 풀어보는 것을 추천한다!
연결 리스트란?
시간 복잡도
O(n)
의 시간이 소요된다. 그 이유는 연결 리스트의 첫 번째 노드(head)에서 시작하여 원하는 인덱스까지 하나하나 읽어나가야 하기 때문이다.O(1)
에 가능하다.런너(Runner) 기법
fast
와 slow
두 개의 포인터를 사용한다.문제: LeetCode 234
easy 난이도의 문제지만, 연결 리스트 클래스를 파이썬 리스트로 바꾸지 않고 그대로 사용하여 풀어보는 게 처음이라면 결코 쉬운 문제는 아니라고 생각한다.
문제 해결을 위해 3가지를 이해해야 한다.
class ListNode(object):
def __init__(self, val=0, next=None):
self.val = val
self.next = next
인스턴스 변수 val
은 해당 노드의 값이다.
인스턴스 변수 next
는 다음 노드를 참조한다.
노드 개수가 홀수일 경우 fast가 끝에 도달했을 때 slow는 연결 리스트의 중앙 노드를 가리킨다. 팰린드롬 여부를 검사하는 것이 목표이기에 이때 slow를 한 번 더 이동시켜주는 작업이 필요하다.
아래 두 코드의 차이를 이해해야 한다.
# slow: 2->3
# rev: 1
# 1
slow, rev, rev.next = slow.next, slow, rev
'''
slow: 3
rev: 2->1
'''
# 2
rev, rev.next = slow, rev
slow = slow.next
'''
slow: 1
rev: 2->1
'''
1번과 2번 코드는 결과가 다르다. 2번의 경우 첫 번째 코드에서 rev와 slow가 같은 노드를 참조하도록 되어 있기 때문에 두 번째 코드가 실행되면 slow는 3이 아닌 rev.next가 가리키는 1이 된다.
아래는 전체 코드이다.
# Definition for singly-linked list.
class ListNode(object):
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution(object):
def isPalindrome(self, head):
fast = head
slow = head
rev = ListNode()
while fast and fast.next:
fast = fast.next.next
slow, rev, rev.next = slow.next, slow, rev
# 노드 개수가 홀수일 경우
if fast:
slow = slow.next
while slow:
if slow.val != rev.val:
break
slow, rev = slow.next, rev.next
else:
return True
return False