AC201-删除递增链表中重复的结点,仅保留第一个¶
代码实现¶
代码逻辑解析¶
- 初始化指针:
p指针从链表的第一个有效结点(头结点的下一个结点)开始遍历。-
这样设计是因为头结点通常不存储有效数据,仅作为链表操作的辅助结点。
-
循环条件:
- 当
p->next != NULL时继续循环,即当前结点的下一个结点存在。 -
这样可以确保在访问
q = p->next时不会出现空指针异常。 -
重复检测:
q指针指向p的下一个结点。-
比较
p->data和q->data的值:- 如果相等,说明找到了重复结点,需要删除
q结点。 - 如果不相等,说明没有重复,移动
p指针继续遍历。
- 如果相等,说明找到了重复结点,需要删除
-
删除操作:
- 当发现重复结点时,将
p->next指向q->next,跳过q结点。 -
使用
free(q)释放被删除结点的内存空间,避免内存泄漏。 -
指针移动:
- 只有在没有删除结点时才移动
p指针,这样可以连续处理多个重复结点。
时间复杂度分析¶
- 遍历次数:
- 每个结点最多被访问一次,总共访问
n-1个结点(不包括头结点)。 -
每次循环都执行常数时间的操作(比较、指针操作、可能的删除操作)。
-
删除操作:
- 每个重复结点只被删除一次,删除操作的时间复杂度为 O(1)。
综上,算法的时间复杂度为 O(n),其中 n 是链表中结点的总数。
空间复杂度分析¶
- 该算法只使用了常量级别的额外空间(如指针变量
p和q)。 - 没有使用与链表长度相关的额外存储空间。
- 删除操作释放了重复结点的内存,不会增加空间使用量。
因此,空间复杂度为 O(1)。
较难理解部分的说明¶
为什么删除结点后不移动 p 指针?¶
这是算法的关键设计:
- 当删除
q结点后,p->next直接指向了q的下一个结点。 - 如果立即移动
p指针,可能会跳过对新p->next结点的检查。 - 不移动
p指针可以确保连续的重复结点都能被正确检测和删除。
图解说明¶
假设链表如下(递增序列):
| Text Only | |
|---|---|
执行过程: 1. 第一次循环:p指向第一个2,q指向第二个2 - 发现重复,删除q结点 - 链表变为:1 -> 2 -> 3 -> 3 -> 3 -> 4 -> NULL - p仍指向第一个2
- 第二次循环:p指向第一个2,q指向3
-
不重复,p移动到3
-
第三次循环:p指向第一个3,q指向第二个3
- 发现重复,删除q结点
- 继续处理直到所有重复都被删除
最终结果:
| Text Only | |
|---|---|
延伸知识点¶
1. 链表操作的安全性¶
| C | |
|---|---|
2. 相关链表操作算法¶
- 删除所有重复结点(包括第一个):需要使用前驱指针
- 删除指定值的所有结点:类似思路,但比较条件不同
- 链表去重的其他策略:使用哈希表记录已出现元素(时间O(n),空间O(n))
3. 递归实现思路¶
| C | |
|---|---|
4. 扩展应用场景¶
- 有序数组去重:类似双指针法
- 多个有序链表合并去重:需要更复杂的比较逻辑
- 带重复计数的去重:记录每个元素的出现次数