Sort a linked list in O(n log n) time using constant space complexity.
实现一个对链表排序的算法,要求时间复杂度为O(nlogn),空间复杂度为O(1)
思路一:
首先想到的是快排,快排代码在下面,可是提交之后总是运行超时,这里做一下分析:
首先分析空间复杂度,一般来说,快排的空间复杂度是O(logn),因为快排是递归调用,每次调用都要用常数的空间,由于对链表进行排序,所以实际上排完序后指向链表节点的指针可以复用,从而实现O(1)的空间复杂度。
其次分析时间复杂度,快排的平均时间复杂度为O(nlogn), 可是最坏的情况为O(n^2),也就是说快排的时间是不稳定的,如果给的测试用例刚好是逆序的,就会到最坏情况。所以会超时,那么需要一种时间比较稳定的算法,选择归并。快排代码如下
#include <iostream>
#include "../coding interviews/Utilities/List.h"
using namespace std;
ListNode* pNext = nullptr;//内循环指针
ListNode* pPNext = nullptr;//永远跟在pNext的前一个
// ListNode* pHead = nullptr;//标示头指针
ListNode* temp = nullptr;
bool ishead = true;
ListNode* sortListCore(ListNode *pbegin, ListNode* pend, ListNode* pPreBegin, ListNode* pHead)
{
if(pbegin == pend)
return pbegin;
ishead = true;//用来确定头指针的位置,头指针是第一个移到pNode前的节点
pNext = pbegin->m_pNext;//内循环指针
pPNext = pbegin;//永远跟在pNext的前一个
pHead = pbegin;//标示头指针
temp = nullptr;
while(pNext != pend)
{
if(pbegin->m_nValue > pNext->m_nValue)
{
if(pPreBegin == nullptr)
{
temp = pNext->m_pNext;
pNext->m_pNext = pbegin;
pPNext->m_pNext = temp;
pPreBegin = pNext;
pNext = pPNext->m_pNext;
if(ishead)
{
pHead = pPreBegin;
ishead = false;
}
}
else
{
temp = pNext->m_pNext;
pNext->m_pNext = pbegin;
pPNext->m_pNext = temp;
pPreBegin->m_pNext = pNext;
pPreBegin = pNext;
pNext = pPNext->m_pNext;
}
}
else
{
pPNext = pNext;
pNext = pNext->m_pNext;
}
}
sortListCore(pbegin->m_pNext, nullptr, pbegin, pHead);
pHead = sortListCore(pHead, pbegin, nullptr, pHead);
return pHead;
}
ListNode *sortList(ListNode *head)
{
// if(head == nullptr)
// return nullptr;
// ListNode* pNode = head;//外循环指针
//ListNode* pPreNode = nullptr;//永远跟在pNode的前一个
return sortListCore(head, nullptr, nullptr, head);
}
int main()
{
ListNode* pNode1 = CreateListNode(5);
ListNode* pNode2 = CreateListNode(4);
ListNode* pNode3 = CreateListNode(6);
ListNode* pNode4 = CreateListNode(2);
ListNode* pNode5 = CreateListNode(1);
ListNode* pNode6 = CreateListNode(0);
ConnectListNodes(pNode1, pNode2);
ConnectListNodes(pNode2, pNode3);
ConnectListNodes(pNode3, pNode4);
ConnectListNodes(pNode4, pNode5);
ConnectListNodes(pNode5, pNode6);
PrintList(pNode1);
ListNode* newNode = sortList(pNode1);
PrintList(newNode);
}
思路二
既然快排行不通,那就用归并吧,直接上代码
// struct ListNode {
// int val;
// ListNode *next;
// ListNode(int x) : val(x), next(nullptr) {}
// };
#include <iostream>
#include "../coding interviews/Utilities/List.h"
using namespace std;
ListNode* pRNext = nullptr;//要归并的后一个数组的头指针
ListNode* pLNext = nullptr;//要归并的前一个数组的头指针
ListNode* temp = nullptr;
ListNode* pmid = nullptr;//标示中间节点的位置
ListNode* Getmid(ListNode *pbegin, ListNode* pend)
{
temp = pbegin;
pmid = pbegin;
if(temp == pend)
{
return temp;
}
else
{
temp = temp->next;
if(temp == pend)
return pmid;
else
temp = temp->next;
}
while(temp != pend && temp->next != pend)
{
pmid = pmid->next;
temp = temp->next->next;
}
return pmid;
}
ListNode* sortListCore(ListNode *pbegin, ListNode* pend, ListNode* pmiddle)
{
if(pbegin != pmiddle->next && pmiddle->next->next != pend && pmiddle->next != pend)
{
pmid = Getmid(pmiddle->next, pend);
pmiddle->next = sortListCore(pmiddle->next, pend, pmid);
//PrintList(pmiddle->next);
pmid = Getmid(pbegin, pmiddle->next);
pbegin = sortListCore(pbegin, pmiddle->next, pmid);
}
pLNext = pbegin;
pRNext = pmiddle->next;
pmiddle->next = nullptr;//将链表拆开
ListNode *dumy = CreateListNode(0);
temp = dumy;
while(pLNext && pRNext)
{
if(pLNext->val > pRNext->val)
{
temp->next = pRNext;
pRNext = pRNext->next;
temp = temp->next;
}
else
{
temp->next = pLNext;
pLNext = pLNext->next;
temp = temp->next;
}
}
if(pLNext) temp->next = pLNext;
if(pRNext) temp->next = pRNext;
temp = dumy->next;
delete dumy;
dumy = nullptr;
return temp;
}
ListNode *sortList(ListNode *head)
{
if(head == nullptr)
return nullptr;
if(head->next == nullptr)
return head;
pmid = Getmid(head, nullptr);
// PrintListNode(pmid);
return sortListCore(head, nullptr, pmid);
}
int main()
{
ListNode* pNode1 = CreateListNode(3);
ListNode* pNode2 = CreateListNode(4);
ListNode* pNode3 = CreateListNode(1);
// ListNode* pNode4 = CreateListNode(0);
// ListNode* pNode5 = CreateListNode(1);
// ListNode* pNode6 = CreateListNode(4);
ConnectListNodes(pNode1, pNode2);
ConnectListNodes(pNode2, pNode3);
// ConnectListNodes(pNode3, pNode4);
// ConnectListNodes(pNode4, pNode5);
// ConnectListNodes(pNode5, pNode6);
PrintList(pNode1);
ListNode* newNode = sortList(pNode1);
PrintList(newNode);
DestroyList(newNode);
}
这个代码需要注意俩点,第一点是结束条件要小心,需要考虑各种不同的情况,因为我返回的是后一个需要归并的链表的前一个节点的指针,所以要特别小心,后一个链表不存在,或存在只有一个节点的情况,同样在获得中间节点函数中也要考虑到这几种情况;第二个需要注意的点是,归并可以直接用一个指针来表示新建立的链表,因此避免了O(n)的辅助空间,但是在做归并是必须先把原链表断开,断开之后不能丢节点才可以。我原先的想法是移动节点的位置,只要对要移动和要插入的节点做好记录(保留前一个节点),就可以实现O(1)时间复杂度的节点移动,但是实际运行发现超内存,我的思路还是不够“归并”,所以改成如上所示的代码。