一、問題描述
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 Two Sorted Lists,只不過之前由加上了歸併。
即:首先將鏈表分割成兩部分,然後對這兩部分進行排序。
鏈表數據的排序,時間複雜度控制在O(n log n)以內;空間複雜度爲常數,即O(1),不允許使用額外的空間。
首先是鏈表排序,不能像數組一樣隨意的訪問元素。歸併排序,時間複雜度爲O(n log n),滿足要求。空間複雜度,數組下的歸併排序爲O(n),即建立一個數組來存放排好的數。但在鏈表數據結構下,空間複雜度爲O(1)。因爲鏈表的節點從一開始給定後就不在變更,在排序過程中只是每個節點指向位置不同,即指針變化,而節點數不變。
歸併排序的核心思想就是:將原數組分開並遞歸此步驟,直到只有1個數時,開始比較排序併合並,即歸併。在鏈表中也類似,從head節點往後移動,找到中間節點並把鏈表一分爲二,遞歸此步驟。
具體是:設置快速指針fast和慢速指針slow,fast跑兩步slow跑一步,當fast跑的最後一個節點時,slow到達middle處。遞歸上述步驟,歸併時將節點重新排列。
注:鏈表,無new ListNode()代碼便不生成新節點,沒有額外空間。當使用ListNode* p創建新的鏈表指針時,並沒有建立新節點沒有增加額外空間,只是爲了改變已有節點的指向(空間複雜度爲O(1))。
三、代碼實現
/**
* 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 || !head->next) return head;
ListNode *slow = head, *fast = head, *pre = head;
while (fast && fast->next)
{
pre = slow;
slow = slow->next;
fast = fast->next->next;
}//退出循環時,fast或fast->next = nullptr,slow指向中部位置
pre->next = NULL; //將左右子鏈表斷開處
return mergeTwoLists(sortList(head), sortList(slow)); //歸併左右子鏈表
}
private:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1 == NULL && l2 == NULL) return NULL;
else if(l1 != NULL && l2 == NULL) return l1;
else if(l1 == NULL && l2 != NULL) return l2;
else {
ListNode* preHead = new ListNode(0);
//preHead 需要移動,因此使用resultHead來先保存下
ListNode* resultHead = preHead;
while( l1!=NULL && l2!=NULL){
//同時不爲空,那就比較。然後將小的添加到preHead的尾部
if(l1->val < l2->val){
preHead->next = l1;
preHead = preHead->next;
l1 = l1->next;
}else{
preHead->next = l2;
preHead = preHead->next;
l2 = l2->next;
}
//while結束,說明有一個爲空了,那就把不爲空的直接加上
if(l1!=NULL) preHead->next = l1;
if(l2!=NULL) preHead->next = l2;
}
return resultHead->next;
}
}
};