每天学习一点算法 2026/05/11
题目:排序链表
给你链表的头结点
head,请将其按 升序 排列并返回 排序后的链表 。
其实链表的排序和数组的排序算法思想是差不多的, 冒泡、选择、插入 这些排序方法基本的都可以使用,但是因为链表只能从前往后遍历,所以一些需要随机访问元素以及从后往前查找这种优化的操作就做不了。
这里我们先写一种 O(n²) 时间复杂度算法中相对效率最高的插入排序。
我们知道数组的插入排序其实就是维护一个已经排序的数组初始放入数组第一项,然后遍历数组剩余元素,将元素插入到第一个比它大的元素前面,这样就可以实现升序排序了,链表的插入排序也可以利用这种思想实现排序:
- 首先我们需要维护一个虚拟链表头
virtualHead,最终返回virtualHead.next - 我们将
virtualHead.next --> head - 然后我们需要维护两个指针
lastSorted和current,lastSorted已排序部分的最后一个节点,current表示当前需要插入的节点 - 初始化
lastSorted--> head,current --> head.next - 然后我们需要比较
current.val和lastSorted.val的大小 current.val >= lastSorted.val表示current节点应该在lastSorted的后面,我们直接移动lastSorted指针到下一个即可,lastSorted = lastSorted.nextcurrent.val < lastSorted.val表示current需要插入到已排序部分中,我们需要遍历lastSorted前面的节点找到小于它的节点,将插入到它后面
typescript
function sortList(head: ListNode | null): ListNode | null {
if (head == null || head.next == null) return head // 节点小于 2 不需要排序
const virtualHead = new ListNode(0) // 虚拟节点
virtualHead.next = head // 初始 next 指向 head
let lastSorted = head, current = head.next // 初始化 `lastSorted` 和 `current`
// 遍历剩余未排序节点
while (current !== null) {
if (current.val >= lastSorted.val) {
// 当前节点大于等于已排序的最大节点,直接移动 lastSorted 指针
lastSorted = lastSorted.next
} else {
// 当前节点小于已排序的最大节点
let pre = virtualHead
// 遍历找到一个最后一个小于的节点
while (current.val >= pre.next.val) {
pre = pre.next
}
// 将 current 取出,插入到 pre 后面
lastSorted.next = current.next
current.next = pre.next
pre.next = current
}
// 移动 current 到下一个为排序的节点
current = lastSorted.next
}
return virtualHead.next
};
这个方法极端情况下依旧是 O(n²) 的时间复杂度,我们可以利用归并的思想,将链表不断平分,将拆分排序后的链表合并在一起,就可以得到最终的排序链表。
合并的方法可以参考 合并K个排序链表 里面的合并方法
typescript
/**
* Definition for singly-linked list.
* class ListNode {
* val: number
* next: ListNode | null
* constructor(val?: number, next?: ListNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
* }
*/
function sortList(head: ListNode | null): ListNode | null {
if (head == null || head.next == null) return head
function mergeTwoLists (listA: ListNode | null, listB: ListNode | null) {
// 当合并的其中一个链表为空时,直接返回另一个链表
if (!listA || !listB) return listA ? listA : listB
const head = new ListNode(0) // 新建一个链表
let tail = head // 合并结果指针, 初始指向 head
// 当合并的两个链表都还有剩余节点时循环
while (listA && listB) {
// 比较两个链表当前节点值大小
if (listA.val > listB.val) {
tail.next = listB
listB = listB.next
} else {
tail.next = listA
listA = listA.next
}
// 更新合并节点指针
tail = tail.next
}
// 当某一个链表合并完毕,处理剩余节点
tail.next = listA ? listA : listB
// 返回合并后链表表头
return head.next
}
function toSortList (head: ListNode, tail: ListNode): ListNode {
if (head == null) return head // head 为 null 直接返回
if (head.next == tail) {
// head.next 为 tail 表示 head 已经是分区的最后一个节点,截断后续节点后返回
head.next = null
return head
}
// 利用快慢指针找到中间节点
let fast = head, slow = head
while (fast !== tail) {
fast = fast.next
slow = slow.next
if (fast !== tail) {
fast = fast.next
}
}
const mid = slow
// 递归传递左右分区的头尾,并将结果进行升序合并
return mergeTwoLists(toSortList(head, mid), toSortList(mid, tail))
}
return toSortList(head, null)
};
题目来源:力扣(LeetCode)