0%
Loading ...

【Leetcode 筆記】143. Reorder List

題目解釋

給你一個 single linked list 的 head。

該 linked list 可以表示為: L0 → L1 → … → Ln - 1 → Ln
將清單重新排序為以下形式: L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

您不能修改 linked list node 中的值。只能更改 node 本身。

範例

  • 輸入: list1 = [1, 2, 3, 4]
  • 輸出: [1, 4, 2, 3]

輸出就是合併後的 linked list。

最佳解法

看的出來是拆成兩條再 merge,所以我們必須要想辦法獲得

  • 左:L0 -> L1 -> ..
  • 右:Ln -> Ln-1 -> ...

左邊好辦,但右邊先反轉 linked list 才能用,
剩下就是 merge 了。

第一段:快慢指針法(Fast-slow Pointers)

說來容易,但我們要怎麼在 O(n) 時間內把 linked list 拆成左右兩邊呢?
這時候就需要用到 快慢指針法(Fast-slow Pointers)

想像我們有兩隻動物:兔子烏龜
兔子 每次走兩步,烏龜 每次只走一步…
咦?是不是等 兔子 碰到底的時候就 烏龜 就正好在中間呢?

沒錯,所以我們只需要確保 fast 已經碰到底就可以完成這件事,
總之我們拿簡單的例子畫圖推導看看。

鏈表長度為奇數的情況

初始化: fastslow 都指在 head

         [fast]
           1 -> 2 -> 3 -> 4 -> 5
         [slow]

第一次執行,slow = slow.next, fast = fast.next.next

                   [fast]
           1 -> 2 -> 3 -> 4 -> 5
              [slow]

第二次執行,slow = slow.next, fast = fast.next.next

                             [fast]
           1 -> 2 -> 3 -> 4 -> 5
                  [slow]

此時,fast 已經觸底 (fast.next = None),所以停止循環。

鏈表長度為偶數的情況

那你說還有一個 while fast 的條件句?再推一次啊 XD

初始化: fastslow 都指在 head

         [fast]
           1 -> 2 -> 3 -> 4
         [slow]

第一次執行,slow = slow.next, fast = fast.next.next

                   [fast]
           1 -> 2 -> 3 -> 4
              [slow]

第二次執行,slow = slow.next, fast = fast.next.next

                              [fast]
           1 -> 2 -> 3 -> 4 -> None
                   [slow]

此時,fast 本人已經指到空的地方 (fast = None),所以停止循環。

如果你心想,為什麼 4 後面會多一個 None
那你肯定必須再回頭看看 ListNode class 的 constructor:

# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

這樣應該懂了吧 ~ 每個 Node 本來預設的 next 就是 None

第二段:反轉右半部鏈表

還記得 【Leetcode 筆記】 206. Reverse Linked List 嗎?
不記得的話…我也就不再贅述了 😛

⚠️ 注意

在開始 reverse 之前,我們還需要先把 linked list 給拆兩半!
記得要先定義右邊的新 head,right_head = slow.next,因為 slow.next 就是中間,
定義好之後才可以把人家拆掉,拆兩半的方式就是 slow.next = None

如此一來左邊就會是 head ~ slow,右邊是 slow.next ~ 尾巴 (fast)

第三段:合併鏈表

經過了一番努力,我們現在已經有這兩條 linked list 了。

  • 左:L0 -> L1 -> ..
  • 右:Ln -> Ln-1 -> ...

剩下就是要把他穿針引線變成 L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 的形式,步驟如下:

  1. 先把左右兩半的 next 都存下來
  2. 左邊的 next 穿到右邊
  3. 右邊的 next 穿到原先左邊的 next
  4. 左右指標都前進一格

程式碼

class Solution:
    def reorderList(self, head: Optional[ListNode]) -> None:
        """
        Do not return anything, modify head in-place instead.
        """
        # 1. Slow and fast pointer
        slow, fast = head, head.next

        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next

        # 2. Split two linked list
        right_head = slow.next
        slow.next = None

        ## reverse right side linked list
        prev = None
        curr = right_head
        while curr:
            next_node = curr.next
            curr.next = prev
            prev = curr
            curr = next_node

        # 3. Merge two linked list
        left_head, right_rev_head = head, prev

        while right_rev_head:
            left_next_node = left_head.next
            right_next_node = right_rev_head.next

            left_head.next = right_rev_head
            right_rev_head.next = left_next_node

            left_head = left_next_node
            right_rev_head = right_next_node

時間複雜度

O(n),n 是 linked list 的元素個數。

空間複雜度

O(n)

結語

快慢指針法(Fast-slow Pointers)算是解題中蠻重要的概念。

他還有很多應用,
像是 Floyd 判圈演算法 (Floyd's cycle-finding algorithm)
又稱 龜兔賽跑演算法(Tortoise and the hare algorithm)
是用來檢查 graph 有沒有出現 cycle 的現象,
如果有的話兩個 pointer 必定會在某處相交 🐰 💥 🐢。

數據女巫 𝔻.𝕡𝕪𝕤 🔮

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.