1. 分割链表到底在考什么
1.1 先看懂题目:手跑一遍最直观
力扣上的“分隔链表”这道题,题号是86,英文名Partition List。题面不长,一句话就能说清楚:给定一个链表和一个值x,把所有小于x的节点放到大于或等于x的节点前面,并且保持每一段里节点的原始相对顺序。
如果你第一次接触,我建议先别急着看题解,拿笔手写一个例子跑一遍。比如:
原链表:1 -> 4 -> 3 -> 2 -> 5 -> 2,x = 3
按题意拆开看:
- 小于3的节点:1、2、2
- 大于等于3的节点:4、3、5
再按题目要求拼起来:1 -> 2 -> 2 -> 4 -> 3 -> 5
你发现没有,这题不是排序。结果里4和3仍然保持着原顺序(4在3前面),2和5也保持着原顺序。真正做的只是按x划了一道分界线,左边的都小于x,右边的都大于等于x。
很多人容易在这里想歪,总以为要排序。一旦去排序,相对顺序就没法保证,复杂度也从O(n)变成O(n log n),完全是往坑里走。
1.2 为什么别急着转数组
我见过不少人在刷链表题时有个习惯,第一反应是把链表转成数组,处理完再重建链表。对某些题这确实能过,但对这道分割链表,强烈不建议。
原因很简单。转数组开辟了O(n)的额外空间,而且丢失了这题真正想考的点。力扣为什么爱出链表题?因为它考察的是指针操作、引用赋值、边界处理这些基本功。你用数组做,相当于绕过了考点。面试的时候考官很大概率会追问一句:“能不能不额外开空间?”到那时候你再临时想指针解法,心态就崩了。
而且从链表本身的性质来看,它天生适合原地调整。节点还是那些节点,不需要新建,只需要改一改next的指向。这就是“拆”和“接”的艺术。
1.3 最正统的解法:两个哨兵链表
这道题最经典的叫法是“双链表法”或者“双指针法”,本质上是用两个哨兵节点(dummy node)分别维护两条新链。
具体来说:
- 一条链收集所有小于x的节点
- 一条链收集所有大于等于x的节点
- 遍历完原链表后,把两条链拼接起来
为什么要用哨兵节点?因为你会发现原链表的头节点可能小于x,也可能大于等于x。如果不用哨兵,你在往两条链里挂第一个节点时,就得专门判断“当前链是不是空的”,代码会变丑,边界条件也会变多。用哨兵之后,所有节点都统一往“尾部”挂,最后返回哨兵的next就行,省去了一堆if判断。
你要记住一个感觉:哨兵节点就像修路时路边插的临时路标,它不参与最终结果,但帮你把方向标清楚了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 双链表法完整实现,逐行拆给你看
2.1 Python版本:刷题最顺手的写法
python复制# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
small = ListNode(-1)
large = ListNode(-1)
p_small = small
p_large = large
cur = head
while cur:
if cur.val < x:
p_small.next = cur
p_small = p_small.next
else:
p_large.next = cur
p_large = p_large.next
cur = cur.next
p_large.next = None
p_small.next = large.next
return small.next
如果你看我上面写的,会发现真正核心的代码不到十行。但每一行都有讲究。
第10行的ListNode(-1)就是dummy节点,我习惯用-1或者0,因为题目里节点值范围不影响这个哨兵本身。p_small和p_large两个尾指针,分别指向两条链的当前尾部。遍历的时候,cur每走到一个节点,按值大小决定挂到哪条链上,然后把对应的尾指针往后挪一步。
第20行p_large.next = None是断尾操作,没有这一步你会被环搞疯。第21行把大值链接到小
