力扣 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));
});
  • 验证头尾节点处理

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

  • 检查空链表处理

相关推荐
ting94520003 小时前
Humalike X Hermes 深度技术剖析:单指令注入群聊社交智能的底层架构、算法与跨 IM 平台实现
人工智能·算法·架构
吴声子夜歌3 小时前
Java面试——算法
java·算法·面试
h_a_o777oah4 小时前
【图论】Tarjan 缩点:解决有向图中环的问题
c++·算法·图论·acm·强连通分量·缩点·tarjan
Tisfy4 小时前
LeetCode 1386.安排电影院座位:哈希表+位运算
算法·leetcode·散列表·题解·哈希表
Nil2084 小时前
leetcode 199二叉树的右视图
算法·leetcode·深度优先
M78佐菲5 小时前
c语言学习笔记:排序与查找方法整理
linux·c语言·笔记·学习·算法
AI备案指南-满满7 小时前
人工智能拟人化互动服务安全自评估报告的评估要点有哪些?
人工智能·算法·安全·机器人·大模型备案·算法备案
LuminousCPP7 小时前
数据结构‑二叉树(二):二叉堆从零实现|Heap结构设计 + 建堆优化 + 堆排序深度解析
c语言·数据结构·笔记·二叉树
土司大王9 小时前
LeetCode hot100——两两交换链表中的节点
算法·leetcode·职场和发展