← 返回题目列表

如何按给定值分隔链表并保持相对顺序?

高频 中等 第 5 / 28 题 更新于 2026/07/29
链表分隔链表双指针dummy

简化版

分隔链表通常用两个 dummy:一个收集小于 x 的节点,一个收集大于等于 x 的节点。遍历原链表时按条件尾插到两条链表,最后把小链表尾部接到大链表头部,并断开大链表尾部的旧 next。

详细版

例如:

1 -> 4 -> 3 -> 2 -> 5 -> 2, x=3
结果:
1 -> 2 -> 2 -> 4 -> 3 -> 5

要保持相对顺序,不能简单左右交换。正确做法是维护 smallTaillargeTail,扫描到小于 x 的节点接到 small,其他接到 large。最后 smallTail.next = largeDummy.nextlargeTail.next = null,防止旧指针形成环。

完整版教学

一、分隔链表的目标不是排序

题目只要求把小于 x 的节点放前面,大于等于 x 的节点放后面,并保持两部分内部的原相对顺序。

input:  1 -> 4 -> 3 -> 2 -> 5 -> 2, x=3
output: 1 -> 2 -> 2 -> 4 -> 3 -> 5

注意 4,3,5 的相对顺序没变,1,2,2 的相对顺序也没变。这不是快排 partition 的不稳定交换版本。

记忆钩子:链表分隔是“两条链表收集”,不是原地乱换。

二、为什么用两个 dummy

我们需要构造两条结果链表:small 和 large。每条链表都要处理“第一次插入”的头节点问题,所以用两个 dummy 最清爽。

smallDummy -> 小于 x 的节点
largeDummy -> 大于等于 x 的节点

同时维护两个尾指针:

ListNode smallTail = smallDummy;
ListNode largeTail = largeDummy;

这样每来一个节点,都能 O(1) 尾插到对应链表。

三、遍历时按条件尾插

核心代码:

while (head != null) {
  if (head.val < x) {
    smallTail.next = head;
    smallTail = smallTail.next;
  } else {
    largeTail.next = head;
    largeTail = largeTail.next;
  }
  head = head.next;
}

这个版本还缺一个关键细节:移动 head 前最好保存 next,或者最后一定要断尾。因为节点原来的 next 仍然保留,可能把两条链串乱。

更稳写法:

ListNode next = head.next;
head.next = null;
// 接到 small 或 large
head = next;

提前断开能减少环和旧连接问题。

四、最后拼接两条链表

遍历完成后:

small: 1 -> 2 -> 2
large: 4 -> 3 -> 5

拼接:

smallTail.next = largeDummy.next;
largeTail.next = null;
return smallDummy.next;

如果所有节点都小于 x,largeDummy.next 是 null,也能自然处理。如果所有节点都大于等于 x,smallDummy.next 为空,直接返回 large 链表需要注意;通常可以先拼接再返回:

smallTail.next = largeDummy.next;
return smallDummy.next;

当 small 为空时,smallTail 仍是 smallDummy,返回 smallDummy.next 正好是 large 头。

五、为什么不能用数组式交换思路

数组 partition 常用左右指针交换,速度快但不稳定。链表题要求保持相对顺序时,交换节点会破坏顺序。

方法是否保持顺序适合
两条链表尾插保持本题标准做法
左右交换值不一定不要求稳定时
收集到数组再重建保持但额外空间 O(n)

链表的优势是节点可以 O(1) 接到尾部,只要维护 tail 就能稳定分组。

六、复杂度和断链细节

每个节点访问一次,时间 O(n)。两个 dummy 和几个指针,额外空间 O(1)。

断链是高频易错点。输入链表节点原本连在一起,如果不把 large 的尾部 next 置空,可能出现:

largeTail.next still points to old node
-> result contains extra nodes
-> even forms cycle

所以最后 largeTail.next = null 是安全习惯。

七、常见误区与追问

  • 误区:分隔链表就是排序链表。 只要求按 x 分两区,区内相对顺序保持。
  • 误区:可以直接交换节点值。 交换值可能破坏稳定性,也不符合重连节点的题意。
  • 误区:拼接后不用断尾。 原链表旧 next 可能造成多余连接或环。
  • 追问:为什么用两个 dummy? 避免分别处理 small 和 large 第一次插入的头节点。
  • 追问:空间复杂度为什么是 O(1)? 没创建新业务节点,只重用原节点并用常数指针。
  • 追问:如果全部小于 x 或全部大于等于 x 怎么办? dummy 拼接逻辑能自然覆盖这些边界。

八、加强记忆

分隔链表记成“两桶稳定收集”。一个 dummy 收小节点,一个 dummy 收大节点,遍历原链表时尾插到对应桶,最后 small 接 large。关键是保持相对顺序,不要用数组式不稳定交换;收尾时要断开 largeTail.next,防止旧指针把结果链表污染。