148.排序链表
(归并排序) 时间:O(nlogn),空间O(1)时间:O(nlogn),空间O(1)
自顶向下递归形式的归并排序,由于递归需要使用系统栈,递归的最大深度是 logn,所以需要额外O(logn) 的空间。
所以我们需要使用自底向上非递归形式的归并排序算法。
基本思路是这样的,总共迭代 logn 次:
第一次,将整个区间分成连续的若干段,每段长度是2:[a0,a1],[a2,a3],…[an−1,an−1][a0,a1],[a2,a3],…[an−1,an−1], 然后将每一段内排好序,小数在前,大数在后;
第二次,将整个区间分成连续的若干段,每段长度是4:[a0,…,a3],[a4,…,a7],…[an−4,…,an−1][a0,…,a3],[a4,…,a7],…[an−4,…,an−1],然后将每一段内排好序,这次排序可以利用之前的结果,相当于将左右两个有序的半区间合并,可以通过一次线性扫描来完成;
依此类推,直到每段小区间的长度大于等于 nn 为止;
另外,当 nn 不是2的整次幂时,每次迭代只有最后一个区间会比较特殊,长度会小一些,遍历到指针为空时需要提前结束。
时间复杂度分析:整个链表总共遍历 lognlogn 次,每次遍历的复杂度是 O(n)O(n),所以总时间复杂度是 O(nlogn)O(nlogn)。
空间复杂度分析:整个算法没有递归,迭代时只会使用常数个额外变量,所以额外空间复杂度是 O(1)
#include<iostream>
using namespace std;
struct ListNode
{
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution
{
public:
ListNode* sortList(ListNode* head)
{
/*
自顶向下递归形式的归并排序,由于递归需要使用系统栈,递归的最大深度是 lognlogn,所以需要额外 O(logn)O(logn) 的空间。
所以我们需要使用自底向上非递归形式的归并排序算法。 O(nlogn) O(1)
*/
auto dummy = new ListNode(-1); //定义一个虚拟结点
dummy->next = head;
int n = 0;
for(auto p = head;p;p=p->next) //求出链表的长度n
n++;
for(int i = 1;i<n;i *= 2) //i从第一个开始遍历
{
auto cur = dummy;
for(int j=0; j+i<n; j+=i*2) //枚举每一对
{
auto left = cur->next, right = cur->next; //l表示左边那一对的起点,r表示右边那一对的起点
for(int k = 0;k<i;k++)
{
right = right->next;
}
int l = 0,r = 0;
while(l<i && r<i && right)
{
if(left->val <= right->val)
{
cur->next = left;
cur = left;
left = left->next;
l++;
}
else
{
cur->next = right;
cur=right;
right = right->next;
r++;
}
}
while(l<i)
{
cur->next = left;
cur = left;
left = left->next;
l++;
}
while(r<i && right)
{
cur->next = right;
cur=right;
right = right->next;
r++;
}
cur->next = right;
}
}
return dummy->next;
}
};