当前位置: 首页 > news >正文

【LeetCode】82. 删除排序链表中的重复元素 II

文章目录

  • 82. 删除排序链表中的重复元素 II
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 提示:
    • 解题思路
      • 问题深度分析
        • 问题本质
        • 核心思想
        • 关键难点分析
        • 典型情况分析
        • 算法对比
      • 算法流程图
        • 主算法流程(双指针)
        • 重复元素处理流程
      • 复杂度分析
        • 时间复杂度详解
        • 空间复杂度详解
      • 关键优化技巧
        • 技巧1:双指针算法(最优解法)
        • 技巧2:递归算法
        • 技巧3:哈希表
        • 技巧4:优化版双指针
      • 边界情况处理
      • 测试用例设计
        • 基础测试
        • 简单情况
        • 特殊情况
        • 边界情况
      • 常见错误与陷阱
        • 错误1:指针更新错误
        • 错误2:边界条件错误
        • 错误3:dummy节点使用错误
      • 实战技巧总结
      • 进阶扩展
        • 扩展1:返回删除的节点
        • 扩展2:统计重复次数
        • 扩展3:支持自定义重复条件
      • 应用场景
    • 代码实现
    • 测试结果
    • 核心收获
    • 应用拓展
    • 完整题解代码

82. 删除排序链表中的重复元素 II

题目描述

给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。

示例 1:

在这里插入图片描述

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

示例 2:

在这里插入图片描述

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

提示:

  • 链表中节点数目在范围 [0, 300] 内
  • -100 <= Node.val <= 100
  • 题目数据保证链表已经按升序排列

解题思路

问题深度分析

这是经典的链表操作问题,也是双指针算法的典型应用。核心在于处理重复元素,在O(n)时间内删除所有重复节点。

问题本质

给定已排序的链表,删除所有重复数字的节点,只保留不重复的节点。这是一个链表遍历问题,需要处理重复元素的删除。

核心思想

双指针 + 重复元素检测

  1. 双指针:使用快慢指针遍历链表
  2. 重复检测:检测连续重复的元素
  3. 节点删除:删除所有重复的节点
  4. 链表重构:重新连接链表

关键技巧

  • 使用dummy节点简化边界处理
  • 使用prev指针记录前一个不重复节点
  • 使用curr指针遍历链表
  • 当发现重复时,跳过所有重复节点
关键难点分析

难点1:重复元素的检测

  • 需要准确检测连续重复的元素
  • 需要区分单个元素和重复元素
  • 需要处理多个连续重复的情况

难点2:节点的删除

  • 需要删除所有重复的节点
  • 需要正确更新指针关系
  • 需要处理边界情况

难点3:链表的重构

  • 需要重新连接链表
  • 需要处理头节点的变化
  • 需要保持链表的完整性
典型情况分析

情况1:一般情况

head = [1,2,3,3,4,4,5]
过程:
1. 1 → 保留
2. 2 → 保留
3. 3,3 → 删除
4. 4,4 → 删除
5. 5 → 保留
结果: [1,2,5]

情况2:头部重复

head = [1,1,1,2,3]
过程:
1. 1,1,1 → 删除
2. 2 → 保留
3. 3 → 保留
结果: [2,3]

情况3:全部重复

head = [1,1,1,1,1]
过程:
1. 1,1,1,1,1 → 全部删除
结果: []

情况4:无重复

head = [1,2,3,4,5]
结果: [1,2,3,4,5]
算法对比
算法时间复杂度空间复杂度特点
双指针O(n)O(1)最优解法
递归O(n)O(n)空间复杂度高
哈希表O(n)O(n)空间复杂度高
暴力法O(n²)O(1)效率较低

注:n为链表长度

算法流程图

主算法流程(双指针)
开始: head
创建dummy节点
初始化: prev=dummy, curr=head
curr != nil?
返回dummy.Next
检查curr是否重复
curr重复?
跳过所有重复节点
保留curr节点
更新curr指针
更新prev指针
curr = curr.Next
重复元素处理流程
graph TDA[检测重复元素] --> B{curr.Next != nil && curr.Val == curr.Next.Val?}B -->|是| C[记录重复值]B -->|否| D[无重复,保留节点]C --> E[跳过所有重复节点]E --> F[更新curr指针]D --> G[更新prev指针]F --> H[继续遍历]G --> H

