1 题目
给你一个整数数组 nums 和一个链表的头节点 head。从链表中移除 所有存在于 nums 中的节点后,返回修改后的链表的头节点。
示例 1:
输入: nums = [1,2,3], head = [1,2,3,4,5]
输出: [4,5]
解释:

移除数值为 1, 2 和 3 的节点。
示例 2:
输入: nums = [1], head = [1,2,1,2,1,2]
输出: [2,2,2]
解释:

移除数值为 1 的节点。
示例 3:
输入: nums = [5], head = [1,2,3,4]
输出: [1,2,3,4]
解释:

链表中不存在值为 5 的节点。
提示:
1 <= nums.length <= 1051 <= nums[i] <= 105nums中的所有元素都是唯一的。- 链表中的节点数在
[1, 105]的范围内。 1 <= Node.val <= 105- 输入保证链表中至少有一个值没有在
nums中出现过。
2 代码实现
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* modifiedList(vector<int>& nums, ListNode* head) {
unordered_set <int> numbers(nums.begin(),nums.end());
if (head == nullptr){
return head ;
}
ListNode* dummy = new ListNode(0);
dummy -> next = head ;
ListNode* cur = dummy;
while(cur -> next != nullptr){
if (numbers.count(cur -> next -> val)){
cur -> next = cur -> next -> next;
}else{
cur = cur -> next;
}
}
head = dummy -> next ;
delete dummy;
return head ;
}
};
思路
这个题目和之前的,上一篇博客的里的Leetcode 97 移除链表元素-CSDN博客 区别在于现在所要检查的val不是单个输入的值,而是数组。
我想到统计出现次数常用的做法就是用set 或者 map ,这里也许把数组的元素存进set里面,然后count调用函数看看当前遍历元素的val是不是数组的,如果是,删除。
删除的逻辑我采用迭代的做法。
自己一开始提交的代码
有点错误
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* modifiedList(vector<int>& nums, ListNode* head) {
unordered_set <int> numbers;
for(int i = 0 ; i < nums.size() ; i++){
numbers.insert(nums[i]);
}
if (head == nullptr){
return head ;
}
ListNode* dummy = new ListNode(0);
dummy -> next = head ;
ListNode* cur = dummy;
while(cur -> next != nullptr){
if (numbers.count(cur -> next -> val)){
ListNode* temp = cur -> next ;
cur -> next = cur -> next -> next ;
delete temp ;
}
cur = cur -> next;
}
head = dummy -> next ;
delete dummy;
return head ;
}
};
错误分析
核心问题出在 while 循环内的指针移动逻辑:
cpp
while(cur -> next != nullptr){
if (numbers.count(cur -> next -> val)){
// 删除节点的逻辑是对的
ListNode* temp = cur -> next ;
cur -> next = cur -> next -> next ;
delete temp ;
}
// 错误点:无论是否删除节点,都移动 cur 指针
cur = cur -> next;
}
- 问题本质 :当删除了
cur->next节点后,cur本身不应该移动(因为新的cur->next还没检查),但你的代码无条件执行cur = cur->next,会跳过部分节点的检查,甚至可能导致空指针访问。 - 举例 :如果连续两个节点都需要删除,第一个节点被删除后,
cur直接移动到下一个节点,第二个需要删除的节点会被漏掉;极端情况下,删除最后一个节点后cur->next为nullptr,再执行cur = cur->next会导致后续访问cur->next时报错。
另外,在leetcode平台上我又出现了执行出错。
错误根源分析
- 内存释放的权责问题 :
- 你在代码中用
delete temp释放了被删除的节点,但 OJ 测试框架本身会负责管理链表的内存(比如测试结束后自动释放整个链表)。 - 你提前释放了节点,测试框架后续访问这些已释放的节点时,就会触发
heap-use-after-free错误。
- 你在代码中用
- 本质 :OJ 环境中,不需要手动释放链表节点的内存 (这是测试框架的职责),你的
delete操作反而破坏了内存管理的一致性。
通过的代码
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* modifiedList(vector<int>& nums, ListNode* head) {
unordered_set <int> numbers;
for(int i = 0 ; i < nums.size() ; i++){
numbers.insert(nums[i]);
}
if (head == nullptr){
return head ;
}
ListNode* dummy = new ListNode(0);
dummy -> next = head ;
ListNode* cur = dummy;
while(cur -> next != nullptr){
if (numbers.count(cur -> next -> val)){
cur -> next = cur -> next -> next;
}else{
cur = cur -> next;
}
}
head = dummy -> next ;
delete dummy;
return head ;
}
};
其实一开始存入set的代码可以更简洁一点。
直接用一行
cpp
unordered_set<int> numbers(nums.begin(), nums.end());
3 小结
这题除了处理val数组没有别的难点,我也想了一下递归,实质就是先写个private函数,传参是val,head,就又回到了上一题,完完全全的写法,只是数据判定不同。但我觉得再依靠一个函数有点冗余了。
这道题让我更深理解了迭代中的条件判断,循环控制。
比如我犯了的错,只有当不需要删除操作的时候,cur指针才往下走,否则逻辑很混乱,下一个指针都要被删掉了怎么走一个next呢?
整体很简单。
-------
核心总结
- 解题核心思路 :将数组存入
unordered_set实现 O (1) 时间复杂度的查找,结合虚拟头节点处理链表删除,迭代遍历链表时仅在不删除节点时移动指针,保证每个节点被检查。 - 关键错误与修复 :
- 指针逻辑错误:原代码无条件移动
cur指针,导致漏检节点 / 空指针,修复后仅在else分支(不删除节点)移动指针; - 内存管理错误:手动
delete链表节点触发heap-use-after-free,修复后仅做逻辑删除(跳过节点),仅释放自建的dummy节点。
- 指针逻辑错误:原代码无条件移动
- 优化与补充 :
- 代码简化:
unordered_set可直接用nums迭代器初始化,省略循环插入; - 解法对比:迭代写法高效(O (1) 额外空间),递归写法更易体现分治思想(需辅助函数,O (n) 递归栈空间)。
- 代码简化: