1. 程式人生 > 實用技巧 >歸併排序(leetcode148)

歸併排序(leetcode148)

148. 排序連結串列

給你連結串列的頭結點 head ,請將其按 升序 排列並返回 排序後的連結串列

進階:

  • 你可以在 O(n log n) 時間複雜度和常數級空間複雜度下,對連結串列進行排序嗎?

示例 1:

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

示例 2:

輸入:head = [-1,5,3,4,0]
輸出:[-1,0,3,4,5]

提示:

  • 連結串列中節點的數目在範圍 [0, 5 * 104]
  • -105 <= Node.val <= 105

題解

參考

思路

看到時間複雜度的要求,而且是連結串列,歸併排序比較好做。

都知道歸併排序要先歸(二分),再並。兩個有序的連結串列是才比較容易合併的。

二分到不能再二分,即遞迴壓棧到了底部,鏈只有一個結點,本身就是有序的,於是在遞迴出棧時進行合併。

// 虛擬碼
func sortList (head) {
	對連結串列進行二分
	l = sortList(左鏈) // 已排序的左鏈
	r = sortList(右鏈) // 已排序的右鏈
	merged = mergeList(l, r) // 進行合併
	return merged		// 返回合併的結果給父呼叫
}

二分、merge 函式

  • 二分,用快慢指標法,快指標走兩步,慢指標走一步,快指標越界時,慢指標正好到達中點,只需記錄慢指標的前一個指標,就能斷成兩鏈。

  • merge 函式做的事是合併兩個有序的左右鏈

    • 設定虛擬頭結點,用 prev 指標去“穿針引線”,prev 初始指向 dummy

    • 每次都確定 prev.Next 的指向,並注意 l1 / l2指標的推進,和 prev 指標的推進

    • 最後返回 dummy.Next ,即合併後的鏈。

程式碼實現(golang)

節點的定義:

//Definition for singly-linked list.
type ListNode struct {
	Val int
	Next *ListNode
 }

MERGESORT:

// 把無序陣列變為有序陣列
func sortList(head *ListNode) *ListNode {
	if head == nil || head.Next == nil { // 遞迴的出口,不用排序 直接返回
		return head
	}
	slow, fast := head, head // 快慢指標
	var preSlow *ListNode    // 儲存slow的前一個結點
	for fast != nil && fast.Next != nil {
		preSlow = slow
		slow = slow.Next      // 慢指標走一步
		fast = fast.Next.Next // 快指標走兩步
	}
    
	preSlow.Next = nil  // 斷開,分成兩鏈
    // 分治,把左邊的無序陣列變為有序陣列
	l := sortList(head) // 已排序的左鏈
    // 分治,把右邊的邊的無序陣列變為有序陣列
	r := sortList(slow) // 已排序的右鏈
	return mergeList(l, r) // 合併已排序的左右鏈,一層層向上返回
}

MERGE

兩種寫法:

  • 迴圈實現分治

    // 非遞迴寫法
    func mergeList(l1, l2 *ListNode) *ListNode {
    	dummy := &ListNode{Val: 0}   // 虛擬頭結點
    	prev := dummy                // 用prev去掃,先指向dummy
    	for l1 != nil && l2 != nil { // l1 l2 都存在
    		if l1.Val < l2.Val {   // l1值較小
    			prev.Next = l1 // prev.Next指向l1
    			l1 = l1.Next   // 考察l1的下一個結點
    		} else {
    			prev.Next = l2
    			l2 = l2.Next
    		}
    		prev = prev.Next // prev.Next確定了,prev指標推進
    	}
    	if l1 != nil {    // l1存在,l2不存在,讓prev.Next指向l1
    		prev.Next = l1
    	}
    	if l2 != nil {
    		prev.Next = l2
    	}
    	return dummy.Next // 真實頭結點
    }
    
  • 遞迴實現分治

    // 遞迴寫法,l1,l2均為有序陣列,將兩個有序數組合併為1個有序陣列
    func mergeList(l1, l2 *ListNode) *ListNode {
        if l1==nil {
            return l2
        }
        if l2==nil {
            return l1
        }
        if l1.Val > l2.Val{
            l2.Next = mergeList(l1, l2.Next)
            return l2
        }
        l1.Next = mergeList(l2, l1.Next)
        return l1
    }
    

MERGE和MERGESORT的演算法描述

MERGESORT(A)

  1. if len(A) <= 1 return A
  2. q \(\leftarrow\) \(\frac{len(A)}{2}\)
  3. L \(\leftarrow\) A[0...q], R \(\leftarrow\) A[q + 1...len(A) - 1]
  4. L \(\leftarrow\) MERGESORT(L)
  5. R \(\leftarrow\) MERGESORT(R)
  6. return MERGE(L,R)

MERGE(L,R)

  1. i \(\leftarrow\) 0; j \(\leftarrow\) 0; aux \(\leftarrow\) [ ]
  2. while i < len(L) and j < len(R)
  3. if L[i] \(\leq\) R[j]
  4. ​ aux:add(L[i]) ; i \(\leftarrow\) i + 1
  5. else aux:add(R[j]) ; j \(\leftarrow\) j + 1
  6. if i == len(L)
  7. ​ aux.add(R[j : len(R) - 1])
  8. else aux.add(L[i : len(L) - 1])
  9. return