题目要求:
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
来源:力扣(LeetCode)
看到这道题真的忘记了用什么方法,即使自己之前学过各种排序,但是时间久了也忘得差不多了。这里要求的是时间复杂度o(nlogn)、空间复杂度为0(1)。我们都知道快速排序、桶排序和归并排序的时间复杂度是o(nlogn),但是快速排序的时间以及空间都是大于等于题目要求的,桶排序可以完成但这里不做解释了,主要说一下由底而上的归并排序。还有一点, 递归的归并排序这里不合适,因为递归深度为logn不符合题目空间复杂度的要求。以下都是从网上找的资料以及一些自己的理解。
首先是各种排序的复杂度,这里直接用的up主花花酱的图
然后自底而上的归并排序的思想是这样的,我们先从一个一个元素归并排序为两个,由两个再到四个,循环logn次。
代码做了注释:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
if(head==null||head.next==null) return head;
ListNode dummy = new ListNode(0); //前指针用于pre指针
int len = getListLen(head);
dummy.next = head; //链接链表
int itrv = 1; //长度
while(itrv < len){
ListNode pre = dummy;
ListNode h = dummy.next; //h指针指向下一次循环的开头节点
while(h!=null){
int i = itrv;
ListNode h1 = h;
for(;h != null && i > 0;i--)//选择需要归并的长度 结尾是h指针 不包含
h = h.next;
if(i>0) //选择h1的长度为空时 链表也是空的
break;
ListNode h2 = h; //h2的开头是h1的结尾
i = itrv;
for(;h != null && i > 0;i--) //截取h2的长度 同h1
h = h.next;
int c1 = itrv; //计算h1 h2 的长度
itn c2 = itrv - i; //h2的长度可能因为总长的奇偶而变
while(c1 > 0 && c2 > 0){ //排序
if(h1.val < h2.val){
pre.next = h1;
h1 = h1.next;
c1--;
}else{
pre.next = h2;
h2 = h2.next;
c2--;
}
pre = pre.next;
}
pre.next = c1 > 0 ? h1 : h2;
while(c1 > 0 || c2 > 0) { //是用来移动pre的位置
pre = pre.next;
c1--;
c2--;
}
pre.next = h; //将排好序的链表和后续未排序的链表链接
}
itrv *= 2;
}
return dummy.next;
}
private int getListLen(ListNode head) {
ListNode cur = head;
int len = 0;
while(cur != null) {
len++;
cur = cur.next;
}
return len;
}
}
难点在于排序,也是跟着代码画图走了好几遍才完全明白的。