1. 程式人生 > 實用技巧 >力扣-148-排序連結串列

力扣-148-排序連結串列

傳送門

題目分析:題目要求時間複雜度為$O(nlogn)$,空間複雜度為$O(1)$,根據時間複雜度,我們自然能想到二分,故這裡要用到歸併排序。對連結串列的排序,可以通過修改指標來更改節點順序,無需像陣列一樣額外開闢儲存空間。歸併排序有遞迴和非遞迴的做法,這裡採用遞迴的做法。

首先,講一下什麼是歸併排序?

歸併排序採用了分治法的思想,對於一個很大的數列的排序,將其對等分為左右兩個子數列進行排序,按照遞迴的思想繼續對等劃分,直至數列中只有一個元素(一個元素就不用排序了)。然後再一層一層地合併(注意這裡合併是按照小的再前進行的)。

具體的做法如下:

  • 分割$cut$環節: 找到當前連結串列中點,並從中點將連結串列斷開(以便在下次遞迴$cut$時,連結串列片段擁有正確邊界);

    我們使用$fast$和$slow$快慢雙指標法,奇數個節點找到中點,偶數個節點找到中心左邊的節點。
    找到中點$slow$後,執行$slow->next = NULL$將連結串列切斷。
    遞迴分割時,輸入當前連結串列左端點$head$和中心節點$slow$的下一個節點$temp$(因為連結串列是從$slow$切斷的)。
    $cut$遞迴終止條件: 當$head.next == NULL$時,說明只有一個節點了,直接返回此節點。

  • 合併$merge$環節: 將兩個排序連結串列合併,轉化為一個排序連結串列。
    雙指標法合併,建立輔助$ListNode* h $作為頭部。
    設定兩指標$ left$, $right$ 分別指向兩連結串列頭部,比較兩指標處節點值大小,由小到大加入合併連結串列頭部,指標交替前進,直至新增完兩個連結串列。

    返回輔助$ListNode* h$作為頭部的下個節點 $h->next$。
    時間複雜度$O(l + r)$,$l$, $r$ 分別代表兩個連結串列長度。

  • 注意:如果$head==NULL$或者$head->next==NULL$時直接返回$head$即可。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public: ListNode* sortList(ListNode* head) { if (head == NULL || head->next == NULL) return head; //尋找連結串列的中點:連結串列有奇數個節點,slow返回List的中點;連結串列有偶數個節點,slow返回List中心的左側 ListNode* fast = head->next; ListNode* slow = head; while(fast != NULL && fast->next != NULL) { slow = slow->next; fast = fast->next->next;//快指標指到連結串列末尾時,慢指標剛好指到連結串列中間 } ListNode* temp = slow->next; slow->next = NULL; //將左連結串列和右連結串列分別排序 ListNode* left = sortList(head); ListNode* right = sortList(temp); ListNode* h = new ListNode(0); ListNode* result = h; //合併 while(left != NULL && right != NULL) { if (left->val < right->val) { h->next = left; left = left->next; } else { h->next = right; right = right->next; } h = h->next; } h->next = left == NULL? right:left; return result->next; } };