法1:递归版归并排序
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
// 归并排序
// 确实链表可以实现哈,不像快排可能说需要双指针往中间靠拢,
// 归并直接从前往后所以不涉及到获取数组中间值的操作,
if(head == null)
return head;
return mergeSort(head);
}
public ListNode mergeSort(ListNode head) {
//仅1个元素了
if(head.next == null)
return head;
//快慢指针,且拆成2半
ListNode secondHead = getSecondHead(head);
head = mergeSort(head);
secondHead = mergeSort(secondHead);
return merge(head, secondHead);
}
//合并两个有序链表
public ListNode merge(ListNode head, ListNode secondHead) {
// 哑结点技巧
ListNode dummyHead = new ListNode();
ListNode curNode = dummyHead;
while(head != null && secondHead != null) {
if(head.val <= secondHead.val) {
curNode.next = head;
head = head.next;
} else {
curNode.next = secondHead;
secondHead = secondHead.next;
}
//艹,忘了更新curNode
curNode = curNode.next;
}
//如果head先为空
if(head == null)
curNode.next = secondHead;
else
curNode.next = head;
return dummyHead.next;
}
//快慢指针获取另一半首节点,前面已经检验至少2个节点,并且要断开为2个链表
public ListNode getSecondHead(ListNode head) {
//初始化
ListNode slow = head;
ListNode fast = head.next;
while(fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
//注意必须要断成2节
head = slow.next;
slow.next = null;
return head;
}
}
空间复杂度为递归栈空间,logn,因为链表特性不像数组版标准归并排序,还需要O(n)的额外数组来合并2个旧数组。
法2:迭代版归并排序
递归是要自顶向下进行分治,一分二,二分四......它是自顶往下调用的,虽然计算仍然是从下往上计算得出然后返回的。
但可以直接用自底向上的思想,直接先从底部计算,再往上“累积”,避开了自顶向下调用的过程。但实现就要麻烦很多。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
// 16.19
if(head == null || head.next == null)
return head;
//获取长度
int n = getLen(head);
ListNode dummyHead = new ListNode(0, head);
ListNode curNode;
ListNode preLinkLast;
ListNode[] heads;
ListNode head1, head2;
//step是每次合并的两链表的长度
for(int step = 1; step < n; step *= 2) {
curNode = dummyHead.next;
preLinkLast = dummyHead;
//2个2个的合并
while(curNode != null) {
head1 = curNode;
// splitList方法,是获取head开头的长step的链表,
// 并将其与后面断开,且返回断开后的下一个链表头
head2 = splitList(head1, step);
//下一轮迭代的head
curNode = splitList(head2, step);
//合并2个有序链表,并返回合并的链表头和尾
heads = mergeTwoLists(head1, head2);
preLinkLast.next = heads[0];
preLinkLast = heads[1];
}
}
return dummyHead.next;
}
//返回链表总长度
public int getLen(ListNode head) {
int cnt = 0;
while(head != null) {
cnt++;
head = head.next;
}
return cnt;
}
//或取head开头长step的链表,并将其与后面断开
// 并返回断开后的head
// 若长度不到step,返回null
public ListNode splitList(ListNode head, int step) {
int cnt = 1;
while(head != null && cnt < step) {
head = head.next;
cnt++;
}
if(head == null)
return null;
ListNode nextHead = head.next;
head.next = null;
return nextHead;
}
// 合并2个有序链表,并返回新的合并后的head和tail
public ListNode[] mergeTwoLists(ListNode head1, ListNode head2){
ListNode dummyHead = new ListNode();
ListNode curNode = dummyHead;
while(head1 != null && head2 != null) {
if(head1.val <= head2.val) {
curNode.next = head1;
head1 = head1.next;
}
else {
curNode.next = head2;
head2 = head2.next;
}
curNode = curNode.next;
}
if(head1 == null)
curNode.next = head2;
else
curNode.next = head1;
while(curNode.next != null) {
curNode = curNode.next;
}
return new ListNode[]{dummyHead.next, curNode};
}
}
TIP:当方法需要返回2个节点时,不一定就用内部类,用数组ListNode[]也行,还能简化些和更清晰些。
TIP:当需要很多局部变量时,可以先循环内定义把逻辑写完,后面再优化提出到循环外定义,直接一开始就把所有变量定义好比较难想,而且容易昏乱
TIP:这里不要采用快慢指针求双链表了,而是用splitList方法,取之后step长的链表并断开,用快慢指针逻辑会变复杂,又要返回断开后2个链表的头,还要返回下一轮迭代链表的头,还要考虑链表长度在0-step,step-2step, >2step几种情况,太烦了。
还是递归好,迭代写起来还挺麻烦的,而且logn空间复杂度其实非常低,n = 1百万时 logn才20,1亿时logn也就26.57左右,其实相比之下还是非常小的。