【每天学习一点算法 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)

相关推荐
鱼听禅2 分钟前
ubuntu学习笔记-使用ls-ml-toolkit工具识别Label Studio数据数据训练yolo模型
笔记·学习·ubuntu
AI科技星1 小时前
基于全域数学公理体系求解三元约束极值题【乖乖数学】
人工智能·算法·机器学习·密码学·拓扑学·乖乖数学·全域数学
伊玛目的门徒8 小时前
试用leetcode之典中典 二数之和问题
java·算法·leetcode
Jerry9 小时前
LeetCode 226. 翻转二叉树
算法
想做小南娘,发现自己是女生喵10 小时前
【无标题】
数据结构·算法
xian_wwq10 小时前
【学习笔记】模型权重管理:Safetensors与私有化Hub(23/35)
笔记·学习
其实防守也摸鱼11 小时前
运维--学习阶段问题解答(1)(自测)
linux·运维·服务器·数据库·学习·自动化·命令模式
ctrl_v助手11 小时前
Halcon学习笔记2
人工智能·笔记·学习
Kx_Triumphs12 小时前
HDU4348 To the moon(主席树区间修改模板)
算法·题解