33.排序链表(学习)
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
示例 1:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例 2:
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
示例 3:
输入:head = []
输出:[]
提示:
链表中节点的数目在范围 [0, 5 * 104] 内
-105 <= Node.val <= 105
解析:
1. 归并排序的基本概念
归并排序是一种分而治之的算法,它将大问题分解为小问题来解决,然后将解决的小问题合并起来以解决整个问题。在链表排序中,我们首先将链表分成两半,分别对这两半进行排序,然后将排序后的两半合并成一个有序链表。
2. 链表的中点分割
由于链表不支持像数组那样的随机访问,我们不能直接通过索引来找到链表的中点。相反,我们使用快慢指针技巧。快指针每次移动两步,慢指针每次移动一步。当快指针到达链表末尾时,慢指针将位于链表的中点(或接近中点,对于偶数长度的链表)。
3. 递归排序
对分割后的两个子链表(左半部分和右半部分)分别进行归并排序。这是通过递归调用sortList函数来实现的。
4. 合并有序链表
将两个排序后的链表合并成一个有序链表。这通过比较两个链表的当前节点值来实现,并将较小的节点连接到结果链表的末尾。
5. 终止条件
递归的终止条件是链表为空或链表只有一个节点,这两种情况下链表已经是有序的,不需要进一步排序。
6. 完整过程
整个归并排序过程从链表的头节点开始,通过递归地找到中点、分割链表、排序子链表,并将排序后的子链表合并,直到整个链表被排序完成。
// 归并排序的合并函数
function merge(l1, l2) {
let dummy = new ListNode(0);
let current = dummy;
while (l1 && l2) {
if (l1.val < l2.val) {
current.next = l1;
l1 = l1.next;
} else {
current.next = l2;
l2 = l2.next;
}
current = current.next;
}
// 连接剩余部分
if (l1) {
current.next = l1;
}
if (l2) {
current.next = l2;
}
return dummy.next;
}
// 归并排序的主函数
function sortList(head) {
// 空链表或只有一个节点的链表不需要排序
if (!head || !head.next) {
return head;
}
// 使用快慢指针找到中点,进行分割
let slow = head, fast = head, prev = null;
while (fast && fast.next) {
prev = slow;
slow = slow.next;
fast = fast.next.next;
}
// 分割链表
prev.next = null;
// 递归地对左右两部分进行排序,并合并
let left = sortList(head);
let right = sortList(slow);
return merge(left, right);
}