用std::unordered_set辅助单链表去重是标准解法,时间复杂度O(n),需边遍历边记录val值,遇重复则跳过并安全释放节点;不可用std::unique,因其不适用于手写单链表。

用 std::unordered_set 辅助去重,时间复杂度 O(n)
单链表本身不支持随机访问,无法像数组那样用双指针原地去重。最直接的做法是边遍历边记录已出现的值,遇到重复节点就跳过。C++ 标准库中 std::unordered_set 提供平均 O(1) 的插入和查找,适合做这个“记忆本”。
注意:必须用值去重(比如 val 字段),而不是节点地址——否则每个节点地址都不同,永远删不掉。
- 遍历前声明
std::unordered_set<int></int>(假设节点值为int) - 对头节点先检查:若空或仅一个节点,直接返回
- 用两个指针:
prev指向前驱,curr指向当前;若curr->val已在 set 中,执行prev->next = curr->next并delete curr,然后只更新curr(prev不动) - 否则把
curr->val插入 set,再同时移动prev和curr
不借助额外空间?只能用 O(n²) 双重遍历
如果题目明确禁止哈希表或额外容器,就得接受性能代价:对每个节点,从它开始向后扫描,删除所有值相同的后续节点。这本质是暴力匹配,没有捷径。
- 外层循环用
curr遍历主链,内层用runner从curr->next开始查重 - 每次找到重复时,执行
prev_runner->next = runner->next,然后delete runner,但注意runner指针失效,要重置为prev_runner->next - 不要在内层循环里盲目递增
runner,否则会跳过刚接上的下一个节点 - 该方法稳定但慢,链表长于几千节点时明显卡顿
std::unique 不能直接用,别被名字误导
std::unique 是针对连续内存(如 std::vector 或数组)设计的算法,要求元素可随机访问且能通过迭代器 + 整数偏移定位。单链表的 std::list 虽然有 unique() 成员函数,但那是为双向链表优化的;而手写单链表(通常指 struct ListNode { int val; ListNode* next; };)没有该接口,强行套用会导致编译错误或未定义行为。
立即学习“C++免费学习笔记(深入)”;
- 错误写法:
std::unique(head, head->next, ...)—— 迭代器类型不匹配,ListNode*不是标准迭代器 - 正确思路:要么自己实现逻辑,要么转成
std::vector再用std::unique,但这就失去链表原地操作的意义 - 如果真用
std::list,记得它是双向链表,调用的是lst.unique(),且默认只删相邻重复项;要去除非相邻重复,得先排序
内存释放不彻底?野指针和漏删是高频坑
删节点不只是改指针,更要确保 delete 后不再访问已释放内存。很多初学者删完没置空指针,后续又拿 curr 去读 curr->val,触发崩溃。
- 每次
delete curr后,立刻让curr = nullptr(尤其在 while 循环中) - 避免写
curr = curr->next; delete curr;—— 这是典型“先移后删”,curr已经指向新地址,删的是下一个节点 - 更安全的顺序是:
ListNode* to_delete = curr; curr = curr->next; delete to_delete; - 如果链表节点由
malloc分配,必须用free,混用new/delete和malloc/free会出问题
实际写的时候,90% 场景推荐第一种哈希法。真正容易被忽略的不是算法选型,而是删节点后指针状态管理——哪怕逻辑全对,一个没置空的 curr 就能让程序在某个边界 case 上段错误。


















