分隔链表(LeetCode 86)
题目链接:分隔链表(LeetCode 86)
难度:中等
1. 题目描述
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
示例:
输入: head = [1,4,3,2,5,2], x = 3
输出: [1,2,2,4,3,5]
输入: head = [2,1], x = 2
输出: [1,2]
2. 问题分析
2.1 规律
- 这是一个链表的划分问题,需要将节点根据其值与
x的关系分成两部分。 - 关键要求是保持每个分区内部节点的 原始相对顺序。这意味着我们不能简单地通过交换节点值来解决,而需要移动节点。
2.2 双链表(双指针)思路
最直观且高效的方法是创建两个新的链表,一个用来存放所有小于 x 的节点,另一个用来存放所有大于或等于 x 的节点。
-
创建哑节点(Dummy Nodes):
less_head:作为 "小于x" 链表的头节点。greater_head:作为 "大于等于x" 链表的头节点。- 使用哑节点可以简化边界条件的处理,避免对头节点的特殊判断。
-
创建遍历指针:
less_ptr:指向 "小于x" 链表的尾部。greater_ptr:指向 "大于等于x" 链表的尾部。
-
遍历原链表:
- 用一个指针
current遍历原链表head。 - 对于每个节点
current:- 如果
current.val < x,则将其链接到less_ptr的后面,并移动less_ptr。 - 如果
current.val >= x,则将其链接到greater_ptr的后面,并移动greater_ptr。
- 如果
- 用一个指针
-
拼接链表:
- 遍历结束后,我们得到了两个独立的链表。
- 将 "小于" 链表的尾部 (
less_ptr) 指向 "大于等于" 链表的头部 (greater_head.next)。 - 重要 :将 "大于等于" 链表的尾部 (
greater_ptr) 的next指针设为None,以防止产生环。 - 返回
less_head.next作为新链表的头节点。
3. 代码实现
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: ListNode, x: int) -> ListNode:
# 创建两个哑节点,分别作为两个新链表的头
less_head = ListNode(-1)
greater_head = ListNode(-1)
# 创建两个指针,用于构建新链表
less_ptr = less_head
greater_ptr = greater_head
current = head
while current:
if current.val < x:
# 将节点加入到 'less' 链表
less_ptr.next = current
less_ptr = less_ptr.next
else:
# 将节点加入到 'greater' 链表
greater_ptr.next = current
greater_ptr = greater_ptr.next
# 移动到下一个节点
current = current.next
# 将 'greater' 链表的末尾指向 None,断开与原链表的连接
greater_ptr.next = None
# 将 'less' 链表的末尾连接到 'greater' 链表的头部
less_ptr.next = greater_head.next
return less_head.next
C++
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
// 创建哑节点
ListNode* less_head = new ListNode(-1);
ListNode* greater_head = new ListNode(-1);
// 创建构建指针
ListNode* less_ptr = less_head;
ListNode* greater_ptr = greater_head;
ListNode* current = head;
while (current != nullptr) {
if (current->val < x) {
less_ptr->next = current;
less_ptr = less_ptr->next;
} else {
greater_ptr->next = current;
greater_ptr = greater_ptr->next;
}
current = current->next;
}
// 断开 'greater' 链表的尾部
greater_ptr->next = nullptr;
// 拼接两个链表
less_ptr->next = greater_head->next;
// 释放哑节点内存(可选,但好习惯)
ListNode* new_head = less_head->next;
delete less_head;
delete greater_head;
return new_head;
}
};
4. 复杂度分析
- 时间复杂度:O(n),其中 n 是链表的节点数。我们只需要遍历一次原链表。
- 空间复杂度 :O(1),我们只使用了常数个额外指针(
less_head,greater_head,less_ptr,greater_ptr),并没有创建新的节点,只是重新组织了原链表的节点。
5. 总结
- 链表划分 问题,特别是要求保持相对顺序时,双链表(或双指针+哑节点) 方法是一个非常经典且有效的解决方案。
- 使用哑节点可以极大地简化代码逻辑,避免对头节点的空指针判断和特殊处理。
- 最后一定要记得处理好两个链表的拼接以及新链表尾部的
next指针(设置为None/nullptr)。
复习
面试经典150题[006]:旋转数组(LeetCode 189)
面试经典150题[036]:旋转图像(LeetCode 48)
面试经典150题[051]:用最少数量的箭引爆气球(LeetCode 452)