Medium草稿★★★★★O(n) 時間 · O(1) 空間
Linked ListTwo Pointers
Patterns↔️ 雙指針⛓️ 鏈結串列操作
尚未複習過

解法已隱藏 — 先讀題目敘述、自己想想看,再點上方按鈕揭曉。

19Remove Nth Node From End of ListLinked ListMediumLinked List

給定一個鏈結串列的頭節點,移除倒數第 n 個節點,並回傳頭節點。要求只用一次遍歷完成。

Example:

Input: head = [1,2,3,4,5], n = 2 Output: [1,2,3,5]

Intuition

TIP

核心思路:讓快指針先走 n 步,然後快慢指針同時走,當快指針到底時,慢指針恰好在目標節點的前一個。

Approaches

1. Two-Pass (Count Length) — O(n) / O(1)
  • Idea: 第一次遍歷計算長度 L,第二次遍歷走到第 (L - n) 個節點進行刪除
  • Time: O(n) - 兩次遍歷
  • Space: O(1)
class Solution {
    fun removeNthFromEnd(head: ListNode?, n: Int): ListNode? {
        val dummy = ListNode(0)
        dummy.next = head

        var length = 0
        var curr = head
        while (curr != null) {
            length++
            curr = curr.next
        }

        curr = dummy
        for (i in 0 until length - n) {
            curr = curr!!.next
        }
        curr!!.next = curr.next?.next

        return dummy.next
    }
}
⭐ 2. One-Pass (Two Pointers) — O(n) / O(1)
  • Idea: 使用 dummy head,快指針先走 n+1 步,然後快慢指針同時走,快指針到底時慢指針在目標前一個節點
  • Time: O(n) - 只遍歷一次
  • Space: O(1)
class Solution {
    fun removeNthFromEnd(head: ListNode?, n: Int): ListNode? {
        val dummy = ListNode(0)
        dummy.next = head
        var fast: ListNode? = dummy
        var slow: ListNode? = dummy

        // 快指針先走 n + 1 步
        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
    }
}

WARNING

Dummy head 是關鍵:當要刪除的是第一個節點(n == list length)時,沒有 dummy head 會需要特殊處理。

🔑 Takeaways