目录
题目来源:力扣(LeetCode)
给你一个链表的头节点
head和一个整数val,请你删除链表中所有满足Node.val == val的节点,并返回 新的头节点 。
一、完整代码
struct ListNode* removeElements(struct ListNode* head, int val)
{
// 删除头节点为val的情况,直到头节点不是val
while (head != NULL && head->val == val)
{
struct ListNode* del = head;
head = head->next;
free(del);
}
// 如果链表为空,直接返回
if (head == NULL)
{
return NULL;
}
struct ListNode* cur = head;
struct ListNode* prev = NULL;
while (cur)
{
if (cur->val == val)
{
struct ListNode* del = cur;
prev->next = cur->next;
cur = cur->next;
free(del);
}
else
{
prev = cur;
cur = cur->next;
}
}
return head;
}
二、解题思路
-
预处理头节点:先处理所有连续的要删除头节点
-
双指针遍历 :
prev:指向当前节点的前一个节点(初始为NULL);
cur:指向当前正在检查的节点。 -
删除逻辑 :
当cur->val == val时,将prev->next指向cur->next,定义临时指针del指向cur,然后删除del;
否则,将prev移动到cur,cur移动到下一个节点。
三、图解
让我们通过一个具体例子来详细理解算法的执行过程。假设我们有以下链表:
要删除所有值为
2的节点。
1.初始化
cur指向头节点1
prev初始为NULL
2.循环启动
(1)第一个循环
由于
cur->val = 1不等于2,执行else分支:
prev = cur(prev指向节点1)
cur = cur->next(cur指向节点2)
(2)删除第一个值为2的节点
cur->val = 2等于要删除的值,执行删除操作:
struct ListNode* del = cur(del指向要删除的节点2)prev->next = cur->next(节点1的next指向节点3)cur = cur->next(cur指向节点3)free(del)(释放节点2的内存)
(3)继续遍历,直到遇到val = 2 的节点
.....................
(4)删除第二个值为2的节点
cur->val = 2等于要删除的值,执行删除操作:
struct ListNode* del = cur(del指向要删除的节点2)
prev->next = cur->next(节点4的next指向下一个节点2)
cur = cur->next(cur指向下一个节点2)
free(del)(释放节点2的内存)
(5)删除第三个值为2的节点
cur->val = 2等于要删除的值,执行删除操作:
struct ListNode* del = cur(del指向要删除的节点2)
prev->next = cur->next(节点4的next指向节点5)
cur = cur->next(cur指向节点5)
free(del)(释放节点2的内存)
(6)继续遍历
cur->val = 5不等于2,执行else分支:
prev = cur(prev指向节点5)
cur = cur->next(cur指向NULL)
3.结束循环
当
cur = NULL时,while循环条件cur为假,循环结束。最终链表为:
返回
head,即节点1
四、常见陷阱
1.空指针解引用陷阱
// 错误示例
if (cur->val == val)
{
prev->next = cur->next; // 当prev为NULL时会崩溃
free(cur);
}
2.内存泄漏陷阱
// 错误示例
if (cur->val == val)
{
prev->next = cur->next;
cur = cur->next;
// 忘记释放del节点的内存!
}
3.指针丢失陷阱
// 错误示例
if (cur->val == val)
{
free(cur); // 先释放内存
prev->next = cur->next; // 访问已释放内存!
cur = prev->next;
}
4.头节点处理陷阱
// 错误示例
struct ListNode* cur = head;
struct ListNode* prev = NULL;
while (cur)
{
if (cur->val == val)
{
if (prev == NULL)
{
head = cur->next; // 头节点删除
}
else
{
prev->next = cur->next;
}
free(cur);
// 问题:删除头节点后,cur指针状态可能不对
}
}
五、总结
双指针法是删除链表节点的核心技巧,使用prev和cur指针遍历链表。需要单独处理可能被连续删除的头节点。删除节点时须严格按顺序操作:先保存待删节点,再移动cur指针,接着更新prev->next,最后释放内存。特别注意,连续删除时prev保持不动,只在保留节点时才向后移动。复杂度:时间O(n),空间O(1)。








