题目:Sort a linked list in O(n log n) time using constant space complexity.
思路:题目要求我们使用常数空间复杂度,时间复杂度为O(nlog(n)). 满足这个时间复杂度的有快速排序,归并排序,堆排序。插入排序时间复杂度为O(n^2). 双向链表用快排比较合适,堆排序也可用于链表,单项链表适合于归并排序。我们就用归并排序的思想来完成链表的排序。
首先是用快慢双指针找到链表中间的位置,然后分成前后端分别递归的归并排序,最后合并。
这道题的归并排序思想,我们在Merge Two Sorted Lists中已经做过。我们需要每次找到中点,然后对左右进行递归,最后用Merge Two Sorted Lists把他们合并起来。
Attention:
1. 快慢指针找中间节点的方法,是常用方法,要熟练使用。
//将链表分成前后两部分 双指针找到中间节点
ListNode* fast = head;
ListNode* slow = head;
while(fast->next != NULL && fast->next->next != NULL)
{
slow = slow->next;
fast = fast->next->next;
}
2. 链表的数据结构是如何实现归并的方法的。定义一个合并后的链表,然后把合适的节点放进去。思想和数组是一样的,只是链表操作不一样。
复杂度:如果这里我们考虑递归的栈空间的话,空间复杂度是O(lg(n))
AC Code:
/**
* 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) {
if(head == NULL || head->next == NULL) return head;
//将链表分成前后两部分 双指针找到中间节点
ListNode* fast = head;
ListNode* slow = head;
while(fast->next != NULL && fast->next->next != NULL)
{
slow = slow->next;
fast = fast->next->next;
}
ListNode* head2 = slow->next;
slow->next = NULL;
ListNode* head1 = head;
head1 = sortList(head1); //前半段归并排序
head2 = sortList(head2); //后半段归并排序
return merge(head1, head2);
}
private:
ListNode* merge(ListNode* h1, ListNode* h2)
{
ListNode* dummyhead = new ListNode(0);
ListNode* mlist = dummyhead;
while(h1 != NULL && h2 != NULL)
{
if(h1->val <= h2->val)
{
mlist->next = h1;
h1 = h1->next;
}
else
{
mlist->next = h2;
h2 = h2->next;
}
mlist = mlist->next;
}
if(h1 != NULL)
{
mlist->next = h1;
}
if(h2 != NULL)
{
mlist->next = h2;
}
return dummyhead->next;
}
};
我们也可以不使用递归的方法,自底向上的非递归版本的归并排序,空间复杂度就是常数了。可以看下这篇博文:sort list.
排序是面试中很常见和基础的一个主题,我们需要对各种常见的排序算法要熟悉。尤其是算法的原理,很多题目虽然没有直接考察排序的实现,但是会用到其中的思想,比如经典的topK问题,用到了快排的原理。这篇文章中有提到,可以看看:Median of Two Sorted Arrays.所以我们应该要更加注重排序算法的原理。