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

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

  • 检查空链表处理

相关推荐
冲帕Chompa1 小时前
图论part10 bellman_ford算法
数据结构·算法·图论
緈福的街口1 小时前
【leetcode】144. 二叉树的前序遍历
算法·leetcode
GG不是gg1 小时前
排序算法之基础排序:冒泡,选择,插入排序详解
数据结构·算法·青少年编程·排序算法
随意起个昵称2 小时前
【双指针】供暖器
算法
倒霉蛋小马2 小时前
最小二乘法拟合直线,用线性回归法、梯度下降法实现
算法·最小二乘法·直线
codists2 小时前
《算法导论(第4版)》阅读笔记:p82-p82
算法
埃菲尔铁塔_CV算法2 小时前
深度学习驱动下的目标检测技术:原理、算法与应用创新
深度学习·算法·目标检测
float_com3 小时前
【背包dp-----分组背包】------(标准的分组背包【可以不装满的 最大价值】)
算法·动态规划
丶Darling.3 小时前
Day119 | 灵神 | 二叉树 | 二叉树的最近共公共祖先
数据结构·c++·算法·二叉树
L_cl4 小时前
【Python 算法零基础 3.递推】
算法