86. 分隔链表
86. 分隔链表
题目链接:86. 分隔链表
题目描述
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
核心思考:双虚拟头节点构建法
本题最直观的解法是模拟法。
与其在原来的链表上进行复杂的节点移除和插入操作,不如直接建立两条全新的链表:
- 小链表 (
dummy1):专门用来接收所有值< x的节点。 - 大链表 (
dummy2):专门用来接收所有值>= x的节点。
我们只需遍历原链表,遇到小于 x 的节点将其链接到 dummy1 后,大于等于 x 的节点将其链接到 dummy2 后。遍历结束后,再将 dummy1 的尾部连接到 dummy2 的实际头部(即 dummy2.next),即可完成链表的分隔。
细节:处理链表成环(断尾)
在将原链表节点重组到新链表时,一个常见的错误是链表成环。这是因为节点挂载到新链表时,依然保留着原始的 next 指针。如果不加以处理,新链表的尾节点可能会指向链表中的某个前驱节点,从而导致结构成环。
解决这个问题,有两种常见的处理思路(代码中均有注释说明):
- 法 1:逐个断开
next指针
在将当前节点curp接入新链表之前,先利用临时变量将其下游curp.next暂存,随后立刻将curp.next置为None。这样可以保证接入新链表的每个节点都是独立的,从根本上阻断了成环的可能。 - 法 2:遍历结束后统一断开结尾
在挂载节点的过程(如p1.next = curp)中,大部分节点的next指针已经被重新指向了正确的下一个节点。因此在遍历中,我们可以直接推进curp = curp.next,无需刻意断开原指针。然而,当遍历结束时,大链表的尾节点p2的next指针依旧保留着原始状态。如果它恰好指向了一个被分配至小链表中的节点,拼接两链表后便会成环。
因此,使用法 2 的核心在于:在退出循环后、拼接大小链表之前,必须显式地执行一次p2.next = None,强制断开尾节点的旧指针。 这种做法相较于法 1 更加简洁,也是在算法实现中更为标准的推荐写法。
解题思路 (Python)
1 | # Definition for singly-linked list. |
复杂度分析
- 时间复杂度:$O(n)$,其中 $n$ 是原链表的长度。我们仅仅对原链表进行了一次线性无回溯的遍历。
- 空间复杂度:$O(1)$。虽然我们“建立”了两条新链表,但实际上只是额外声名了几个虚拟节点 (
dummy1,dummy2,p1,p2) 的指针变量,那些庞大的节点内存全部都是原地复用的原链表节点对象,并没有申请 $O(n)$ 的新内存。