【C/C++】分隔链表 (leetcode T86)
核心考点预览:链表 (双指针) 技巧:虚拟头结点
题目描述:
给你一个链表的头节点 head
和一个特定值 x
,请你对链表进行分隔,使得所有 小于 x
的节点都出现在 大于或等于 x
的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
示例 1:
输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
示例 2:
输入:head = [2,1], x = 2
输出:[1,2]
提示:
-
链表中节点的数目在范围
[0, 200]
内 -
-100 <= Node.val <= 100
-
-200 <= x <= 200
详细解答:
方法一:
// 定义链表节点结构
struct ListNode {
int val; // 节点值
struct ListNode *next; // 下一个节点指针
};
// 对链表进行分隔,使得所有小于x的节点在前,大于等于x的节点在后
struct ListNode* partition(struct ListNode* head, int x) {
struct ListNode *p, *q, *res = (struct ListNode*)malloc(sizeof(struct ListNode));
res->next = NULL; // 初始化头节点
p = head; // p指针用于遍历原链表
q = res; // q指针用于构建新链表,开始时指向虚拟头节点
// 第一步:将所有小于x的节点放入新的链表
while (p != NULL) {
if (p->val < x) {
struct ListNode* temp = (struct ListNode*)malloc(sizeof(struct ListNode));
temp->val = p->val; // 复制节点值
temp->next = NULL; // 设定新节点的next为NULL
q->next = temp; // 将新节点链接到当前链表
q = temp; // 更新q为新节点
}
p = p->next; // 移动到下一个节点
}
// 第二步:将所有大于等于x的节点放入新的链表
p = head; // 重新指向原链表头
while (p != NULL) {
if (p->val >= x) {
struct ListNode* temp = (struct ListNode*)malloc(sizeof(struct ListNode));
temp->val = p->val; // 复制节点值
temp->next = NULL; // 设定新节点的next为NULL
q->next = temp; // 将新节点链接到当前链表
q = temp; // 更新q为新节点
}
p = p->next; // 移动到下一个节点
}
// 返回分隔后的链表,跳过虚拟头节点
return res->next;
}
题目解析
思路分析
- 两个链表:使用两个步骤,分别处理小于
x
和大于或等于x
的节点,将它们合并在一个新链表中。- 第一步:遍历原链表,将所有小于
x
的节点复制到新链表中。 - 第二步:重新遍历原链表,将所有大于或等于
x
的节点复制到新链表中。
- 第一步:遍历原链表,将所有小于
- 注意:两个分区中的节点相对顺序保持不变。每次分配新节点时,我们保持链表顺序。
- 链表构建:需要两个指针
p
和q
来分别遍历原链表和构建新链表。
考点分析
- 链表操作:链表的遍历、节点的插入,要求保留相对顺序。
- 空间管理:每次插入一个新节点时,都需要使用
malloc
分配内存。 - 时间复杂度:因为每次遍历整个链表,所以时间复杂度为 O(n),其中
n
是链表的长度。
这道题的难点在于如何分隔链表,并保证每个分区内的相对顺序不变。通过分两次遍历原链表并插入新的节点,可以有效地解决该问题。
易错点:
1.没有使用temp,导致原链表的顺序改变,在第二次遍历的时候走的都是第一轮改过的路径,即都走的是小于x的值
2.没有考虑下面这组测试用例:
head=[] x=0;
如果没有在开始对res->next=NULL;的话,会导致最后返回的时候不知道res->next是什么
方法二:
// 定义链表节点结构
struct ListNode {
int val; // 节点值
struct ListNode *next; // 下一个节点指针
};
// 对链表进行分隔,使得所有小于x的节点在前,大于等于x的节点在后
struct ListNode* partition(struct ListNode* head, int x) {
struct ListNode *cur, *p, *q, *res, *res1 = (struct ListNode*)malloc(sizeof(struct ListNode)), *res2 = (struct ListNode*)malloc(sizeof(struct ListNode));
p = res1; // p指针用于构建小于x的链表
q = res2; // q指针用于构建大于或等于x的链表
cur = head; // cur指针用于遍历原链表
// 遍历链表,将小于x的节点放入res1链表,大于等于x的节点放入res2链表
while (cur != NULL) {
if (cur->val < x) {
p->next = cur; // 将cur节点加入res1链表
p = p->next; // 更新p为当前节点
} else {
q->next = cur; // 将cur节点加入res2链表
q = q->next; // 更新q为当前节点
}
cur = cur->next; // 移动到下一个节点
}
q->next = NULL; // 确保res2链表的尾部没有循环(结束)
// 将res1链表和res2链表连接起来
res = res1; // res指向小于x部分的虚拟头节点
p->next = res2->next; // 将大于等于x的部分连接到小于x的部分后
// 返回合并后的链表,从res->next开始
return res->next;
}
易错点:
在最后添加 q->next=NULL; 以保证在主函数输出的时候能找到结尾
思路分析
- 虚拟头节点:通过
res1
和res2
虚拟头节点来分别存储小于x
和大于等于x
的节点。这样可以简化操作,避免在连接节点时处理特殊情况。 - 链表合并:遍历原链表,将小于
x
的节点放入res1
,大于等于x
的节点放入res2
,最后将两个链表连接起来,保证了相对顺序不变。 - 连接步骤:
- 在遍历完链表后,
p
和q
分别指向各自链表的尾部,将res1
和res2
链表通过p->next
和q->next
连接起来。 - 注意:
q->next = NULL;
这一行代码用于确保res2
链表最后的节点不再指向其他节点,避免形成环。
- 在遍历完链表后,