题目: 在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
输入: 4->2->1->3 输出: 1->2->3->4
对一个链表进行排序,且时间复杂度要求为 O(nlogn) ,空间复杂度为常量。一看到 O(nlogn)的排序,首先应该想到归并排序和快速排序,但是通常我们使用这两种排序方法时都是针对数组的,现在是链表了。
归并排序法:在动手之前一直觉得空间复杂度为常量不太可能,因为原来数组使用归并时,都是 O(N)的,需要复制出相等的空间来进行赋值归并。对于链表,实际上是可以实现常数空间占用的(链表的归并排序不需要额外的空间)。
利用归并的思想,递归地将当前链表分为两段,然后merge,分两段的方法是使用 fast-slow 法,用两个指针,一个每次走两步,一个走一步,直到快的走到了末尾,然后慢的所在位置就是中间位置(注意1->2这种情况),这样就分成了两段。merge时,把两段头部节点值比较,用一个冗余节点head当作新链表头结点,cur初始值head,每一次比较两个链表的头,cur->next指向left和right链表头较小的节点,移动对应链表头和cur。
主要考察3个知识点,
知识点1:归并排序的整体思想
知识点2:找到一个链表的中间节点的方法
知识点3:合并两个已排好序的链表为一个新的有序链表
Java:
class Solution {
ListNode sortList(ListNode head) { // 归并,返回排序好的链表头
if (head == null || head.next == null) return head;
ListNode halfListNode = findHalfNode(head);
ListNode rightListHead = halfListNode.next;//右半部分链表头
halfListNode.next = null;
// 不能先调用两个递归sortList之后,传给mergeList参数:head和rightHalfHead
// 因为上一次合并后的链表 head 和 rightHalfHead指向的节点不一定是头节点了
return mergeList(sortList(head), sortList(rightListHead));
}
// 合并并排序两个未排序链表
ListNode mergeList(ListNode left, ListNode right) {
ListNode dummy = new ListNode(-1);
ListNode fail = dummy;
while (left != null && right != null) {
if (left.val < right.val) {
fail.next = left;
left = left.next;
} else {
fail.next = right;
right =right.next;
}
fail = fail.next;
}
if (left == null) fail.next = right;
if (right == null) fail.next = left;
return dummy.next;
}
// 快慢指针找中间节点
ListNode findHalfNode(ListNode head) {
if (head == null || head.next == null) return head;
ListNode slow = head, fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
C++:
ListNode* sortList(ListNode* head) {
return head == NULL ? head : sortListCore(head);
}
ListNode* sortListCore(ListNode* head) {
//递归出口,说明此子链表只剩一个节点,
//最后一次拆分链表完成,开始合并
//不能是head==null 因为head链表只有一个元素的时候
//midnode一直是这个元素 rightNode 一直是null
//head = sortListCore(head);会死递归
if (head->next == NULL) return head;
ListNode *mid = findMid(head); // 返回链表中间节点
ListNode *rightHead = mid->next; //分割的右链表头
mid->next = NULL; // 左链表尾
head = sortListCore(head); //左递归
rightHead = sortListCore(rightHead);//右递归
return mergeList(head, rightHead);//合并左右两个子链表
}
// 合并两个链表,用冗余节点
ListNode *mergeList(ListNode *leftHead, ListNode *rightHead) {
ListNode *dummyHead = new ListNode(-1);
ListNode *cur = dummyHead, *listA = leftHead, *listB = rightHead;
while (listA != NULL && listB != NULL) {
if (listA->val < listB->val) {
cur->next = listA;
listA = listA->next;
} else {
cur->next = listB;
listB = listB->next;
}
cur = cur->next; // cur每次都要前进
}
if (listA != NULL) cur->next = listA; // 处理剩下的
else cur->next = listB;
ListNode *ans = dummyHead->next;
delete dummyHead;
return ans;
}
ListNode *findMid(ListNode *head) {//快慢指针找链表的中间节点
ListNode *slow = head, *fast= head;
//fast->next->next 防止1,2这种情况返回2
while (fast->next != NULL && fast->next->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
一定注意:while 条件 要包含 fast->next->next != NULL
否则 1->2 这种情况返回mid=2,令右边链表头(rightHead=mid->next)为NULL
可能你会说,在sortListCore递归出口里加上head==NULL的条件,
让rightHead是空的时候返回,
但是这时候1->2这种情况left链表一直被分割成1->2,right链表一直是NULL,
一直得不到处理啊!死循环!!