在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
思路
由于时间复杂度在O(nlogn),容易想到归并排序和堆排序,由于堆排序需要能够按index访问,并不适合链表,递归的归并排序空间复杂度O(logn),不满足常数空间复杂度要求,所以使用非递归版本归并排序。
归并排序最主要的算法是合并两个有序的子序列,我们称为merge(l,r);
另外对于链表,我们在合并子序列时需要判断子序列的结束位置,好的想法是在merge前做断链,将要排序的链表切断,我们成为cut(l,n);
代码
class Solution {
public ListNode sortList(ListNode head) {
int step = 1;
ListNode p = head;
int len = 0;
while (p != null) {
len++;
p=p.next;
}
while (true) {
ListNode next = head;
ListNode preEnd = null;
boolean first = true;
while (next != null) {
ListNode l = next;
ListNode r = cut(l, step);
next = cut(r, step);
ListNode me = merge(l,r);
if(first){
head = me;
first = false;
}
if(preEnd!=null){
preEnd.next = me;
}
while(me.next!=null){
me = me.next;
}
preEnd = me;
}
if(step>len/2){
return head;
}
step = step * 2;
}
}
public ListNode cut(ListNode l, int n) {
for (int i = 0; i < n - 1; i++) {
if (l == null) return l;
l = l.next;
}
if (l == null) return l;
ListNode lnext = l.next;
l.next = null;
return lnext;
}
public ListNode merge(ListNode l, ListNode r) {
if (r == null || l == null) return l;
ListNode head;
if(l.val<r.val){
head = l;
l = l.next;
}else{
head = r;
r = r.next;
}
ListNode curr = head;
while(l!=null&&r!=null){
if(l.val<r.val){
curr.next = l;
curr = curr.next;
l = l.next;
}else{
curr.next = r;
curr = curr.next;
r = r.next;
}
}
if(l!=null){
curr.next = l;
}
if(r!=null){
curr.next = r;
}
return head;
}
}
递归版的归并排序:
递归版耗时比非递归版要少,采用双指针的方式确定链表中间元素,省去了cut函数。
public ListNode sortList(ListNode head){
if(head==null||head.next==null) return head;
ListNode p1=head,p2=head,pre=null;
while(p2!=null){
pre = p1;
p1 = p1.next;
p2 = p2.next;
if(p2!=null){
p2=p2.next;
}
}
ListNode rHead = p1;
if(pre!=null){
pre.next = null;
}
head = sortList(head);
rHead = sortList(rHead);
return merge(head,rHead);
}
public ListNode merge(ListNode l, ListNode r) {
if (r == null || l == null) return l;
ListNode head;
if(l.val<r.val){
head = l;
l = l.next;
}else{
head = r;
r = r.next;
}
ListNode curr = head;
while(l!=null&&r!=null){
if(l.val<r.val){
curr.next = l;
curr = curr.next;
l = l.next;
}else{
curr.next = r;
curr = curr.next;
r = r.next;
}
}
if(l!=null){
curr.next = l;
}
if(r!=null){
curr.next = r;
}
return head;
}
一个细节:非递归版本的归并排序中,使用了三个指针p1,p2,pre来分割将链表分割为两个。pre的作用是找到左链表的最后一个节点。
看到另一种找左链表尾节点的方法:
ListNode n0 = new ListNode(0);
n0.next = head;
p1 = n0;
ListNode p1=head,p2=head;
while(p2!=null){
p1 = p1.next;
p2 = p2.next;
if(p2!=null){
p2=p2.next;
}
}
n0.next = null;
在表头节点前插入一个节点,即可省去pre指针。