Leetcode148. Sort List
Sort a linked list in O ( n l o g n ) O(nlogn) O(nlogn) time using constant space complexity.
Example 1:
Input: 4->2->1->3
Output: 1->2->3->4
Example 2:
Input: -1->5->3->4->0
Output: -1->0->3->4->5
解法一 归并排序 递归 自顶向下
要求时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),可以使用归并排序。
归并排序需要一个辅助方法,也就是对两个有序链表进行合并,具体见Leetcode21. Merge Two Sorted Lists
归并排序是从中间分一半,因此我们需要用到快慢指针来定位链表中点
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy;
ListNode slow = dummy;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
dummy指针作用是当节点个数是偶数的时候,让 slow 刚好指向前边一半节点的最后一个节点,也就是下边的状态。
1 2 3 4
^ ^
slow fast
或者可以添加一个pre指针,让它一直指向 slow 的前一个即可。
ListNode prev = null, slow = head, fast = head;
while (fast != null && fast.next != null) {
prev = slow;
slow = slow.next;
fast = fast.next.next;
}
- 时间复杂度:
O(nlogn)
- 空间复杂度:
O(logn)
通过递归实现链表归并排序:
- 分割 cut 环节: 找到当前链表中点,并从中点将链表断开;
- 快慢双指针法,奇数个节点找到中点,偶数个节点找到中心左边的节点。
- 找到中点
slow
后,执行slow.next = None
将链表切断。 - 递归分割时,输入当前链表左端点
head
和中心节点slow
的下一个节点(因为链表是从 slow 切断的)。 cut
递归终止条件: 当head.next == None
时,说明只有一个节点了,直接返回此节点。
- 合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
- 双指针法合并,建立
dummyhead
作为头部。 - 设置两指针
left
,right
分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。 - 返回
dummy.next
(它指向链表头部)。 - 时间复杂度
O(l + r)
,l
,r
分别代表两个链表长度。
- 双指针法合并,建立
- 当题目输入的 head == None 时,直接返回None。
public ListNode sortList(ListNode head) {
return mergeSort(head);
}
private ListNode mergeSort(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy;
ListNode slow = dummy;
//快慢指针找中点
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode head2 = slow.next;
slow.next = null;
head = mergeSort(head);
head2 = mergeSort(head2);
return merge(head, head2);
}
private ListNode merge(ListNode head1, ListNode head2) {
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
while (head1 != null && head2 != null) {
if (head1.val < head2.val) {
tail.next = head1;
tail = tail.next;
head1 = head1.next;
} else {
tail.next = head2;
tail = tail.next;
head2 = head2.next;
}
}
if (head1 != null) {
tail.next = head1;
}
if (head2 != null) {
tail.next = head2;
}
return dummy.next;
}
解法二 归并排序 迭代 自底向上
对于非递归的归并排序,需要使用迭代的方式替换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(1)
。
步骤:
- 统计链表长度
length
,用于通过判断intv < length
判定是否完成排序; - 额外声明一个节点
dummy
,作为头部后面接整个链表,用于:intv *= 2
即切换到下一轮合并时,可通过dummy.next
找到链表头部;- 执行排序合并时,作为链表头部排序合并时的辅助头部;后面的合并排序可以将上次合并排序的尾部
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
public class Solution {
private class MergeHelper {
public ListNode newHead;
public ListNode newTail;
}
public ListNode sortList(ListNode head) {
if ( head == null || head.next == null) {
return head;
}
ListNode dummyHeadOne = new ListNode(0);
ListNode dummyHeadTwo = new ListNode(0);
ListNode dummySortedHead = new ListNode(0);
ListNode dummySortedLast = dummySortedHead;
ListNode unvisitedNode = head;
MergeHelper mergeRst = new MergeHelper();
int listLength = 0;
int level = 0;
while ( unvisitedNode != null && unvisitedNode.next != null ) {
unvisitedNode = addNode ( dummyHeadOne, unvisitedNode, 1<<level);
unvisitedNode = addNode ( dummyHeadTwo, unvisitedNode, 1<<level);
merge ( dummyHeadOne.next, dummyHeadTwo.next, mergeRst);
dummySortedLast.next = mergeRst.newHead;
dummySortedLast = mergeRst.newTail;
listLength += 2;
}
if (unvisitedNode != null) {
dummySortedLast.next = unvisitedNode;
listLength ++;
}
level ++;
while ( listLength > 1 << level) {
dummySortedLast = dummySortedHead;
unvisitedNode = dummySortedHead.next;
while (unvisitedNode != null) {
unvisitedNode = addNode ( dummyHeadOne, unvisitedNode, 1<<level);
unvisitedNode = addNode ( dummyHeadTwo, unvisitedNode, 1<<level);
merge ( dummyHeadOne.next, dummyHeadTwo.next, mergeRst);
dummySortedLast.next = mergeRst.newHead;
dummySortedLast = mergeRst.newTail;
}
level ++;
}
return dummySortedHead.next;
}
/* merge listOne and listTwo.
Save the sorted list head into rst.newHead
Save the last node of the sorted list into rst.newTail
*/
private void merge (ListNode listOne, ListNode listTwo, MergeHelper rst) {
ListNode dummyHead = new ListNode (0);
ListNode lastNode = dummyHead;
while (listOne != null && listTwo != null) {
if ( listOne.val < listTwo.val ) {
lastNode.next = listOne;
listOne = listOne.next;
} else {
lastNode.next = listTwo;
listTwo = listTwo.next;
}
lastNode = lastNode.next;
}
while (listOne != null) {
lastNode.next = listOne;
listOne = listOne.next;
lastNode = lastNode.next;
}
while ( listTwo != null ) {
lastNode.next = listTwo;
listTwo = listTwo.next;
lastNode = lastNode.next;
}
rst.newHead = dummyHead.next;
rst.newTail = lastNode;
}
/*
add at max #"count" nodes into "head" from "source"
return the new position of source after adding.
*/
private ListNode addNode ( ListNode head, ListNode source, int count ) {
while (count > 0 && source != null) {
head.next = source;
head = head.next;
source = source.next;
count --;
}
head.next = null;
return source;
}}
解法三 快速排序
public ListNode sortList(ListNode head) {
if (head == null) return null;
ListNode pivot = head;
head = head.next;
pivot.next = null;
if (head == null) return pivot;
ListNode small = new ListNode(0);
ListNode large = new ListNode(0);
ListNode p = pivot;
ListNode s = small;
ListNode l = large;
while (head!=null) {
if (head.val < pivot.val) {
s.next = head;
s = s.next;
} else if (head.val == pivot.val) {
p.next = head;
p = p.next;
} else {
l.next = head;
l = l.next;
}
head = head.next;
}
l.next = null;
s.next = null;
p.next = null;
ListNode ss = sortList(small.next);
if (ss == null) {
ss = pivot;
} else {
ListNode sss = ss;
while (sss.next!=null) {
sss = sss.next;
}
sss.next = pivot;
}
p.next = sortList(large.next);
return ss;
}