一、题目描述:148. 排序链表(中等)
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:输入: -1->5->3->4->0
输出: -1->0->3->4->5来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sort-list
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
二、解题思路
解答一:归并排序(递归法)
题目要求时间空间复杂度分别为O(nlogn)O(nlogn)和O(1)O(1),根据时间复杂度我们自然想到二分法,从而联想到归并排序;
对数组做归并排序的空间复杂度为 O(n)O(n),分别由新开辟数组O(n)O(n)和递归函数调用O(logn)O(logn)组成,而根据链表特性:
数组额外空间:链表可以通过修改引用来更改节点顺序,无需像数组一样开辟额外空间;
递归额外空间:递归调用函数将带来O(logn)O(logn)的空间复杂度,因此若希望达到O(1)O(1)空间复杂度,则不能使用递归。
通过递归实现链表归并排序,有以下两个环节:
分割 cut 环节: 找到当前链表中点,并从中点将链表断开(以便在下次递归 cut 时,链表片段拥有正确边界);
我们使用 fast,slow 快慢双指针法,奇数个节点找到中点,偶数个节点找到中心左边的节点。
找到中点 slow 后,执行 slow.next = None 将链表切断。
递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点 tmp(因为链表是从 slow 切断的)。
cut 递归终止条件: 当head.next == None时,说明只有一个节点了,直接返回此节点。
合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
双指针法合并,建立辅助ListNode h 作为头部。
设置两指针 left, right 分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
返回辅助ListNode h 作为头部的下个节点 h.next。
时间复杂度 O(l + r),l, r 分别代表两个链表长度。
当题目输入的 head == None 时,直接返回None。
方法二、自底向上的归并
对于非递归的归并排序,需要使用迭代的方式替换cut环节:
我们知道,cut环节本质上是通过二分法得到链表最小节点单元,再通过多轮合并得到排序结果。
每一轮合并merge操作针对的单元都有固定长度intv,例如:
第一轮合并时intv = 1,即将整个链表切分为多个长度为1的单元,并按顺序两两排序合并,合并完成的已排序单元长度为2。
第二轮合并时intv = 2,即将整个链表切分为多个长度为2的单元,并按顺序两两排序合并,合并完成已排序单元长度为4。
以此类推,直到单元长度intv >= 链表长度,代表已经排序完成。
根据以上推论,我们可以仅根据intv计算每个单元边界,并完成链表的每轮排序合并,例如:
当intv = 1时,将链表第1和第2节点排序合并,第3和第4节点排序合并,……。
当intv = 2时,将链表第1-2和第3-4节点排序合并,第5-6和第7-8节点排序合并,……。
当intv = 4时,将链表第1-4和第5-8节点排序合并,第9-12和第13-16节点排序合并,……。
此方法时间复杂度O(nlogn)O(nlogn),空间复杂度O(1)O(1)。
模拟上述的多轮排序合并:
统计链表长度length,用于通过判断intv < length判定是否完成排序;
额外声明一个节点res,作为头部后面接整个链表,用于:
intv *= 2即切换到下一轮合并时,可通过res.next找到链表头部h;
执行排序合并时,需要一个辅助节点作为头部,而res则作为链表头部排序合并时的辅助头部pre;后面的合并排序可以将上次合并排序的尾部tail用做辅助节点。
在每轮intv下的合并流程:
根据intv找到合并单元1和单元2的头部h1, h2。由于链表长度可能不是2^n,需要考虑边界条件:
在找h2过程中,如果链表剩余元素个数少于intv,则无需合并环节,直接break,执行下一轮合并;
若h2存在,但以h2为头部的剩余元素个数少于intv,也执行合并环节,h2单元的长度为c2 = intv - i。
合并长度为c1, c2的h1, h2链表,其中:
合并完后,需要修改新的合并单元的尾部pre指针指向下一个合并单元头部h。(在寻找h1, h2环节中,h指针已经被移动到下一个单元头部)
合并单元尾部同时也作为下次合并的辅助头部pre。
当h == None,代表此轮intv合并完成,跳出。
每轮合并完成后将单元长度×2,切换到下轮合并:intv *= 2。
三、代码
方法一
/**
* 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)
return NULL;
int n = 0;
ListNode * head_ori = head;
int count = 0;
while(head_ori != NULL)
{
head_ori= head_ori->next;
count++;
}
vector head_tail = sorListr(head, count);
//print(head_tail[0],count);
return head_tail[0];
}
vector sorListr(ListNode * head, int n)
{
//print(head,n);
vector head_tail(2,NULL);
if(n == 1)
{
head_tail[0] = head;
head_tail[1] = head;
return head_tail;
}
if(n == 2)
{
if(head == NULL)
return head_tail;
if(head->next == NULL)
{
head_tail[0] = head;
return head_tail;
}
if(head->val > head->next->val)
{
ListNode * tmp = head->next->next;
ListNode * next = head->next;
next->next = head;
head->next = tmp;
head = next;
}
head_tail[0] = head;
head_tail[1] = head->next;
//cout<<"n2="<<2<<"---"<val<<","<next->val<next<<","<next->next< 0)
{
if(n == 1)
tail = mid;
mid = mid->next;
count_t--;
}
//cout<<"n+1/2="<<(n+1)/2;
//cout<<"begin="<val< first = sorListr(head, (n+1)/2);
//print(first[0],(n+1)/2,false);
//if(mid != NULL)
//cout<<"begin_second="<val< second = sorListr(mid, n-(n+1)/2);
//print(second[0],n-(n+1)/2,false);
if(first[0] == NULL)
return head_tail;
int count_f = (n+1)/2;
int count_s = n-(n+1)/2;
if(second[0] == NULL)
{
first[1]->next = NULL;
head_tail[0] = first[0];
head_tail[1] = first[1];
return head_tail;
}
ListNode * return_head = first[0];
if(first[0]->val > second[0]->val)
{
ListNode * tmp = second[0]->next;
second[0]->next = first[0];
pre = second[0];
return_head = second[0];
second[0] = tmp;
//cout<<"second[0]="<val< 0 && count_s > 0)
{
if(second[0] == NULL || first[0] == NULL)
break;
if(first[0]->val <= second[0]->val)
{
if(pre_first != first[0])
{
pre_first = first[0];
}
first[0] = first[0]->next;
count_f--;
}
else if(second[0] != NULL)
{
ListNode * next = second[0]->next;
pre_first->next = second[0];
second[0]->next = first[0];
pre_first = second[0];
second[0] = next;
count_s--;
}
if(second[1] != NULL && second[0] == second[1]->next)
break;
if(first[1] != NULL && first[0] == first[1]->next)
break;
}
//print(return_head,n,false);
//cout<<"n="<next = second[0];
head_tail[1] = second[1];
}
else if (count_s == 0 && second[1] != NULL)
{
cout<next<next = second[0];
second[1]->next = first[0];
head_tail[1] = first[1];
}
else
{
if(first[1] != NULL)
{
first[1]->next = NULL;
head_tail[1] = NULL;
}
}
head_tail[0] = return_head;
//print(head_tail[0],n,false);
return head_tail;
}
void print(ListNode * head,int n,bool c = false)
{
cout<<"n="<val<<",";
head = head->next;
if(c == false)
{
if(--n==0)
break;
}
}
cout<
方法一、Python版本
class Solution:
def sortList(self, head: ListNode) -> ListNode:
if not head or not head.next: return head # termination.
# cut the LinkedList at the mid index.
slow, fast = head, head.next
while fast and fast.next:
fast, slow = fast.next.next, slow.next
mid, slow.next = slow.next, None # save and cut.
# recursive for cutting.
left, right = self.sortList(head), self.sortList(mid)
# merge `left` and `right` linked list and return it.
h = res = ListNode(0)
while left and right:
if left.val < right.val: h.next, left = left, left.next
else: h.next, right = right, right.next
h = h.next
h.next = left if left else right
return res.next
方法二、
class Solution:
def sortList(self, head: ListNode) -> ListNode:
h, length, intv = head, 0, 1
while h: h, length = h.next, length + 1
res = ListNode(0)
res.next = head
# merge the list in different intv.
while intv < length:
pre, h = res, res.next
while h:
# get the two merge head `h1`, `h2`
h1, i = h, intv
while i and h: h, i = h.next, i - 1
if i: break # no need to merge because the `h2` is None.
h2, i = h, intv
while i and h: h, i = h.next, i - 1
c1, c2 = intv, intv - i # the `c2`: length of `h2` can be small than the `intv`.
# merge the `h1` and `h2`.
while c1 and c2:
if h1.val < h2.val: pre.next, h1, c1 = h1, h1.next, c1 - 1
else: pre.next, h2, c2 = h2, h2.next, c2 - 1
pre = pre.next
pre.next = h1 if c1 else h2
while c1 > 0 or c2 > 0: pre, c1, c2 = pre.next, c1 - 1, c2 - 1
pre.next = h
intv *= 2
return res.next