在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:输入: -1->5->3->4->0
输出: -1->0->3->4->5
不考虑常数级空间复杂度时,可以采用递归的做法:
找到当前链表的中点后的一个节点, 将链表从中间断开,然后对两半链表进行merge,递归的完成这个操作
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
// 将两根链表合并,并返回链表的第一个节点
private ListNode merge(ListNode a,ListNode b){
ListNode dummy=new ListNode(0);
ListNode tail=dummy;
while(a!=null||b!=null){
if(a==null){
tail.next=b;
break;
}else if(b==null){
tail.next=a;
break;
}else{
// 均不为null
if(a.val<b.val){
tail.next=a;
tail=a;
a=a.next;
}else{
tail.next=b;
tail=b;
b=b.next;
}
}
}
return dummy.next;
}
public ListNode sortList(ListNode head) {
if(head==null||head.next==null){
return head;
}
ListNode fast,slow;
fast=head.next;
slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
ListNode t=slow.next;
slow.next=null;
return merge(sortList(head),sortList(t));
}
}
注意寻找中点时,快指针一开始应该是head.next,而不是head,否则找到的不是链表中点,甚至会导致栈溢出:
比如4->2->null,此时fast到达null,slow到达2,然后将t设为slow->next即null,然后slow->next置为空,接着sortlist(head),最终导致爆栈
因为使用了栈实现递归,所以上述不是常数级空间复杂度,要符合这个要求,我们可以使用自底向上的归并排序,其中涉及到两个关键操作,cut和merge,merge和上述相同,cut则是将传入链表从第n个节点处断开,并返回后半部分的第一个节点
实现了这两个函数之后,我们需要从头到尾,首先是每两个一merge,然后是每4个一merge,直到排序完成
class Solution {
// 将两根链表合并,并返回链表的第一个节点
private ListNode merge(ListNode a, ListNode b) {
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
while (a != null || b != null) {
if (a == null) {
tail.next = b;
break;
} else if (b == null) {
tail.next = a;
break;
} else {
// 均不为null
if (a.val < b.val) {
tail.next = a;
tail = a;
a = a.next;
} else {
tail.next = b;
tail = b;
b = b.next;
}
}
}
return dummy.next;
}
private ListNode cut(ListNode head, int n) {
int c = 1;
ListNode tail = head;
while (c < n && tail != null) {
c++;
tail = tail.next;
}
if (tail == null) {
return null;
}
ListNode t = tail.next;
tail.next = null;
return t;
}
public ListNode sortList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
// 首先算出链表长度
int len = 0;
ListNode t = head;
while (t != null) {
t = t.next;
len++;
}
ListNode dummy = new ListNode(0);
dummy.next = head;
// 自底向上的归并排序
for (int sz = 1; sz < len; sz *= 2) {
// 此处应该设置为dummy.next,而不是head,因为head在下面的变换中会变化
// 而dummy始终不变
ListNode cur = dummy.next;
// 这里不是指向head,是因为要将后面归并的链表(包括head)放在dummy.next,而不是head.next
ListNode tail = dummy;
// 使用当前sz,归并完当前链表
while (cur != null) {
ListNode left = cur;
ListNode right = cut(cur, sz);
cur = cut(right, sz);
tail.next = merge(left, right);
// tail始终指向已排序链表的末尾
while (tail.next != null) {
tail = tail.next;
}
}
}
return dummy.next;
}
}
细节是魔鬼,链表的自底向上的归并需要多看几遍