题目链接:147.链表插入排序 148.链表排序
147链表直接插入排序:
思路:从第二个节点开始,每个当前节点curr都和其前面一个节点即当前有序序列的最后一个节点lastSorted进行比较,若curr.val小于lastSorted.val,则进行遍历将curr插入lastSorted之前;否则,继续往后遍历。
事件复杂度是O(n^2),空间复杂度是O(1)。
代码如下:
// 直接插入排序O(n^2)
public static ListNode sortList(ListNode head){
// 设置一个哨兵,虚拟头结点
ListNode fakeHead=new ListNode(0);
fakeHead.next=head;
ListNode lastSorted=head,cur=head.next;
while (cur!=null){
if(cur.val < lastSorted.val){
// 这个时候才需要交换
ListNode prev=fakeHead;
while(prev.next.val <= cur.val)
prev=prev.next;
lastSorted.next=cur.next;
cur.next=prev.next;
prev.next=cur;
}else{
lastSorted=lastSorted.next;
}
cur=lastSorted.next;
}
return fakeHead.next;
}
148:链表排序
分析:题目要求排序的时间复杂度是O(n logn),符合O(n logn)的排序算法有归并排序,堆排序和快速排序(快速排序在极端案例的情况下,时间复杂度会达到O(n^2))。
快速排序要尽量避免极端案例情况的出现,需要随机选择一个数据作为pivot,这个随机选择过程对于链表来说不是很友好,感觉快速排序不太合适。
感觉堆排序也很难实现。这里参照题解选择了归并排序。
思路:归并排序的思路就是将两个已经有序的小区间进行合并得到一个有序的较大的区间。有自顶向下或自底向上两种思路。
方法一:自顶向下
自顶向下就是按照递归的方法,将整个链表依次从中间节点分成两部分,直到每个小区间中都只有一个节点(这一个节点自己肯定是满足有序的),然后开始合并有序的小区间成为有序的大区间,直到整个链表有序。时间复杂度是O(n logn),递归的空间复杂度是O(logn)。
代码如下:
public static ListNode sortList(ListNode head){
return sort(head,null);
}
public static ListNode sort(ListNode head,ListNode tail){
if(head==null)
return head;
// 说明只有两个元素,自动断开连接,否则会陷入死循环
if(head.next==tail){
head.next=null;
return head;
}
ListNode slow=head;
ListNode fast=head;
// 寻找链表中点,用快慢指针。
while(fast!=tail){
slow=slow.next;
fast=fast.next;
if(fast!=tail)
fast=fast.next;
}
ListNode middle=slow;
ListNode list1=sort(head,middle);
ListNode list2=sort(middle,tail);
// 合并两个区间
return merge(list1,list2);
}
public static ListNode merge(ListNode list1,ListNode list2){
ListNode p=list1;
ListNode q=list2;
ListNode dummyHead=new ListNode(0);
ListNode prev=dummyHead;
// 合并两个链表
while (p!=null && q!=null){
if(p.val<q.val){
prev.next=p;
p=p.next;
}else{
prev.next=q;
q=q.next;
}
prev=prev.next;
}
if(p!=null)
prev.next=p;
if(q!=null)
prev.next=q;
return dummyHead.next;
}
}
方法二:自底向上
自底向上就是先把链表中的每个节点拆分成一个个独立的节点(即初始时,每个区间中只有一个节点,自然是有序的),互不连接,然后逐渐合并将两个有序的小区间合并成一个有序的大区间,再连接到一起,那么每个区间长度变化为:1 2 4 ... length。时间O(n logn),空间O(1)。
代码如下:
// 自底向上
public static ListNode sortList(ListNode head){
// 计算长度
ListNode temp=head;
int length=0;
while (temp!=null){
length++;
temp=temp.next;
}
ListNode dummyHead=new ListNode(0,head);
// 子区间的不同长度
for(int subLength=1;subLength<length;subLength<<=1){
ListNode prev=dummyHead,curr=dummyHead.next;
// 从头遍历
while (curr!=null){
// 子区间1
ListNode list1=curr;
for(int i=1;i<subLength && curr.next!=null;i++)
curr=curr.next;
// 子区间2
ListNode list2=curr.next;
// 断开子区间1和2的连接
curr.next=null;
curr=list2;
for(int i=1;i<subLength && curr!=null && curr.next!=null ;i++)
curr=curr.next;
// 下一个子区间的起点
ListNode next=null;
if(curr!=null){
next=curr.next;
// 断开和下一个子区间的连接
curr.next=null;
}
// 合并两个子区间
ListNode merged=merge(list1,list2);
// 用prev连接每个合并后的区间
prev.next=merged;
// 移动prev,准备连接下一个合并后的区间
while (prev.next!=null){
prev=prev.next;
}
// 继续遍历
curr=next;
}
// 每次for循环完,整个链表都是连接好的,每subLength*2个节点之间是有序的。
}
return dummyHead.next;
}
public static ListNode merge(ListNode list1,ListNode list2){
ListNode p=list1;
ListNode q=list2;
ListNode dummyHead=new ListNode(0);
ListNode prev=dummyHead;
// 合并两个链表
while (p!=null && q!=null){
if(p.val<q.val){
prev.next=p;
p=p.next;
}else{
prev.next=q;
q=q.next;
}
prev=prev.next;
}
if(p!=null)
prev.next=p;
if(q!=null)
prev.next=q;
return dummyHead.next;
}