0148. Sort List - chasel2361/leetcode GitHub Wiki

Sort a linked list in O(n log n) time using constant space complexity.

Example 1:
Input: 4->2->1->3
Output: 1->2->3->4

Example 2:
Input: -1->5->3->4->0
Output: -1->0->3->4->5

這題要求將串鍊排序,這邊先使用 Merge Sort 來解,但此法的空間複雜度不為 O(1),所以僅供參考。Merge Sort 法的概念在於將資料像二元搜尋樹一樣,先取中間值做為根,不停對半拆分直至剩下一個,再按照想要的條件合併資料,整體概念如下圖。

https://miro.medium.com/max/1086/1*4WoOrvkGX3FDNcHq2tk-2w.gif

此處需要處理的資料為串鍊,取中間值較不方便,這裡採用的方法有點類似之前 Linked List Cycle 的狀況,一樣使用 slow 與 fast 兩種移動速度不同的指標來判斷中間值,差別在於上次為了確認串鍊的長度,停止的條件設定為 slow 與 fast 相同,此處只要移動速度比 slow 快一倍的 fast 走到盡頭就表示 slow 剛好走到一半的位置,這樣只要在使用一個指標來儲存 slow 的前一個串鍊就可以了。

合併時依序將兩個相鄰的資料一一對照,將較小的串鍊接入要回傳的變數中即可。

[1] 將串鍊斷開
[2] 遞迴拆分串鍊

class Solution:
    def sortList(self, head: ListNode) -> ListNode:
        if not head or not head.next: return head
            
        pre, slow, fast = None, head, head
        while fast and fast.next:
            pre = slow
            slow = slow.next
            fast = fast.next.next        
        pre.next = None  #[1]
        n1 = self.sortList(head)  #[2]
        n2 = self.sortList(slow)  #[2]
        return self.merge(n1, n2)   
            
    def merge(self, n1, n2):
        n = ListNode(0)
        itr = n
        while n1 and n2:
            if n1.val < n2.val:
                itr.next = n1
                n1 = n1.next
            else:
                itr.next = n2
                n2 = n2.next
            itr = itr.next
        if n1: itr.next = n1
        if n2: itr.next = n2        
        return n.next

由於每次拆分是對半分,會拆 log(N) 次,最後合併時會把所有資料掃過一遍,所以整體的時間複雜度為 O(Nlog(N)),空間複雜度為 O(N)


若要符合題目要求,也就是空間複雜度為 O(1) 的話便不能使用遞迴法,此時仍然可以使用 Merge Sort ,但必須倒過來做。把原本拆分資料的過程改為一次取 2 的指數個資料排序,直至僅取兩組資料為止,詳細作法如下。

[1] tail 作為 dummy 的指標,當 tail 被修改時,dummy 也會被修改
[2] bs 受到 bitwise operator 作用,每次都多進一位 (二進位)

class Solution:
    def sortList(self, head: ListNode) -> ListNode:
        if not head or not head.next: return head        
        def getSize(head):
            count = 0
            while head:
                count += 1
                head = head.next
            return count
            
        def split(head, count):
            i = 1
            while i < count and head:
                head = head.next
                i += 1
            if not head: return None
            temp = head.next
            head.next = None
            return temp
            
        def merge(l, r, head):
            cur = head
            while (l and r):
                if l.val < r.val:
                    cur.next = l
                    l = l.next
                else:
                    cur.next = r
                    r = r.next
                cur = cur.next                
            cur.next = l or r
            while cur.next:
                cur = cur.next
            return cur
            
        size = getSize(head)
        bs = 1
        dummy = ListNode(0)
        dummy.next = head
        l, r, tail = None, None, None
            
        while bs < size:
            cur = dummy.next
            tail = dummy  #[1]
            while cur:
                l = cur
                r = split(l, bs)
                cur = split(r, bs)
                tail = merge(l, r, tail)
            bs <<= 1  #[2]
        return dummy.next

這個做法可以保持時間複雜度為 O(Nlog(N)),且空間複雜度下降至 O(1),但實際上跑出來的速度似乎比前面的方法慢了一些。