LeetCode 86中等链表 · 双指针
分隔链表 图解题解
这道题到底在问什么
给定链表头 head 和值 x,对链表进行分隔:使所有 < x 的节点都排在所有 ≥ x 的节点之前,并保留两个分区里各自节点的初始相对顺序。
- 输入
- head = 1→4→3→2→5→2, x = 3
- 输出
- 1→2→2→4→3→5
最优解:一步一步想明白
- 3看到 1→2→2→4→3→5 很多人以为要排序——其实 4 还在 3 前面、根本没排好。规则只有一条:小的整体在前、大的整体在后,每堆内部保持原来的先后。
- 4准备两条空链表,各自焊一个哑节点:small 专收小于 x 的,large 专收大于等于 x 的。原链表从头扫一遍,每个节点按值丢进对应的链;扫完把 small 的尾巴接到 large 的头,大功告成。
- 5small / large 都是空哑头上面是原链表,cur 站在头节点 1。下面两条是空链:small(Sd 哑头)、large(Ld 哑头),各有一个尾指针 sp / lp 守在自己哑头上,等着往后挂节点。
- 6cur 值 < 3 → 小堆;否则大堆判据只有一条:看 cur 当前节点的值。小于 3 就挂到 small 尾,大于等于 3 就挂到 large 尾。下面一格一格走给你看。
- 71 < 3,归小堆cur 的值是 1,1 < 3 成立 → 归 small。把节点 1 挂到 small 链的尾巴上(标绿),原链里它也标绿表示「已分流」。
- 8sp=1, cur 移到 4尾指针 sp 跟着前移到刚挂上的 1(成为新的尾)。原链 cur 往后走一格,来到 4,继续判断。
- 94 ≥ 3,归大堆cur 的值是 4,4 ≥ 3 → 归 large。把节点 4 挂到 large 链尾(标红),原链里 4 也标红表示进了大堆。
- 10lp=4, cur 移到 3尾指针 lp 跟到 large 新尾 4。原链 cur 前移到 3。
- 113 ≥ 3,归大堆cur 是 3。判定条件是 小于 x,3 不小于 3(等于也算大堆) → 归 large。把 3 挂到 large 尾(标红)。这一格就是雷区:用的是 < 不是 ≤。
- 12lp=3, cur 移到 2lp 跟到 large 新尾 3。原链 cur 前移到第一个 2。
- 132 < 3,归小堆cur 是第一个 2,2 < 3 → 归 small。挂到 small 尾(标绿),它接在 1 后面,顺序自然保住。
- 14sp=2, cur 移到 5sp 跟到 small 新尾(第一个 2)。原链 cur 前移到 5。
- 155 ≥ 3,归大堆cur 是 5,5 ≥ 3 → 归 large。挂到 large 尾(标红),接在 3 后面。
- 16lp=5, cur 移到第二个 2lp 跟到 large 新尾 5。原链 cur 前移到最后一个 2。
- 172 < 3,归小堆最后一个 2,2 < 3 → 归 small,挂到 small 尾(标绿)。它排在前一个 2 后面——两个 2 的先后被完整保留。
- 18扫描完成,cur = nullsp 跟到 small 最后的 2。原链 cur 已走到 null,整条扫完。绿格全归 small、红格全归 large——分流完毕,只差拼接。
- 19small=1,2,2 · large=4,3,5停下来看一眼:small 里 1→2→2、large 里 4→3→5,两堆内部都严格保持原链顺序(尾插的天然结果)。注意 large 是 4→3→5,根本没排序——这正符合题意。
- 20lp.next = null拼接前先把 large 的尾巴 lp.next 置为 null。为什么?最后那个 5 在原链里本来还连着别的指针残留,不断开会成环或拖出多余节点。这一步是最容易漏的雷。
- 21sp.next = large.next关键一接:让 small 的尾 sp.next 指向 large.next(也就是 large 的第一个真节点 4)。small 的 1→2→2 后面直接续上 large 的 4→3→5。
- 22Sd → 1→2→2→4→3→5现在整条链是 Sd→1→2→2→4→3→5,绿色是原来 small 的 3 个,后半段刚接上的 4→3→5 是 large 段。哑头 Sd 还杵在最前面,得跳过它。
- 23answer = 1→2→2→4→3→5返回 small.next(跳过哑头 Sd),就是最终链 1→2→2→4→3→5。小的整体在前、大的整体在后,且各堆内部原序未变——完美符合题意。
- 26一句话记牢:small 收小、large 收大,扫完 large 尾置 null 再把 small 尾接 large 头,返回 small.next。
- 28lp.next 没断 → 5 还指回 2如果忘了 lp.next = null:原链里 5 后面那个 2 早被搬进 small 了,5 的 next 还指着它,于是链尾 5 回头指向 2 形成环(尾部 ↺)。遍历这条链会死循环。这就是必须收口的原因。
- 35这题学完别乱跳,去 链表专题 连刷同类「双指针 + 哑头」题;卡住了可以随时在页面里提问「为什么这一步成立」。
⚠️ 容易写错的地方
✗ 错:忘了把 large 尾的 next 置 null
✓ 对:扫完先 lp.next = null
原链最后节点的 next 残留,不断开会拖出多余节点甚至成环(死循环)。
✗ 错:判定写成 val <= x
✓ 对:必须严格 val < x
题目要求 < x 进前面,等于 x 的要留在后面;写 ≤ 会把等于 x 的节点错放到前堆。
✗ 错:想着先排序再分
✓ 对:稳定分区,堆内保持原序
题目不要求整体有序(4 仍在 3 前),排序既多余又破坏相对顺序。
完整代码(Python / C++ / Java)
Python
class Solution:
def partition(self, head, x):
small = ListNode(0) # 小堆哑头
large = ListNode(0) # 大堆哑头
sp, lp = small, large # 两个尾指针
while head:
if head.val < x: # 严格小于才进小堆
sp.next = head; sp = sp.next
else:
lp.next = head; lp = lp.next
head = head.next
lp.next = None # 大堆尾收口,防成环
sp.next = large.next # 小堆尾接大堆头
return small.nextC++
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
ListNode small(0), large(0);
ListNode* sp = &small; ListNode* lp = &large;
while (head) {
if (head->val < x) { sp->next = head; sp = sp->next; }
else { lp->next = head; lp = lp->next; }
head = head->next;
}
lp->next = nullptr; // 大堆尾收口
sp->next = large.next; // 小尾接大头
return small.next;
}
};Java
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode small = new ListNode(0);
ListNode large = new ListNode(0);
ListNode sp = small, lp = large;
while (head != null) {
if (head.val < x) { sp.next = head; sp = sp.next; }
else { lp.next = head; lp = lp.next; }
head = head.next;
}
lp.next = null; // 大堆尾收口
sp.next = large.next; // 小尾接大头
return small.next;
}
}复杂度
时间复杂度
O(n)
每个节点只被 cur 访问并分流一次,扫一遍即完成
空间复杂度
O(1)
只新建两个哑头 + 几个指针,没有额外按节点数增长的空间(节点是搬移不是复制)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 分隔链表 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么用两个哑头能保证「稳定」(堆内原序不变)?+
因为是从头到尾扫描,遇到属于某堆的节点就直接接到该堆的尾部,先来的先接、后来的后接,天然保持了原始相对顺序,无需任何排序。
这道题为什么用「双指针」,换最直接的暴力解会差在哪?+
双指针抓住了本题的结构特征,把暴力解里重复的工作省掉;暴力解通常要多嵌套一层枚举,数据一大就超时。具体对比见上文「暴力解及其卡点」与「最优解逐步推演」两节。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 分隔链表 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。