148. 排序链表 ●●
描述
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
进阶:你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
示例
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
题解
1. 归并排序
- 自顶向下 递归
归并排序主要分为三大步:
- 找到中间节点;
- 分割链表,并进行左右递归;
- 合并左右两部分有序链表,返回合并后的头结点。
- 时间复杂度: O ( n log n ) O(n \log n) O(nlogn),其中 n 是链表的长度。
- 空间复杂度: O ( log n ) O(\log n) O(logn),其中 n 是链表的长度。空间复杂度主要取决于递归调用的栈空间。
class Solution {
public:
ListNode* mergeSort(ListNode* head){
if(head == nullptr || head->next == nullptr) return head; // 终止条件,空节点或只有一个节点
/*================== 快慢指针确定重点位置 ==================*/
ListNode* slow = head;
ListNode* fast = head;
while(fast->next != nullptr && fast->next->next != nullptr){
slow = slow->next;
fast = fast->next->next;
}
/*================== 链表分割,递归 ==================*/
ListNode* rightHead = slow->next; // 右半部分链表头结点slow->next
slow->next = nullptr; // 分割链表,指向空节点截断链表
ListNode* left = mergeSort(head); // 递归
ListNode* right = mergeSort(rightHead);
/*================== 合并两个有序链表 ==================*/
ListNode* dummyHead = new ListNode(-1); // 迭代,LC21.合并两个有序链表
ListNode* prev = dummyHead;
while(left != nullptr && right != nullptr){
if(left->val < right->val){
prev->next = left;
left = left->next;
}else{
prev->next = right;
right = right->next;
}
prev = prev->next;
}
prev->next = left == nullptr? right : left;
return dummyHead->next; // 返回合并后的链表头结点
}
ListNode* sortList(ListNode* head) {
return mergeSort(head);
}
};
- 自底向上 空间复杂度O(1)
使用自底向上的方法实现非递归归并排序,则可以达到 O(1) 的空间复杂度。
归并排序中,每次分割都是通过二分法得到链表的最小节点单元,然后再通过多轮合并得到排序结果;
每一轮合并都有固定的单元长度,即 step = 1, 2, 4, 8…;
因此,我们可以通过迭代的方法模拟这种分割合并操作。
主要思路为:
1 - 统计链表的长度 n;
2 - 子链表长度 step 从 1 开始成倍增加,不大于 n;
3 - 分割长度为 step 的左链表,并返回右链表的头结点,继续分割,得到下一次合并的头结点;
4- 合并左右链表,并连接到上一次合并的链表中,多轮合并后最终完成排序。
- 时间复杂度: O ( n log n ) O(n \log n) O(nlogn),其中 n 是链表的长度。
- 空间复杂度: O ( 1 ) O(1) O(1)。
class Solution {
public:
ListNode* merge(ListNode* left, ListNode* right){
ListNode* dummyHead = new ListNode(-1); // LC21.合并两个有序链表
ListNode* prev = dummyHead;
while(left != nullptr && right != nullptr){
if(left->val < right->val){
prev->next = left;
left = left->next;
}else{
prev->next = right;
right = right->next;
}
prev = prev->next;
}
prev->next = left == nullptr? right : left;
ListNode* head = dummyHead->next;
delete dummyHead;
return head; // 返回合并后的链表头结点
}
ListNode* cut(ListNode* head, int len){
// 从 head 开始分割长度为 len 的链表,并返回下一个链表的头结点
if(head == nullptr) return nullptr;
for(int i = 1; i < len; ++i){
if(head == nullptr) return nullptr; // 最后一个单元长度可能小于 len,因此返回 null
head = head->next;
}
if(head != nullptr){
ListNode* nextHead = head->next;
head->next = nullptr;
return nextHead; // 返回下一个链表的头结点
}else{
return nullptr;
}
}
ListNode* sortList(ListNode* head) {
if(head == nullptr || head->next == nullptr) return head;
int n = 0;
ListNode* curr = head;
while(curr != nullptr){
++n;
curr = curr->next; // 统计链表长度
}
ListNode* dummyHead = new ListNode(0, head);// 虚拟头结点
for(int step = 1; step < n; step *= 2){
// step = step * 2
ListNode* prev = dummyHead; // 上一次合并链表的尾节点 prev
ListNode* curr = dummyHead->next; // 遍历节点
while(curr != nullptr){
ListNode* head1 = curr; // 左链表头结点
ListNode* head2 = cut(head1, step); // 分割左链表,并返回右链表头结点
curr = cut(head2, step); // 分割右链表,并返回下一次合并的头结点
prev->next = merge(head1, head2); // 合并,连接链表
while(prev->next != nullptr){
prev = prev->next; // 更新合并链表的尾节点
}
}
}
return dummyHead->next; // 返回合并后的头结点
}
};
2. 快速排序
通过交换节点值来实现快速排序,并未进行节点指针操作;
每次选取下标为 n/4 的节点作为分界值,可以避免极端情况下的超时结果。
- 时间复杂度:选取分界值复杂度 O ( n ) O(n) O(n),分区复杂度 O ( n ) O(n) O(n),递归复杂度是 O ( l o g n ) O(logn) O(logn)
快速排序的最优时间复杂度和平均时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) ,最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2) 。 - 空间复杂度:递归层数 O ( l o g n ) O(logn) O(logn)
class Solution {
public:
void Qsort(ListNode* head, int cnt){
// 对从 head 开始的 cnt 个节点进行快速排序
if(head == nullptr || cnt == 0) return; // 排序结束
ListNode* curr = head;
for(int i = 0; i < cnt/4; ++i) curr = curr->next; // (随机)选取分界值
swap(head->val, curr->val);
int pivot = head->val; // 当前排序的分界值
ListNode* curr_changed = head; // 交换过的最后一个节点
curr = head->next;
int changeCnt = 0; // 节点交换的次数
for(int i = 1; i < cnt; ++i){
// 遍历待排序的所有节点
if(curr->val < pivot){
curr_changed = curr_changed->next; // 左右划分
swap(curr_changed->val, curr->val);
++changeCnt;
}
curr = curr->next;
}
swap(head->val, curr_changed->val); // 交换分界值到合适位置
Qsort(head, changeCnt); // 递归前半部分
Qsort(curr_changed->next, cnt-changeCnt-1); // 递归后半部分
}
ListNode* sortList(ListNode* head) {
int n = 0;
ListNode* curr = head;
while(curr != nullptr){
// 统计链表长度
++n;
curr = curr->next;
}
if(n <= 1) return head;
Qsort(head, n); // 排序
return head;
}
};
3. 插入排序
时间复杂度: O ( n 2 ) O(n^2) O(n2),超出时间限制。