Task

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

Example

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

Solution

  • 常见排序算法在不同容器上的复杂度: leetcode148_1
  • 根据上图,需要O(nlogn)的时间复杂度只能merge sort,使用分治实现
  • 两种方法:自顶向下(递归)和自底向上(循环) leetcode148_2

top-down归并排序

  • 每次进入递归时判断是否终止,终止条件为该链表为空或只有一个节点
  • 将链表等分为两个链表,方法是用快慢指针,快指针走完时慢指针走到中点,以此分开
  • 对左右两个链表分别递归,递归完成后是两个已分别排好序的链表,将它们合并
  • 计算两个已排序的链表的merge,见LeetCode21
  • 时间复杂度:每次merge两个已排序链表是O(n),递归深度O(logn),总时间复杂度O(nlogn)
  • 空间复杂度:递归深度O(logn),空间复杂度O(nlogn)
  • 实现:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Runtime: 52 ms, faster than 66.92% of C++ online submissions for Sort List.
// Memory Usage: 27.1 MB, less than 5.00% of C++ online submissions for Sort List.
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        //递归终点:空链表或只有一个node
        if(!head || !(head->next))
            return head;
        //定义快慢指针,快指针初始化为慢指针的next,为了处理链表只有2个node的情形
        ListNode *slow=head,*fast=head->next;
        //快慢指针移动,将链表分为两个
        while(fast && fast->next){
            fast=fast->next->next;
            slow=slow->next;
        }
        //产生两个链表
        ListNode *mid=slow->next;
        slow->next=nullptr;
        //将两个链表分别递归排序后merge
        return mergeTwoLists(sortList(head),sortList(mid));
    }
private:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        //建立dummy节点和添加结果用的tail指针
        ListNode *dummy=new ListNode;
        ListNode *tail=dummy;
        //当两个链表都未走到头时,比较两个节点
        while(l1 && l2){
            //保证l1节点比l2节点小
            if(l1->val > l2->val) swap(l1,l2);
            //将更小的节点放入结果链表中,继续走
            tail->next=l1;
            tail=tail->next;
            l1=l1->next;
        }
        //循环终止时可能有一个链表尾部非空,直接放到结果后面
        tail->next=l1?l1:l2;
        return dummy->next;
    }
};

bottom-up归并排序

  • 使用循环代替递归
    • 第一轮循环:每次取1个节点当小链表,相邻两个小链表merge
    • 第二轮循环:每次取2个节点当小链表,相邻两个小链表merge(由于每个小链表都是第一轮中的相邻小链表,故第二轮的每个小链表都已排好序)
    • 第三轮循环:每次取4个节点当小链表,相邻两个小链表merge(由于每个小链表都是第二轮中的相邻小链表,故第三轮的每个小链表都已排好序)
    • 直到小链表长度即为整个链表长度时,整个链表已被排好序
  • 辅助函数move_n:从链表中取出前n个节点作为小链表,剩下的作为另一部分
  • 辅助函数mergeTwoLists:修改LeetCode21的代码使其返回merge结果的首节点和尾节点(便于bottom-up)
  • 时间复杂度:每次merge两个已排序链表是O(n),循环次数是O(logn),总时间复杂度O(nlogn)
  • 空间复杂度:除dummy外未新建节点,且操作是循环而非递归,占用空间不受递归深度影响,空间复杂度是O(1)
  • 实现:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// Runtime: 44 ms, faster than 75.98% of C++ online submissions for Sort List.
// Memory Usage: 14.2 MB, less than 15.00% of C++ online submissions for Sort List.
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if(!head || !(head->next)) return head;
        ListNode *dummy=new ListNode(0,head);
        //记录链表长度
        int len=1;
        while(head=head->next)
            ++len;
        //每轮循环对两个相邻的长为i的小链表排序
        //每轮for循环i长度翻倍,即每轮for循环中长度为i的小链表都已排好序
        for(int i=1;i<len;i*=2){
            ListNode *l1=nullptr,*l2=nullptr,*rest=dummy->next,*tail=dummy;
            //当剩下链表非空时,继续取小链表并merge
            while(rest){
                //将剩下的链表分为l1、l2、rest三份
                //由于bottom-up,l1和l2都在上一个for循环中已排好序
                l1=rest;
                l2=move_n(l1,i);
                rest=move_n(l2,i);
                //将排好序的l1和l2进行merge
                auto merged=mergeTwoLists(l1,l2);
                //将merged的链表左侧加入大链表
                tail->next=merged.first;
                //将merged的右侧
                tail=merged.second;
            }
        }
        return dummy->next;
    }
private:
    //从链表中取出前n个节点作为小链表,剩下的作为另一部分
    ListNode *move_n(ListNode *head,int n){
        //移动指针至分割点
        while(--n && head)
            head=head->next;
        //将剩余部分取出
        ListNode *rest=(head)?(head->next):nullptr;
        //将头n个节点的尾指针置为nullptr,两个链表分开
        if(head) head->next=nullptr;
        return rest;
    }
    //将两个排好序的链表merge,返回merge后的首尾节点指针
    pair<ListNode *,ListNode *> mergeTwoLists(ListNode* l1, ListNode* l2) {
        //建立dummy节点和添加结果用的tail指针
        ListNode *dummy=new ListNode;
        ListNode *tail=dummy;
        //当两个链表都未走到头时,比较两个节点
        while(l1 && l2){
            //保证l1节点比l2节点小
            if(l1->val > l2->val) swap(l1,l2);
            //将更小的节点放入结果链表中,继续走
            tail->next=l1;
            tail=tail->next;
            l1=l1->next;
        }
        //循环终止时可能有一个链表尾部非空,直接放到结果后面
        tail->next=l1?l1:l2;
        while(tail->next)
            tail=tail->next;
        return {dummy->next,tail};
    }
};