【每天学习一点算法 2026/05/11】排序链表

每天学习一点算法 2026/05/11

题目:排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

其实链表的排序和数组的排序算法思想是差不多的, 冒泡、选择、插入 这些排序方法基本的都可以使用,但是因为链表只能从前往后遍历,所以一些需要随机访问元素以及从后往前查找这种优化的操作就做不了。

这里我们先写一种 O(n²) 时间复杂度算法中相对效率最高的插入排序。

我们知道数组的插入排序其实就是维护一个已经排序的数组初始放入数组第一项,然后遍历数组剩余元素,将元素插入到第一个比它大的元素前面,这样就可以实现升序排序了,链表的插入排序也可以利用这种思想实现排序:

  • 首先我们需要维护一个虚拟链表头 virtualHead ,最终返回 virtualHead.next
  • 我们将 virtualHead.next --> head
  • 然后我们需要维护两个指针 lastSortedcurrentlastSorted 已排序部分的最后一个节点, current 表示当前需要插入的节点
  • 初始化 lastSorted--> headcurrent --> head.next
  • 然后我们需要比较 current.vallastSorted.val 的大小
  • current.val >= lastSorted.val 表示 current 节点应该在 lastSorted 的后面,我们直接移动 lastSorted 指针到下一个即可,lastSorted = lastSorted.next
  • current.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)

相关推荐
复杂网络3 小时前
论最小 Agent 计算机的形态
算法
kisshyshy18 小时前
🍦 雪糕、食堂、火车厢:三幅漫画吃透栈、队列与链表
javascript·算法
猿人谷1 天前
不只是 CPU 阈值:STAR 如何用 GAT + Transformer 做容器级自动扩缩容?
人工智能·算法
复杂网络1 天前
Stable Diffusion 视觉大模型微调技术深度调研
算法
复杂网络1 天前
基于 Stable Diffusion 架构的视觉大模型代表性工作与原理深度解析
算法
MrZhao4001 天前
Agent Loop 如何用 Hook 扩展:权限、日志与工具拦截
算法
MrZhao4001 天前
Agent 为什么需要 Skills:别把所有知识都塞进 system prompt
算法
JieE2123 天前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
JieE2124 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
Jack204 天前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法