题目:
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
进阶:
你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
/**
* 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) {}
* };
*/
// // 方法一:归并排序 自顶向下实现
// // 递归实现,递归会带来的额外空间
// // 总的时间复杂度 O(nlogn), 空间复杂度为 O(logn)
// class Solution {
// public:
// ListNode* sortList(ListNode* head)
// {
// // 1、这里是 或 的关系
// if(!head || !head->next) return head;
// ListNode* slow = head, *fast = head->next;
// while(!fast && !fast->next)
// {
// fast = fast->next->next;
// slow = slow->next;
// }
// // 2、不知道 return 该怎么写,还是没搞明白,while循环是需要把链表断成两节,所以下一步是要截断
// ListNode* mid = slow->next;
// slow->next = nullptr;
// return merge(sortList(head), sortList(mid));
// }
// private:
// ListNode* merge(ListNode* l1, ListNode* l2)
// {
// // 这里定义的 dummy 可不是指针哦,所以操作符是 . 而不是 -> 不是指针就不用解引用
// ListNode dummy(0);
// ListNode* tail = &dummy; // Q1:一个临时变量
// while(l1 && l2)
// {
// if(l1->val > l2->val) swap(l1, l2); // Q2:这里是怎么 swap 的? A:是整条链表换,1变2, 2变1
// // 经过 if 之后,l1 一定是较小的数,所以只需要对 l1 进行操作
// tail->next = l1;
// l1 = l1->next;
// tail = tail->next;
// }
// // 跳出 while 循环时,肯定是 l1 或者 l2 为空,因此把非空的的链表接在 tail后面即可
// tail->next = l1 ? l1 : l2;
// return dummy.next;
// }
// };
// 方法二:归并排序 自底向上实现
// 非递归实现,通过循环来模拟递归的过程,就不需要递归带来的额外空间了
// 总的时间复杂度 O(nlogn), 空间复杂度为 O(1)
class Solution {
public:
ListNode* sortList(ListNode* head)
{
// 边界条件 写错第二弹:是或的关系啦~~~
if (!head || !head->next) return head;
int len = 1;
ListNode* cur = head; // 通过while循环求出链表的长度
// 这里的时间复杂度为O(n)
while (cur = cur->next) ++len; // Q1:这里的写法第一次见呢
ListNode dummy(0);
dummy.next = head;
ListNode* left, *right, * tail; // tail指向的是 merge sort 之后的队尾
// 这里的时间复杂度为O(log(n)),有点二分的意思
// n 是从 1 开始,每次翻倍
for (int n = 1; n < len; n <<= 1) // n = n * n ,左移是乘以2,右移是除以2,这里长度每次翻倍
{
cur = dummy.next; // cur指向的是虚拟头节点的下一个节点,也就是 head
tail = &dummy; // tail指向虚拟头节点, tail指向的是 merge sort 之后的队尾
// 这里的时间复杂度分析,每进入一次 while 循环,就需要全部遍历一遍,所以每次都是 O(n)
while(cur)
{
// very important 1 ***** 这里要重点理解一下,因为 split 函数其实是会对链表进行阶段的,所以记录头结点就刚好可以实现
left = cur; // left 表示前 n 个元素
right = split(left, n); // right 表示接下来的 n 个元素
cur = split(right, n); // cur 分割后,就把前面 left 和 right 分割后的元素去掉,可以再次进行分组了
auto merged = merge(left, right);
// very important 2 ***** 这里 环环相扣,非常妙
tail->next = merged.first; // 先在tail 后把归并后的拼接上,这里就体现出了为什么merge的时候需要多加一个参数 tail,
tail = merged.second; // tail更新为merge中的第二个参数,一定每次要把 tail指向队尾,方便做拼接
}
}
return dummy.next;
}
private:
// 将链表截断为两段,第一段就是链表中前n个元素,第二段是后面剩下的部分,返回值是第二段的头结点
ListNode* split (ListNode* head, int n)
{
// 当 n>0 且 head 不为空时
while(--n && head) head = head->next;
ListNode* rest = head ? head->next : nullptr; // 如果head为空,那么rest赋值为空节点
if(head) head->next = nullptr; // 截断,上面通过rest已经记录了后面剩余部分是 head->next。所以从head开始截断为两部分
return rest;
}
// merge函数,将两个有序链表合并为一个有序链表
pair<ListNode*, ListNode*> merge (ListNode* l1, ListNode* l2)
{
ListNode dummy(0);
ListNode* tail = &dummy;
while(l1 && l2)
{
if(l1->val > l2->val) swap(l1, l2);
tail->next = l1;
l1 = l1->next;
tail = tail->next;
}
tail->next = l1 ? l1 : l2;
while (tail->next) tail = tail->next; // 这里与方法一的merge函数不同,多了一步 tail 要指向链表中最后并返回tail
return {dummy.next, tail};
}
};