题目描述
给定一个链表的头节点 head 和一个整数 val,要求删除链表中所有满足 Node.val == val 的节点,并返回新的头节点。
示例
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1
输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]
解题思路
这个问题需要我们在单链表中删除所有特定值的节点。由于是单链表,删除节点时需要特别注意指针的操作,特别是当要删除的节点是头节点时。
关键点分析
-
头节点可能被删除:如果头节点的值等于目标值,我们需要更新头指针
-
连续删除情况:可能存在多个连续节点都需要被删除
-
边界情况:空链表、所有节点都需要删除等情况
算法步骤
-
处理空链表:如果链表为空,直接返回 NULL
-
处理头节点连续等于目标值的情况:使用循环处理头节点可能需要连续删除的情况
-
处理中间节点:使用双指针法遍历链表,删除中间等于目标值的节点
代码实现
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val) {
// 处理空链表情况
if(head == NULL)
{
return NULL;
}
// 处理头节点连续等于目标值的情况
while(head && head->val == val)
{
struct ListNode* temp = head->next;
free(head); // 释放被删除节点的内存
head = temp; // 更新头指针
}
// 使用双指针处理中间节点
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); // 释放被删除节点的内存
continue; // 跳过后续指针更新
}
// 正常情况:移动指针
prev = cur;
cur = cur->next;
}
return head;
}
代码详解
第一部分:处理特殊情况
if(head == NULL)
{
return NULL;
}
- 如果链表为空,直接返回 NULL,避免后续操作出现空指针异常
第二部分:处理头节点
while(head && head->val == val)
{
struct ListNode* temp = head->next;
free(head);
head = temp;
}
-
使用
while循环处理头节点可能需要连续删除的情况 -
比如示例3中
[7,7,7,7],所有节点都需要删除 -
每次删除当前头节点,并将头指针移动到下一个节点
-
循环条件
head && head->val == val确保头节点存在且值等于目标值
第三部分:处理中间节点
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);
continue;
}
prev = cur;
cur = cur->next;
}
双指针法的核心逻辑:
-
cur:当前遍历的节点 -
prev:当前节点的前一个节点
删除节点的步骤:
-
保存要删除的节点:
del = cur -
前驱节点跳过当前节点:
prev->next = cur->next -
当前指针移动到下一个节点:
cur = cur->next -
释放被删除节点的内存:
free(del) -
使用
continue跳过正常的指针更新
正常遍历的步骤:
-
前驱指针移动到当前位置:
prev = cur -
当前指针移动到下一个位置:
cur = cur->next
复杂度分析
-
时间复杂度:O(n),其中 n 是链表的长度,需要遍历整个链表一次
-
空间复杂度:O(1),只使用了常数级别的额外空间
其他解法思路
方法二:虚拟头节点法
struct ListNode* removeElements(struct ListNode* head, int val) {
// 创建虚拟头节点
struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));
dummy->next = head;
struct ListNode* cur = dummy;
while(cur->next) {
if(cur->next->val == val) {
struct ListNode* temp = cur->next;
cur->next = cur->next->next;
free(temp);
} else {
cur = cur->next;
}
}
struct ListNode* newHead = dummy->next;
free(dummy);
return newHead;
}
优点:统一处理头节点和中间节点的删除逻辑,代码更简洁
总结
这道题考察了链表的基本操作,特别是删除操作中指针的处理。关键是要注意:
-
头节点可能被删除,需要特殊处理
-
删除节点时要正确更新前后节点的指针
-
记得释放被删除节点的内存
-
考虑各种边界情况
掌握这种双指针遍历链表的方法对于解决链表相关问题非常有帮助。