力扣 83 . 删除排序链表中的重复元素:深入解析与实现

目录

一、链表扫盲专区

[1.1 什么是链表?](#1.1 什么是链表?)

[1.2 单向链表特性](#1.2 单向链表特性)

[1.3 链表 vs 数组](#1.3 链表 vs 数组)

二、问题分析

[2.1 题目要求](#2.1 题目要求)

[2.2 关键点分析](#2.2 关键点分析)

三、算法思路剖析

[3.1 双指针法](#3.1 双指针法)

[3.2 流程演示](#3.2 流程演示)

四、代码深度解析

[4.1 关键点解读](#4.1 关键点解读)

[4.2 复杂度分析](#4.2 复杂度分析)

五、边界条件处理

[5.1 特殊输入处理](#5.1 特殊输入处理)

[5.2 常见错误](#5.2 常见错误)

六、实战测试


一、链表扫盲专区

1.1 什么是链表?

链表是由一系列节点组成的线性数据结构,每个节点包含:

  • 数据域:存储当前节点的值

  • 指针域:指向下一个节点的内存地址

1.2 单向链表特性

  • 非连续存储:节点分散在内存各处

  • 单向访问:只能从头节点开始顺序访问

  • 时间复杂度

    • 查找:O(n)

    • 插入/删除:O(1)(已知前驱节点时)

1.3 链表 vs 数组

特性 数组 链表
内存连续性 连续 非连续
随机访问 O(1) O(n)
插入/删除效率 O(n) O(1)
空间利用率 固定 动态分配

二、问题分析

2.1 题目要求

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

示例:

输入:1 -> 1 -> 2

输出:1 -> 2

2.2 关键点分析

  • 已排序:重复元素必定相邻

  • 保留一个:发现重复时保留首个元素

  • 原地修改:需直接修改链表结构


三、算法思路剖析

3.1 双指针法

使用当前指针进行遍历:

  1. 初始化指针cur指向头节点

  2. 循环检查cur与cur.next:

    • 值相同 → 删除后一个节点

    • 值不同 → 指针前移

3.2 流程演示

  • cur.valcur.next.val 相等时说明需要去重,则将 cur 的下一个指针指向下一个的下一个,这样就能达到去重复的效果
  • 如果不相等则 cur 移动到下一个位置继续循环
  • curcur.next 的存在为循环结束条件,当二者有一个不存在时说明链表没有去重复的必要了

以链表 1->1->2->3->3 为例:

复制代码
初始状态:①→①→②→③→③
第1步:发现重复,跳过第二个1 → ①→②→③→③
第2步:1≠2,指针前移
第3步:2≠3,指针前移
第4步:发现重复,跳过第二个3 → ①→②→③

四、代码深度解析

复制代码
var deleteDuplicates = function(head) {
    let cur = head;
    while(cur && cur.next) {
        if(cur.val === cur.next.val) {
            cur.next = cur.next.next; // 跳过重复节点
        } else {
            cur = cur.next; // 指针前移
        }
    }
    return head;
};

4.1 关键点解读

  1. 循环条件cur && cur.next确保能安全访问两个节点

  2. 删除操作:直接修改next指针实现节点跳过

  3. 指针移动:仅当无重复时才移动指针

4.2 复杂度分析

  • 时间复杂度:O(n) 只需一次遍历

  • 空间复杂度:O(1) 仅使用常量空间


五、边界条件处理

5.1 特殊输入处理

输入情况 处理方式
空链表 直接返回null
单节点链表 直接返回原链表
全重复链表 保留头节点

5.2 常见错误

  1. 空指针异常:未检查cur.next是否存在

  2. 尾部处理:最后一个节点的next应为null

  3. 指针移动错误:在删除节点后错误移动指针


六、实战测试

复制代码
// 定义链表节点
class ListNode {
  constructor(val, next) {
    this.val = val === undefined ? 0 : val;
    this.next = next === undefined ? null : next;
  }
}

// 链表去重函数
const deleteDuplicates = function (head) {
  let cur = head;
  while (cur && cur.next) {
    if (cur.val === cur.next.val) {
      cur.next = cur.next.next;
    } else {
      cur = cur.next;
    }
  }
  return head;
};

// 数组转链表
function createList(arr) {
  if (!arr || arr.length === 0) return null;
  const dummy = new ListNode(-1);
  let cur = dummy;
  for (const num of arr) {
    cur.next = new ListNode(num);
    cur = cur.next;
  }
  return dummy.next;
}

// 链表转数组(用于验证结果)
function listToArray(head) {
  const res = [];
  while (head) {
    res.push(head.val);
    head = head.next;
  }
  return res;
}

// 测试用例
const testCases = [[1, 1, 2], [1, 1, 1], [], [1, 2, 3, 3, 4]];

testCases.forEach((tc) => {
  const input = createList(tc);
  const result = deleteDuplicates(input);
  console.log(`输入:${tc}\t输出:`, listToArray(result));
});
  • 验证头尾节点处理

  • 测试连续多个重复的情况

  • 检查空链表处理

相关推荐
adam_life15 分钟前
http://noi.openjudge.cn/——2.5基本算法之搜索——200:Solitaire
算法·宽搜·布局唯一码
我想进大厂1 小时前
图论---朴素Prim(稠密图)
数据结构·c++·算法·图论
我想进大厂1 小时前
图论---Bellman-Ford算法
数据结构·c++·算法·图论
AIGC大时代1 小时前
高效使用DeepSeek对“情境+ 对象 +问题“型课题进行开题!
数据库·人工智能·算法·aigc·智能写作·deepseek
lkbhua莱克瓦241 小时前
用C语言实现——一个中缀表达式的计算器。支持用户输入和动画演示过程。
c语言·开发语言·数据结构·链表·学习方法·交友·计算器
CODE_RabbitV2 小时前
【深度强化学习 DRL 快速实践】近端策略优化 (PPO)
算法
Wendy_robot2 小时前
【滑动窗口+哈希表/数组记录】Leetcode 438. 找到字符串中所有字母异位词
c++·算法·leetcode
程序员-King.3 小时前
day49—双指针+贪心—验证回文串(LeetCode-680)
算法·leetcode·贪心算法·双指针
转基因3 小时前
Codeforces Round 1020 (Div. 3)(题解ABCDEF)
数据结构·c++·算法
Forworder4 小时前
[数据结构]树和二叉树
java·数据结构·intellij-idea·idea