就是你想的那个意思,给链表排序。
最简单的办法是,读取链表里的数字存到数组里,然后给数组排个序,最后放回去,时间复杂度O(nlogn)空间复杂度O(n)。这是很自然而然的想法。
但是他妈的字节跳动要求你的空间复杂度必须是O(1),也就是说你得原地排序。
那怎么办呢,首先原地排序,我想到了归并排序,正好leetcode21题要求写合并两个有序链表,可以用上。但是仔细一想发现不会,归并排序会压栈,这样的话一样会有空间开销,空间复杂度为O(logn),空间复杂度主要取决于递归调用的栈空间。
那么我们不能用传统的归并排序思路来做,我们需要自底向上进行归并排序。 这里看官方题解怎么说
来看代码
ListNode* sortList(ListNode* head)
{
if(!head) return head;
int length;
ListNode* p=head;
while(p!=nullptr)
{
length++;
p=p->next;
}
ListNode* vh=new ListNode(0,head);
//每次for循环都会将subLength扩大两倍
for(int subLength=1;subLength<length;subLength*=2)
{
ListNode* prev=vh;
ListNode* cur=vh->next;
//每次按照subLength的长度分割出两条链表,然后让他俩归并排序一遍,然后再接回去,如此反复到结尾
while(cur!=nullptr)
{
ListNode* head1=cur;
for(int i=1;i<subLength;i++)
{
if(cur->next==nullptr)
break;
cur=cur->next;
}
ListNode* head2=cur->next;
cur->next=nullptr;
cur=head2;
for(int i=1;i<subLength;i++)
{
if(cur==nullptr || cur->next==nullptr)
break;
cur=cur->next;
}
ListNode* next=nullptr;
if(cur!=nullptr)
{
next=cur->next;
cur->next=nullptr;
}
ListNode* merged=mergeTwoLists(head1, head2);
prev->next=merged;
while(prev->next!=nullptr)
prev=prev->next;
cur=next;
}
}
return vh->next;
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{
ListNode* vh=new ListNode;
ListNode* p=vh;
ListNode* p1=l1;
ListNode* p2=l2;
while(p1!=nullptr && p2!=nullptr)
{
if(p1->val<=p2->val)
{
p->next=p1;
p1=p1->next;
}
else
{
p->next=p2;
p2=p2->next;
}
p=p->next;
}
if(p1!=nullptr)
{
p->next=p1;
}
if(p2!=nullptr)
{
p->next=p2;
}
return vh->next;
}
这种题目各个大厂面试都很喜欢考,为什么呢?首先,这题目有足够的优化空间,面试官可以通过你用的方法区分面试者的水平。 遇到变态的面试官可能会要求你一定要写出最优的算法。其次,链表排序这名字就很好记,面试的时候随手就能想到出这一题,然后看你吭哧吭哧写半天,对于他们来说,既不用废自己脑子,又可以折磨你,的确是一种精神上的享受。
而对于我们这种苦逼的面试者,只能尽力将遇到的这种题目的最优解法背下来,才有可能通过面试。
诸君,共勉吧,希望有朝一日,我们能脱离算法题的折磨