力扣刷题#16:LeetCode0083-删除排序链表中的重复元素

题目

给定一个已排序的链表的头 head,删除所有重复的元素,使每个元素只出现一次。返回已排序的链表。

输入:head = [1, 1, 2]
输出:[1, 2]

输入:head = [1, 1, 2, 3, 3]
输出:[1, 2, 3]

初始思路

看到"已排序 + 删除重复",我立刻想到了 LeetCode 26(删除有序数组中的重复项)——那是用快慢指针做的。链表版的逻辑应该是一样的:cur 停留在不重复的位置,next 往前探,探到不同值就接上去。

思路是清晰的,但在写链表代码时踩了一串坑。


踩坑记录

坑 1:用 . 访问指针成员

while(next.val != cur.val ...)    // ❌ next 是指针
tmp.next                           // ❌
cur.next                           // ❌

ListNode* 是指针类型,访问成员要用 ->,不是 .

while(next->val != cur->val ...)   // ✅
tmp->next                          // ✅
cur->next                          // ✅

数组值直接 .(比如 cur.val 在结构体变量上),指针走 ->。第一阶段的数组题没有指针,这是切换到链表后遇到的第一个语法问题。

知识点总结:.-> 的区别

这是 C++ 里的一个基础语法,通俗地讲:

写法 用在什么上 原因
变量.成员 普通变量(栈上的对象) 直接访问
指针->成员 指针变量 -> 等价于 (*指针).成员,先解引用再访问

生活化类比:

  • . 就像你直接走进自己家里拿东西——你就在家里
  • -> 就像你手里拿着一张写着地址的纸条,先按纸条找到那栋房子(解引用),再进去拿东西

在链表题里怎么看?

ListNode  node(5);     // node 是普通变量,用 .
node.val  = 5;
node.next = nullptr;

ListNode* p = &node;   // p 是指针变量,用 ->
p->val  = 5;           // 等价于 (*p).val = 5
p->next = nullptr;

力扣给的 ListNode 为什么总是用 -> 因为力扣传给你的链表是 ListNode* head——它是一个指针,不是变量本身。所以所有成员访问都是 head->valhead->next

一句话记住:看类型就能判断——类型带 * 就用 ->,不带就用 .

坑 2:循环条件写反了

while(next->val != cur->val && next != nullptr)

[1, 1, 2] 跑一遍,cur=1next=1

  • next->val(1) != cur->val(1)false
  • 循环直接跳过,什么都没处理,返回了原链表 [1,1,2]

条件写反了。 我要的是值相同时进去处理重复,不是值不同才进去。

坑 3:先取值再判空

next->val ... && next != nullptr     // ❌ next 是 null 的时候先崩了
next != nullptr && next->val ...     // ✅ 短路求值,先判空

任何时候指针取值之前,先把 != nullptr 的判断放在前面——C++ 短路求值会先检查这个条件,失败就不会执行后面的取值,避免访问非法内存。

坑 4:cur 跳早了

cur = next;    // ← 把 cur 移到了下一个节点

cur 本来指着 1₀,应该用 cur->next = 下一个不同的节点 来跳过重复。但我先让 cur 移到了 1₁,再改链接就没意义了——cur 已经不在正确的位置上了。

cur 只有一个任务:停在当前不重复的位置上,只有删完重复之后才往前走一步。

只处理了值相同的情况,值不同的情况什么都没做——cur 和 next 都原地不动,while 循环永远跳不出去。

值不同时,cur 和 next 都要往前走一步。


最终 AC 代码

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if (head == nullptr || head->next == nullptr)
            return head;

        ListNode* cur = head;          // 慢指针:指向保留的最后一个节点
        ListNode* next = head->next;   // 快指针:向前探

        while (next != nullptr) {
            if (next->val == cur->val) {
                // 值相同:跳过所有重复
                ListNode* tmp = cur;
                while (next != nullptr && next->val == tmp->val) {
                    tmp = next;
                    next = tmp->next;
                }
                // next 现在指向第一个不同的节点
                cur->next = next;      // 接上后面的链表
            } else {
                // 值不同:两个指针都往前走
                cur = next;
                next = cur->next;
            }
        }
        return head;
    }
};

代码逻辑

情况 cur 做什么 next 做什么
next->val == cur->val 不动,等 next 找到下一个不同值后 cur->next = next 一直往前走,直到找到不同值
next->val != cur->val 往前走一步 (cur = next) 往前走一步 (next = cur->next)

关键理解:这道题和 LeetCode 26 是同一道题

LeetCode 26(数组) LeetCode 83(链表)
核心逻辑 cur 慢指针 + 快指针扫描 完全一样
保留条件 nums[i] != nums[k-1] next->val != cur->val
跳过重复 快指针自然跳过 while 循环跳过
改链接 nums[k] = nums[i] cur->next = next
收尾 return k return head

数组版用下标操作,链表版用指针操作——本质是同一种快慢指针模式,只是语言不同。


链表 vs 数组判断空值

数组 链表
v.empty() p == nullptr
取值前保证 i < v.size() 取值前保证 p != nullptr

链表没有 empty()size(),所有判断靠 == nullptr!= nullptr


复杂度分析

维度 说明
时间复杂度 O(n) 每个节点访问一次
空间复杂度 O(1) 只用了几个指针

本文档由 AI 辅助生成,作者提供问题,思路和代码,AI仅负责文本修饰,综合获得以上内容。

posted @ 2026-06-16 17:59  STA_running  阅读(4)  评论(0)    收藏  举报