复杂度分析

时间复杂度详解

双指针算法:O(n)

  • 每个节点最多被访问一次
  • 重复节点被一次性跳过
  • 总时间:O(n)

递归算法:O(n)

  • 递归深度为链表长度
  • 时间复杂度相同
  • 空间复杂度较高
空间复杂度详解

双指针算法:O(1)

  • 只使用常数额外空间
  • 原地修改链表
  • 总空间:O(1)

关键优化技巧

技巧1:双指针算法(最优解法)
func deleteDuplicates(head *ListNode) *ListNode {if head == nil {return nil}// 创建dummy节点简化边界处理dummy := &ListNode{Next: head}prev := dummycurr := headfor curr != nil {// 检查curr是否重复if curr.Next != nil && curr.Val == curr.Next.Val {// 记录重复值val := curr.Val// 跳过所有重复节点for curr != nil && curr.Val == val {curr = curr.Next}// 删除重复节点prev.Next = curr} else {// 保留curr节点prev = currcurr = curr.Next}}return dummy.Next
}

优势

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
  • 逻辑清晰,易于理解
技巧2:递归算法
func deleteDuplicates(head *ListNode) *ListNode {if head == nil || head.Next == nil {return head}if head.Val == head.Next.Val {// 跳过所有重复节点val := head.Valfor head != nil && head.Val == val {head = head.Next}return deleteDuplicates(head)} else {// 保留当前节点head.Next = deleteDuplicates(head.Next)return head}
}

特点:使用递归,代码简洁但空间复杂度高

技巧3:哈希表
func deleteDuplicates(head *ListNode) *ListNode {if head == nil {return nil}// 统计每个值的出现次数count := make(map[int]int)curr := headfor curr != nil {count[curr.Val]++curr = curr.Next}// 创建新链表,只保留出现一次的值dummy := &ListNode{}prev := dummycurr = headfor curr != nil {if count[curr.Val] == 1 {prev.Next = currprev = prev.Next}curr = curr.Next}prev.Next = nilreturn dummy.Next
}

特点:使用哈希表统计,空间复杂度高

技巧4:优化版双指针
func deleteDuplicates(head *ListNode) *ListNode {if head == nil {return nil}dummy := &ListNode{Next: head}prev := dummyfor prev.Next != nil {curr := prev.Next// 检查是否有重复if curr.Next != nil && curr.Val == curr.Next.Val {val := curr.Val// 跳过所有重复节点for curr != nil && curr.Val == val {curr = curr.Next}prev.Next = curr} else {prev = prev.Next}}return dummy.Next
}

特点:优化指针更新逻辑

边界情况处理

  1. 空链表:返回nil
  2. 单节点:直接返回
  3. 全部重复:返回空链表
  4. 头部重复:需要更新头节点
  5. 尾部重复:正确处理尾部

测试用例设计

基础测试
输入: head = [1,2,3,3,4,4,5]
输出: [1,2,5]
说明: 一般情况
简单情况
输入: head = [1]
输出: [1]
说明: 单节点情况
特殊情况
输入: head = [1,1,1,2,3]
输出: [2,3]
说明: 头部重复
边界情况
输入: head = []
输出: []
说明: 空链表情况

常见错误与陷阱

错误1:指针更新错误
// ❌ 错误:指针更新时机错误
if curr.Val == curr.Next.Val {// 删除重复节点prev.Next = curr.Nextcurr = curr.Next // 错误:应该跳过所有重复节点
}// ✅ 正确:跳过所有重复节点
if curr.Val == curr.Next.Val {val := curr.Valfor curr != nil && curr.Val == val {curr = curr.Next}prev.Next = curr
}
错误2:边界条件错误
// ❌ 错误:没有检查边界条件
for curr != nil {if curr.Val == curr.Next.Val { // 可能越界// ...}
}// ✅ 正确:先检查边界条件
for curr != nil {if curr.Next != nil && curr.Val == curr.Next.Val {// ...}
}
错误3:dummy节点使用错误
// ❌ 错误:没有使用dummy节点
func deleteDuplicates(head *ListNode) *ListNode {// 直接处理head,可能丢失头节点
}// ✅ 正确:使用dummy节点简化处理
func deleteDuplicates(head *ListNode) *ListNode {dummy := &ListNode{Next: head}// 使用dummy节点处理
}

实战技巧总结

  1. 双指针模板:prev和curr指针配合
  2. dummy节点:简化边界处理
  3. 重复检测:准确检测连续重复元素
  4. 节点删除:正确删除重复节点
  5. 指针更新:正确更新指针关系

进阶扩展

扩展1:返回删除的节点
func deleteDuplicatesWithDeleted(head *ListNode) (*ListNode, []*ListNode) {// 返回新链表和删除的节点// ...
}
扩展2:统计重复次数
func deleteDuplicatesWithCount(head *ListNode) (*ListNode, map[int]int) {// 返回新链表和每个值的重复次数// ...
}
扩展3:支持自定义重复条件
func deleteDuplicatesCustom(head *ListNode, isDuplicate func(int, int) bool) *ListNode {// 支持自定义重复判断条件// ...
}

应用场景

  1. 数据处理:清理重复数据
  2. 链表优化:减少存储空间
  3. 算法竞赛:链表操作基础
  4. 系统设计:数据去重
  5. 数据分析:数据清洗

代码实现

本题提供了四种不同的解法,重点掌握双指针算法。

测试结果

测试用例双指针递归哈希表优化版
基础测试
简单情况
特殊情况
边界情况

核心收获

  1. 双指针算法:链表操作的经典应用
  2. dummy节点:简化边界处理
  3. 重复检测:准确检测连续重复元素
  4. 节点删除:正确删除重复节点
  5. 指针更新:正确的指针更新时机

应用拓展

  • 链表数据处理和清洗
  • 算法竞赛基础
  • 系统设计应用
  • 数据分析技术
  • 内存优化技术

完整题解代码

package mainimport ("fmt"
)// ListNode 链表节点定义
type ListNode struct {Val  intNext *ListNode
}// =========================== 方法一:双指针算法(最优解法) ===========================func deleteDuplicates(head *ListNode) *ListNode {if head == nil {return nil}// 创建dummy节点简化边界处理dummy := &ListNode{Next: head}prev := dummycurr := headfor curr != nil {// 检查curr是否重复if curr.Next != nil && curr.Val == curr.Next.Val {// 记录重复值val := curr.Val// 跳过所有重复节点for curr != nil && curr.Val == val {curr = curr.Next}// 删除重复节点prev.Next = curr} else {// 保留curr节点prev = currcurr = curr.Next}}return dummy.Next
}// =========================== 方法二:递归算法 ===========================func deleteDuplicates2(head *ListNode) *ListNode {if head == nil || head.Next == nil {return head}if head.Val == head.Next.Val {// 跳过所有重复节点val := head.Valfor head != nil && head.Val == val {head = head.Next}return deleteDuplicates2(head)} else {// 保留当前节点head.Next = deleteDuplicates2(head.Next)return head}
}// =========================== 方法三:哈希表 ===========================func deleteDuplicates3(head *ListNode) *ListNode {if head == nil {return nil}// 统计每个值的出现次数count := make(map[int]int)curr := headfor curr != nil {count[curr.Val]++curr = curr.Next}// 创建新链表,只保留出现一次的值dummy := &ListNode{}prev := dummycurr = headfor curr != nil {if count[curr.Val] == 1 {prev.Next = currprev = prev.Next}curr = curr.Next}prev.Next = nilreturn dummy.Next
}// =========================== 方法四:优化版双指针 ===========================func deleteDuplicates4(head *ListNode) *ListNode {if head == nil {return nil}dummy := &ListNode{Next: head}prev := dummyfor prev.Next != nil {curr := prev.Next// 检查是否有重复if curr.Next != nil && curr.Val == curr.Next.Val {val := curr.Val// 跳过所有重复节点for curr != nil && curr.Val == val {curr = curr.Next}prev.Next = curr} else {prev = prev.Next}}return dummy.Next
}// =========================== 辅助函数 ===========================// 创建链表
func createList(vals []int) *ListNode {if len(vals) == 0 {return nil}head := &ListNode{Val: vals[0]}curr := headfor i := 1; i < len(vals); i++ {curr.Next = &ListNode{Val: vals[i]}curr = curr.Next}return head
}// 链表转数组
func listToArray(head *ListNode) []int {var result []intcurr := headfor curr != nil {result = append(result, curr.Val)curr = curr.Next}return result
}// 比较两个链表是否相等
func compareLists(l1, l2 *ListNode) bool {curr1, curr2 := l1, l2for curr1 != nil && curr2 != nil {if curr1.Val != curr2.Val {return false}curr1 = curr1.Nextcurr2 = curr2.Next}return curr1 == nil && curr2 == nil
}// =========================== 测试代码 ===========================func main() {fmt.Println("=== LeetCode 82: 删除排序链表中的重复元素 II ===\n")testCases := []struct {nums   []intexpect []int}{{[]int{1, 2, 3, 3, 4, 4, 5},[]int{1, 2, 5},},{[]int{1, 1, 1, 2, 3},[]int{2, 3},},{[]int{1},[]int{1},},{[]int{},[]int{},},{[]int{1, 1, 1, 1, 1},[]int{},},{[]int{1, 2, 3, 4, 5},[]int{1, 2, 3, 4, 5},},{[]int{1, 1, 2, 2, 3, 3},[]int{},},{[]int{1, 2, 2, 3, 3, 4},[]int{1, 4},},}fmt.Println("方法一:双指针算法(最优解法)")runTests(testCases, deleteDuplicates)fmt.Println("\n方法二:递归算法")runTests(testCases, deleteDuplicates2)fmt.Println("\n方法三:哈希表")runTests(testCases, deleteDuplicates3)fmt.Println("\n方法四:优化版双指针")runTests(testCases, deleteDuplicates4)
}func runTests(testCases []struct {nums   []intexpect []int
}, fn func(*ListNode) *ListNode) {passCount := 0for i, tc := range testCases {input := createList(tc.nums)expected := createList(tc.expect)result := fn(input)status := "✅"if !compareLists(result, expected) {status = "❌"} else {passCount++}fmt.Printf("  测试%d: %s\n", i+1, status)if status == "❌" {fmt.Printf("    输入: %v\n", tc.nums)fmt.Printf("    输出: %v\n", listToArray(result))fmt.Printf("    期望: %v\n", tc.expect)}}fmt.Printf("  通过: %d/%d\n", passCount, len(testCases))
}
http://www.dtcms.com/a/513819.html

