【码道初阶】Leetcode面试题02.04:分割链表[中等难度]

链表稳定分区:一次遍历,分成两段再拼接(Partition List)

题目描述

给定单链表头结点 head 和一个值 x,要求把所有 小于 x 的结点排在 其余结点 之前,并且 不能改变原来的相对顺序(也就是"稳定")。返回重新排列后的链表头结点。

这类题看似在"排序",但其实更像在做稳定分区:把元素按条件分到两个桶里,桶内顺序保持不变。


为什么不能简单"往前插"?

很多人第一反应:遇到 < x 的结点就插到链表前面。

问题是:插入到前面会逆序------后遇到的小结点会跑到先遇到的小结点前面,稳定性直接破坏。

想要稳定,最稳的方式是:

  • < x 的结点按遍历顺序串成一条链
  • >= x 的结点按遍历顺序串成一条链
  • 最后把两条链拼起来

核心思路:两个区间、四个指针

用四个指针维护两段链表的"头和尾":

  • bs / be:小于 x 的区间(before start / before end)
  • as / ae:大于等于 x 的区间(after start / after end)

再用 cur 从头到尾扫一遍原链表:

  • 如果 cur.val < x:追加到 bs~be 的尾部
  • 否则:追加到 as~ae 的尾部

这样做的好处是:
只做尾插,不做头插 → 桶内顺序天然保持不变(稳定)。


代码实现(按这个思路)

java 复制代码
class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode cur = head;
        ListNode bs = null, be = null; // 小于x区间:头/尾
        ListNode as = null, ae = null; // 大于等于x区间:头/尾
        if (head == null) return null;

        while (cur != null) {
            if (cur.val < x) {
                if (bs == null) {
                    bs = be = cur;      // 第一次放入小区间
                } else {
                    be.next = cur;      // 尾插
                    be = be.next;
                }
            } else {
                if (as == null) {
                    as = ae = cur;      // 第一次放入大区间
                } else {
                    ae.next = cur;      // 尾插
                    ae = ae.next;
                }
            }
            cur = cur.next;
        }

        // 如果小区间为空,直接返回大区间
        if (bs == null) return as;

        // 拼接:小区间尾 -> 大区间头
        be.next = as;

        // 关键收尾:如果大区间不为空,要断开大区间尾巴,避免"旧next"带来串链/成环
        if (as != null) ae.next = null;

        return bs;
    }
}

这题最容易踩的坑:最后一个 next 不是你以为的 null

这也是这题最"阴"的点。

因为我们复用的是原链表节点 ,每个节点最初的 next 都指向原来的后继。

当你把节点"分流"进两个区间时,你其实只是改了某些节点的 next,但最后一个节点很可能还保留着"旧世界"的指针:

  • 轻则:新链表尾部莫名其妙多出一段
  • 重则:形成环,遍历直接死循环

所以你在拼接后加的这一句非常关键:

java 复制代码
if (as != null) ae.next = null;

它的意义是:强制让最终链表的尾结点指向 null,把旧链表残留关系彻底切断。

另外你题解里那句总结也很到位:

如果两段都可能有元素,就存在最后一个结点的原指针不为 null 的情况,需要手动调整。


边界情况怎么处理?

  • 全都在大区间bs == null):说明没有 < x 的节点,直接返回 as,顺序本来也没变。
  • 全都在小区间as == null):拼接时 be.next = null,返回 bs,同样没问题。

复杂度

  • 时间:O(n)(只遍历一次)
  • 额外空间:O(1)(只用了几个指针变量)

一个实用的小建议(写链表更稳)

这份写法最后统一断尾也能通过。实际工程/面试里,为了更"抗风险",常见做法是遍历时先把当前节点从原链表"摘下来":

java 复制代码
ListNode next = cur.next;
cur.next = null;  // 先断开,避免旧next污染
// 再把cur接到bs/be或as/ae后面
cur = next;

这样就算后面忘了断尾,也不容易出"尾巴拖着旧链条"的问题。


总结

这题的正确打开方式不是"插来插去",而是:

分流(稳定尾插)→ 拼接(小尾接大头)→ 断尾(避免旧next污染)

把这套"两个区间四指针"的模板记牢,后面很多链表题(稳定重排、按条件拆分、奇偶重排、分隔链表)都能一把套上去。

相关推荐
铉铉这波能秀4 分钟前
LeetCode Hot100数据结构背景知识之字典(Dictionary)Python2026新版
数据结构·python·算法·leetcode·字典·dictionary
蜡笔小马13 分钟前
10.Boost.Geometry R-tree 空间索引详解
开发语言·c++·算法·r-tree
kali-Myon13 分钟前
2025春秋杯网络安全联赛冬季赛-day1
java·sql·安全·web安全·ai·php·web
我是咸鱼不闲呀17 分钟前
力扣Hot100系列20(Java)——[动态规划]总结(下)( 单词拆分,最大递增子序列,乘积最大子数组 ,分割等和子集,最长有效括号)
java·leetcode·动态规划
唐梓航-求职中21 分钟前
编程-技术-算法-leetcode-288. 单词的唯一缩写
算法·leetcode·c#
仟濹23 分钟前
【算法打卡day3 | 2026-02-08 周日 | 算法: BFS】3_卡码网99_计数孤岛_BFS | 4_卡码网100_最大岛屿的面积DFS
算法·深度优先·宽度优先
清水白石00826 分钟前
深入解析 LRU 缓存:从 `@lru_cache` 到手动实现的完整指南
java·python·spring·缓存
Ll130452529827 分钟前
Leetcode二叉树part4
算法·leetcode·职场和发展
Queenie_Charlie34 分钟前
stars(树状数组)
数据结构·c++·树状数组
颜酱36 分钟前
二叉树遍历思维实战
javascript·后端·算法