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

相关推荐
小糖学代码1 小时前
LLM系列:2.pytorch入门:10.划分训练集与测试集(sklearn.model_selection)
人工智能·python·深度学习·神经网络·学习·sklearn
wefg11 小时前
一些零散的算法
c++·算法
染予1 小时前
共阵面系统学习
网络·学习
khalil10201 小时前
代码随想录算法训练营Day-48 单调栈02 | 42. 接雨水、84.柱状图中最大的矩形
数据结构·c++·算法·leetcode·单调栈·接雨水
Hcoco_me1 小时前
Ai:Agent/ infra / 智驾 / 推广算法 题库
人工智能·深度学习·算法·自动驾驶·剪枝
项目申报小狂人1 小时前
提出了一种带双向搜索的粒子群优化算法,一种基于双四元数运动优化的新型无人机3D路径规划方法及应用
算法·3d·无人机
驼同学.1 小时前
牛客网面试TOP101 - Python算法学习指南
python·算法·面试
生活观察站1 小时前
中星微端侧芯片,赋能多行业智能化转型落地
数码相机·学习
嵌入式-老费1 小时前
esp32开发与应用(怎么用好esp32)
学习