题目
给定单链表的头节点 head 和整数 val,删除链表中所有满足 Node.val == val 的节点,并返回新的头节点。
示例 1:
❯Text
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]示例 2:
❯Text
输入:head = [], val = 1
输出:[]示例 3:
❯Text
输入:head = [7,7,7,7], val = 7
输出:[]提示:
- 链表节点数在
[0, 10^4]范围内 1 <= Node.val <= 500 <= val <= 50
考察内容
- 单链表节点的删除与重新连接
- 头节点变化的特殊性
- 使用虚拟头节点统一删除逻辑
- 删除节点后的指针移动
- 动态内存释放
解题思路
直接操作原链表
普通节点有前驱节点,可以通过 previous->next = deleted->next 将它移出链表。但头节点没有前驱,因此需要先单独删除所有值等于 val 的连续头节点,再处理剩余节点。
如果头部节点全部被删除,head 会变成 nullptr,后续遍历必须正确处理这一情况。
虚拟头节点
更推荐在原头节点前创建一个虚拟节点 dummy:
❯Text
dummy → head → ...这样原头节点也变成了一个“有前驱的普通节点”。令 cur 指向 dummy,每轮检查 cur->next:
- 若
cur->next->val == val,删除cur->next,但cur保持不动; - 否则将
cur移动到下一个节点。
删除节点时不能立即移动 cur,因为新接到 cur->next 的节点可能仍然等于 val。例如 [7,7,7] 必须在同一个前驱位置连续删除。
以 [1,2,6,3,4,5,6]、val = 6 为例:
dummy指向原头节点1;cur依次经过1、2;- 发现
cur->next是6,令cur->next跳到3并释放旧节点; - 继续遍历,在尾部用相同逻辑删除第二个
6; - 返回
dummy.next,即新的真实头节点。
- 时间复杂度:,每个节点最多检查一次
- 空间复杂度:
代码演示
❯C++
struct ListNode {
int val;
ListNode* next;
ListNode() : val(0), next(nullptr) {}
explicit ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode* next) : val(x), next(next) {}
};
// 推荐写法:虚拟头节点统一处理所有位置
ListNode* removeElements(ListNode* head, int val) {
ListNode dummy(0, head);
ListNode* cur = &dummy;
while (cur->next != nullptr) {
if (cur->next->val == val) {
ListNode* deleted = cur->next;
cur->next = deleted->next;
delete deleted;
} else {
cur = cur->next;
}
}
return dummy.next;
}
// 不使用虚拟头节点:需要单独处理链表头部
ListNode* removeElementsWithoutDummy(ListNode* head, int val) {
while (head != nullptr && head->val == val) {
ListNode* deleted = head;
head = head->next;
delete deleted;
}
ListNode* cur = head;
while (cur != nullptr && cur->next != nullptr) {
if (cur->next->val == val) {
ListNode* deleted = cur->next;
cur->next = deleted->next;
delete deleted;
} else {
cur = cur->next;
}
}
return head;
}易错点
- 忽略头节点可能被删除:删除原头节点后,函数需要返回新的
head。 - 删除后仍移动
cur:新接入的下一个节点可能也需要删除,应保持cur不动并继续检查。 - 先释放节点再读取
next:delete后不能再访问该节点;应先完成链表连接,再释放节点。 - 删除所有头节点后解引用空指针:直接写法的遍历条件必须包含
cur != nullptr。 - 返回虚拟节点地址:
dummy是局部变量,应该返回dummy.next,不能返回&dummy。 - 忘记释放被移除的节点:在手动管理内存的 C++ 环境中,应对断开的节点执行
delete。
总结
虚拟头节点为原链表增加了一个稳定的前驱,使头节点与中间节点可以使用完全相同的删除逻辑。链表题中只要头节点可能变化,优先考虑 dummy 通常能显著减少分支和边界错误。
相关题目
-
- 删除链表的倒数第 N 个结点
-
- 合并两个有序链表
-
- 删除排序链表中的重复元素
-
- 反转链表
-
- 设计链表