题目
在 O(nlog n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例1
输入: 4->2->1->3
输出: 1->2->3->4
示例2
输入: -1->5->3->4->0
输出: -1->0->3->4->5
思路
- 链表的特性使得链表的操作只能老老实实从左到右遍历。但要实现O(nlogn)的时间复杂度,得使用到归并的思想。遍历第一遍的时候,将第1、2个结点合并,3、4个结点合并…遍历第二遍的时候,将1、2、3、4个结点合并,5、6、7、8个结点合并…遍历第三遍的时候,将1、2、3、4、5、6、7、8个结点合并…
- 可见,每次合并的step从1开始不停的翻倍,这样遍历的次数就位logn,合并的过程复杂度为n,所以总的复杂度为O(nlogn)。
- 每次打断链表得到3部分,前两部分为size尺寸的链表,第三部分为剩余的链表。将两部分size的链表合并,再继续遍历第三部分的链表,继续重复流程。每次遍历一遍,size加倍。
- 使用tail记录每两个合并链表合并之后的尾结点,方便在后面两个链表合并之后,用tail连接,然后再让tail指向连接和的链表的最后一个节点,继续流程。
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
int length = 0;
ListNode* p = head;
while ( p ) { // 计算链表的长度
++length;
p = p->next;
}
for ( int size = 1; size < length; size <<= 1 ) {
ListNode* cur = dummyHead->next;
ListNode* tail = dummyHead; // 用tail表示每两个被打断的链表合并后的最后一个节点。
while ( cur ) {
ListNode* left = cur;
ListNode* right = cut( left, size );
cur = cut( right, size );
tail->next = merge( left, right );
while ( tail->next )
tail = tail->next;
}
}
p = dummyHead->next;
delete dummyHead;
return p;
}
ListNode* cut( ListNode* node, int size ) {
// node开始,size个结点后,把链表打断,返回打断处后面的结点
ListNode* pre = nullptr;
while ( node && size-- ) {
pre = node;
node = node->next;
}
if ( pre ) pre->next = nullptr;
return node;
}
ListNode* merge( ListNode* left, ListNode* right ) {
// 融合两个链表
ListNode* dummyHead = new ListNode(0);
ListNode* p = dummyHead;
while ( left && right ) {
if ( left->val < right->val ) {
p->next = left;
p = p->next;
left = left->next;
}
else {
p->next = right;
p = p->next;
right = right->next;
}
}
p->next = left ? left : right;
p = dummyHead->next;
delete dummyHead;
return p;
}
};