在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
解析:对时间空间复杂度有要求,那么就必须思考用什么方法进行排序
对于时间复杂度O(nlogn) ,可以考虑归并排序或者快速排序
而归并排序在链表的情况下,可以优化为O(1)的空间复杂度。
因此本题采用归并排序
归并排序的要点在于两点,分治的拆分链表,以及合并两个链表
我们分别处理这两个功能,并归纳为函数
对于自顶向下的归并排序,通常采用递归调用,每个递归函数的功能便是找到目前的中心,并对两端进行递归调用,最后合并后返回上一层。
而这样的整个过程,必然会产生函数的递归调用,并因此消耗额外的函数栈空间。不符合题目要求的O(1)空间复杂度要求。
因此我们需要将递归修改为迭代。
我们转变方向,自下而上的进行归并排序。
对自顶向下的一般归并方法进行分析,发现最终的函数调用顺序是:
对1、2两个节点进行归并
对3、4两个节点进行归并
对1-4四个节点进行归并
对5、6两个节点进行归并
对7、8两个节点进行归并
对5-8四个节点进行归并
对1-8八个节点进行归并。。。
可以看出,
整个过程其实是先每两个一组进行合并,然后每四个一组,八个,十六个
每次合并的大小扩张为原来的两倍。
因此我们可以用迭代的方式手动模拟递归。两两合并、然后四个一组合并。。。
下面是最后代码
class Solution {
public:
ListNode* sortList(ListNode* head) {
//虚拟头节点,用来定位
ListNode p(0);
ListNode *pre = &p;
pre->next = head;
//获取链表长度
ListNode *tmp = head;
int n=0;
while(tmp){
++n;
tmp = tmp->next;
}
//按照1,2,4,8...的数量开始层层归并排序
for(int i = 1;i<n;i*=2){
ListNode *cur = pre->next; //目前的头节点
ListNode *tail = pre; //归并后链表的连接位置
while(cur){
ListNode *left = cur; //第一段链表的起点
ListNode *right = cut(left,i); //第二段链表的起点
cur = cut(right,i); //cur指向后序链表
//此时left,right 分别指向两条长度为i的,从原链表上切下的子链表
tail->next = merge(left,right); //归并两条子链表,连接至tail节点后
while(tail->next) tail = tail->next; //将连接位置后移
}
}
return pre->next;
}
//将链表从cur节点开始切下num个节点,返回被切下来节点之后的节点
ListNode* cut(ListNode* cur,int num){
ListNode* tmp = cur;
while(tmp && --num) tmp = tmp->next;
if(!tmp) return nullptr;
ListNode* res = tmp->next;
tmp->next = nullptr;
return res;
}
//将给定的两个有序链表合并,返回新链表的头节点
ListNode* merge(ListNode* left,ListNode* right){
ListNode p(0);
ListNode *pre = &p;
ListNode *head = &p;
while(left && right){
if(left->val > right->val){
pre->next = right;
right = right->next;
pre = pre->next;
}
else{
pre->next = left;
left = left->next;
pre = pre->next;
}
}
pre->next = (left?left:right);
return head->next;
}
};
来源:CSDN
作者:二次元憨批
链接:https://blog.csdn.net/qq_37292201/article/details/104059604