相关文章:

  • 网站推广由什么样的人来做如何推广网站话术
  • 做网站含备案费么软件开发工具免费
  • 网站建设模块需求深圳物流公司哪家便宜又好
  • 电子商务网站推广的主要方法有什么好的网站建设的书
  • 有哪些好的做兼职网站有哪些wordpress带会员vip主题
  • 创建一个网站所需的成本中山网站建设熊掌号
  • 建设银行的网站查询密码创意网络广告
  • 小型手机网站建设企业如乐建站之家
  • 襄阳宜城网站建设进入wordpress
  • 免费的网站怎么建做推广任务的网站
  • 东莞手机建网站上海58招聘网最新招聘
  • 外贸网站建设内容包括哪些电子商务网站策划书2000字
  • 小说网站wordpress制作一个网站难吗
  • 网站一般怎么推广html网站 怎么做seo
  • 网站后台英文一个网站一年的费用
  • 建设科技网络网站的意义和目的wordpress怎么让文章只显示摘要
  • 网站建设书店目标客户分析太原推广型网站开发
  • 免费下载建筑图集规范的网站快速将网站seo
  • 二次元网站设计绍兴网站建设网站
  • 个人建设什么网站好初中学习网站大全免费
  • 济南做网站建设中小企业网站功能模块及数据库表
  • 官方网站开发商网络新闻专题做的最好的网站
  • 容桂网站建设公司网站开发 无代码
  • 婚礼效果图网站h5制作工具免费版
  • 如何取一个大气的名字的做网站北京国际化品牌设计
  • 如何查询网站服务商中小企业建站的方法
  • 南昌网站排名wordpress系统语言设置
  • 可以做视频创收的网站公众号开发工具下载
  • 北京的电商平台网站有哪些想建网站须要什么条件
  • 景区网站建设的好处网架报价清单表格