-
Floyd’s Cycle Detection Algorithm - 투포인터 문제
https://medium.com/swlh/floyds-cycle-detection-algorithm-32881d8eaee1 플로이드의 사이클 탐지 알고리즘이란 연결 리스트에서 사이클을 감지하는 문제에서 자주 사용되는 알고리즘으로 토끼와 거북이 알고리즘으로도 불린다. 두개의 포인터를 사용해서 리스트를 탐색하는데, 하나의 포인터는 거북이처럼 한칸씩 이동하고, 나머지 포인터는 토끼처럼 m칸씩 움직인다. (2칸) 만약 사이클이 없다면, 빠른 포인터는 끝(fast.next == null)에 도달하고, 사이클이 존재한다면 느린 포인터를 결국 따라잡게 된다. (slow == fast)
시간 복잡도 : O(n)
공간 복잡도 : O(1)
1) linked-list-cycle
문제 : https://leetcode.com/problems/linked-list-cycle/
class Solution { fun hasCycle(head: ListNode?): Boolean { if(head == null) { return false } var slow: ListNode? = head.next var fast : ListNode? = head.next?.next var answer = false while(true) { if (fast == null) { answer = false break } if (fast == slow) { answer = true break } slow = slow?.next fast = fast?.next?.next } return answer } } class Solution { fun hasCycle(head: ListNode?): Boolean { var slow = head var fast = head while (fast != null && fast.next != null) { slow = slow?.next fast = fast.next?.next if (slow == fast) return true } return false } }
2) remove-nth-node-from-end-of-list
문제 : https://leetcode.com/problems/remove-nth-node-from-end-of-list
class Solution { fun removeNthFromEnd(head: ListNode?, n: Int): ListNode? { val dummy = ListNode(0) dummy.next = head var fast: ListNode? = dummy var slow: ListNode? = dummy for(i in 0..n) { fast = fast?.next } while(fast != null) { fast = fast?.next slow = slow?.next } slow?.next = slow?.next?.next return dummy.next } }
3) reorder-list
문제 : https://leetcode.com/problems/reorder-list/
주어진 리스트를 노드의 값을 바꾸지 않고 제시된 순서대로 재정렬하는 문제다.
L0 → L1 → L2 → … → Ln-1 → Ln ➡️ L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
1. 리스트를 중간에서 반으로 자르고, -> (TwoPointer 활용)
2. 후반부 리스트를 역순으로 정렬하고,
3. 반쪽 앞 리스트와 반쪽 뒤 리스트를 교차하면서 병합해야 한다.
class Solution { fun reorderList(head: ListNode?): Unit { if(head == null) { return } // 중간 지점 찾기 var slow = head var fast = head while (fast?.next != null && fast?.next?.next != null) { slow = slow?.next fast = fast?.next?.next } // 뒤쪽 리스트 역순 정렬하기 + 중간에서 자르기 var second = reverse(slow?.next) slow?.next = null // 두 리스트 병합하기 var first = head while(second != null) { val tmp1 = first?.next val tmp2 = second?.next first?.next = second second.next = tmp1 first = tmp1 second = tmp2 } } private fun reverse(head:ListNode?): ListNode? { var prev: ListNode? = null var curr = head while (curr != null) { val next = curr.next curr.next = prev prev = curr curr = next } return prev } }
merge-two-sorted-lists
문제 : https://leetcode.com/problems/merge-two-sorted-lists/
두개의 연결리스트에서 next를 변경해가면서 정렬하는 문제이다.
시간 복잡도 : O(n + m) → 리스트 길이의 합만큼 순회
공간 복잡도 : O(1) → in-place로 연결 (노드 새로 안 만듦)
class Solution { fun mergeTwoLists(list1: ListNode?, list2: ListNode?): ListNode? { var l1 = list1 var l2 = list2 val dummy = ListNode(0) var curr = dummy while(l1 != null && l2 != null) { if(l1.`val` <= l2.`val`) { curr.next = l1 l1 = l1.next }else { curr.next = l2 l2 = l2.next } curr = curr.next!! } curr.next = l1 ?: l2 return dummy.next } }
merge-k-sorted-lists
문제 : https://leetcode.com/problems/merge-k-sorted-lists/
1. PriorityQueue를 활용하는 방법
시간 복잡도 : O(n log k)
공간 복잡도 : O(1) → in-place로 연결 (노드 새로 안 만듦)
class Solution { fun mergeKLists(lists: Array<ListNode?>): ListNode? { // Priority Queue val priorityQueue = PriorityQueue<ListNode>(Comparator { a, b -> a.`val` - b.`val`}) for(listNode in lists) { listNode?.let {priorityQueue.offer(listNode)} } val dummy = ListNode(0) var curr = dummy var next = priorityQueue.poll() while(next != null) { curr.next = next next.next?.let {priorityQueue.offer(it)} curr = curr.next next = priorityQueue.poll() } return dummy.next } }
2. Divide & Conquer를 활용하는 방법
두개씩 병합하면서 점점 줄여나가는 방식으로 merge sort와 유사하다. 각 단계에서 리스트의 개수는 절반씩 줄어들고 각 병합은 O(n) 시간 (= 두 리스트 길이의 합만큼) 소요된다. 병합 단계가 logK 개이므로 (이분) 총 시간 복잡도는 O(n logK) n: 총 노드 수, k: 리스트 개수)
시간 복잡도 : O(n log k)
공간 복잡도 : O(1) → in-place로 연결 (노드 새로 안 만듦)
class Solution { fun mergeKLists(lists: Array<ListNode?>): ListNode? { // Divide & Conquer (분할정복) if(lists.isEmpty()) return null return mergeRange(lists, 0, lists.size-1) } private fun mergeRange(lists: Array<ListNode?>, left: Int, right: Int): ListNode? { if(left == right) return lists[left] val mid = (left + right) / 2 val l1 = mergeRange(lists, left, mid) val l2 = mergeRange(lists, mid + 1, right) return mergeTwoLists(l1, l2) } private fun mergeTwoLists(l1:ListNode?, l2:ListNode?):ListNode? { val dummy = ListNode(0) var curr = dummy var a = l1 var b = l2 while(a != null && b != null) { if(a.`val` <= b.`val`) { curr.next = a a = a.next } else { curr.next = b b = b.next } curr = curr.next!! } curr.next = a ?: b return dummy.next } }
'알고리즘' 카테고리의 다른 글
문자열 (0) 2025.04.17 행렬 문제 (0) 2025.04.11 그래프(DFS, BFS) 문제 (0) 2025.03.11 백준 14891) 톱니바퀴 (골드.5) (0) 2022.08.02 백준 14503) 로봇청소기 (골드.5) (0) 2022.08.02