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

相关推荐
for_ever_love__7 小时前
UI学习:UISearchController基础了解和应用
学习·ui·ios·objective-c
心中有国也有家7 小时前
GE图引擎深度解析——CANN的计算图优化与执行引擎
人工智能·pytorch·python·学习·numpy
地平线开发者8 小时前
profiler debug 工具用法与高一致性策略
算法·自动驾驶
编程大师哥8 小时前
匿名函数 lambda + 高阶函数
java·python·算法
GHL2842710908 小时前
换脸工作流学习
学习·ai
我叫袁小陌8 小时前
算法解题思路指南
算法
地平线开发者8 小时前
Conv+BN+Add+ReLU 融合机制简介
算法·自动驾驶
_李小白9 小时前
【android opencv学习笔记】Day 28: 滤波算法之中值滤波器
android·opencv·学习
yuanyuan2o29 小时前
模型预训练:Hugging Face Transformers 基础
算法·ai·语言模型·自然语言处理·nlp·深度优先