사이클 감지 (Cycle Detection)
문제 설명
연결 리스트가 순회 중에 동일한 노드를 두 번 이상 방문하면 해당 연결 리스트는 사이클이 포함되어 있다고 합니다. 연결 리스트의 헤드(head)가 주어졌을 때, 사이클이 포함되어 있는지 확인하세요. 만약 사이클이 있다면 1을 반환하고, 없으면 0을 반환하세요.
입력 형식
- 연결 리스트의 헤드(head)가 함수의 매개변수로 주어집니다.
- 연결 리스트의 노드들은 아래와 같은 구조를 가집니다:
- int data: 노드의 값
- SinglyLinkedListNode next: 다음 노드를 가리키는 포인터
출력 형식
- 연결 리스트에 사이클이 있다면 1을 반환합니다.
- 연결 리스트에 사이클이 없다면 0을 반환합니다.
제약 조건
- 연결 리스트의 크기: 0≤list size≤10000
예제
입력 1
헤드가 다음 노드를 가리킴: 1 → 2 → 3 → NULL
출력 1
0
(사이클이 없음)
입력 2
헤드가 다음 노드를 가리킴: 1 → 2 → 3 → 1 (사이클 존재)
출력 2
1
(사이클이 있음)
자료구조 문제로 연결 리스트에 순환되는 부분이 있는지 없는지 확인하는 문제이다. 이를 해결하기 위해 다음과 같은 방법을 이용했다.
1. visit_list에 지나간 주소를 저장한다.
2. 리스트를 순회하며 현재 노드가 visit_list에 이미 존재한다면 순환이 존재한다는 뜻이므로 1을 반환
3. 현재 노드가 visit_list에 없다면 현재 노드의 주소를 visit_list에 추가
4. 순회를 완료했는데 사이클이 없다면 0을 반환
코드는 다음과 같다.
def has_cycle(head):
visit_list = set()
while head:
if head in visit_list:
return 1
else:
visit_list.add(head)
head = head.next
continue
return 0
위 코드로 문제를 통과했지만, 구글링을 해보니 floyd's cycle detection algorithm이 있다는 것을 알게 되었다. 이 알고리즘은 2개의 포인터( 느린 포인터와 빠른 포인터)를 이용해서 만약 순환이 존재한다면 2개의 포인터가 만나는 지점이 있다는 부분을 활용해서 작성한 알고리즘이다.
def has_cycle(head):
slow = head
fast = head
while fast and fast.next:
slow = slow.next # 한 칸 이동
fast = fast.next.next # 두 칸 이동
if slow == fast: # 두 포인터가 만난다면 사이클 존재
return 1
return 0 # 사이클 없음
위 코드를 보면 느린 포인터(slow)는 한 번에 한 노드씩 이동하고, 빠른 포인터(fast)는 한 번에 두 노드씩 이동한다. 만약 리스트에 사이클이 있다면, 두 포인터는 결국 만나게 된다. 이 방식을 통해 추가 메모리 사용 없이 사이클을 감지할 수 있다.
처음에 작성한 visit_list를 사용한 방식도 충분히 문제를 해결할 수 있었지만, Floyd’s Algorithm은 더 효율적이고 깔끔한 방법으로 문제를 해결할 수 있다는 점에서 유용하다는 것을 배울 수 있었다.
'코딩테스트' 카테고리의 다른 글
[Hackerrank] Inserting a Node Into a Sorted Doubly Linked List (0) | 2025.01.25 |
---|---|
[Hackerrank] Ice Cream Parlor (0) | 2025.01.24 |
[Hackerrank] Merge two sorted linked lists (0) | 2025.01.22 |
[Hackerrank] Insert a node at a specific position in a linked list (0) | 2025.01.21 |
[Hackerrank] Reverse a doubly linked list (0) | 2025.01.20 |