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

LeetCode 381 - O(1) 时间插入、删除和获取随机元素(允许重复)

在这里插入图片描述
在这里插入图片描述

文章目录

    • 摘要
    • 描述
    • 题解答案
    • 题解代码分析
    • 代码拆解
    • 示例测试及结果
    • 时间复杂度
    • 空间复杂度
    • 总结

摘要

这道题有点意思,它让我们实现一个特殊的数据结构 RandomizedCollection,要求能在 平均 O(1) 时间 内完成插入、删除和随机获取元素,而且集合里是允许有重复值的。

听起来像是 HashSet + Array 的结合体,但允许重复元素就让问题变得更 tricky。接下来我会详细展开解决方案、代码解析,以及跟实际场景结合,让你彻底搞懂这道题。

描述

我们需要实现一个 RandomizedCollection,支持以下操作:

  • 初始化:创建一个空集合
  • insert(val):把元素插入集合。即使该元素已经存在也要插进去。如果元素之前不存在,返回 true,否则返回 false
  • remove(val):删除集合里的一个 val,如果存在返回 true,否则返回 false。注意如果有多个相同元素,只删除其中一个。
  • getRandom():随机返回集合中的一个元素,返回的概率跟该元素出现的次数成正比。

举个例子:

let collection = RandomizedCollection()
collection.insert(1)   // true, 集合 = [1]
collection.insert(1)   // false, 集合 = [1, 1]
collection.insert(2)   // true, 集合 = [1, 1, 2]
collection.getRandom() // 2/3 概率返回 1, 1/3 概率返回 2
collection.remove(1)   // true, 集合 = [1, 2]
collection.getRandom() // 50% 概率返回 1, 50% 概率返回 2

题解答案

核心思路是结合 动态数组 + 哈希表

  1. 用一个数组 nums 存储所有元素,这样我们可以 O(1) 时间随机访问
  2. 用一个字典 indices 存储每个值对应的 下标集合(因为有重复,所以要存多个下标)。
  3. 插入时:直接追加到数组,并把下标存进字典。
  4. 删除时:为了保持 O(1),我们把要删的元素跟最后一个元素交换,再 pop 掉数组末尾,这样就不会有“挪动一大段数组”的开销。字典里的下标集合也要同步更新。
  5. 随机获取:直接从数组里 randomElement() 即可。

题解代码分析

下面是 Swift 实现:

import Foundationclass RandomizedCollection {private var nums: [Int]private var indices: [Int: Set<Int>]init() {nums = []indices = [:]}// 插入元素func insert(_ val: Int) -> Bool {let existed = indices[val] != nilnums.append(val)indices[val, default: []].insert(nums.count - 1)return !existed}// 删除元素func remove(_ val: Int) -> Bool {guard var valSet = indices[val], !valSet.isEmpty else {return false}// 从 val 的下标集合里取出一个位置let removeIndex = valSet.removeFirst()// 如果删除的不是最后一个元素,需要做交换if let lastVal = nums.last, removeIndex < nums.count - 1 {nums[removeIndex] = lastVal// 更新 lastVal 的索引集合indices[lastVal]!.remove(nums.count - 1)indices[lastVal]!.insert(removeIndex)}nums.removeLast()if valSet.isEmpty {indices[val] = nil} else {indices[val] = valSet}return true}// 随机返回一个元素func getRandom() -> Int {return nums.randomElement()!}
}

代码拆解

  • nums: [Int]
    存储所有插入的元素,可以重复。

  • indices: [Int: Set<Int>]
    用一个字典来记录每个值对应的下标集合,比如 {1: {0,1}, 2: {2}}

  • insert
    直接把值 append 到 nums,并把下标放进 indices

  • remove

    • 先拿到该值的一个下标 removeIndex
    • 如果要删除的位置不是最后一个元素,就跟 nums.last 交换位置
    • 更新哈希表里对应的索引集合
    • 删除数组最后一个元素(O(1))
  • getRandom
    直接调用 randomElement(),时间复杂度 O(1)。

示例测试及结果

我们来运行一下 Demo:

let collection = RandomizedCollection()print(collection.insert(1)) // true, 集合 = [1]
print(collection.insert(1)) // false, 集合 = [1,1]
print(collection.insert(2)) // true, 集合 = [1,1,2]print("随机结果:", collection.getRandom()) // 可能是 1 或 2print(collection.remove(1)) // true, 集合 = [1,2]print("随机结果:", collection.getRandom()) // 可能是 1 或 2

可能输出:

true
false
true
随机结果: 1
true
随机结果: 2

多次运行,你会发现返回的结果是随机的,但概率分布符合要求。

时间复杂度

  • insert: O(1)
  • remove: O(1)(通过交换和字典更新实现)
  • getRandom: O(1)

即便有重复元素,整体仍能保证平均 O(1) 的时间复杂度。

空间复杂度

  • nums 存储所有元素,占用 O(n)。
  • indices 存储每个值对应的下标集合,也占用 O(n)。
    整体空间复杂度:O(n)。

总结

这道题的关键点在于:

  • 数组提供 O(1) 随机访问
  • 哈希表提供 O(1) 元素索引定位
  • 交换删除保证 O(1) 删除操作

实际场景里,这个结构特别适合做 随机抽奖池随机推荐系统:比如你有一个商品池,用户可以不断加商品、删商品,每次要随机推荐一个商品给用户,而且还要考虑重复的概率。这个题的思路就是一个很实用的底层实现。


文章转载自:

http://h80Dzo4M.wzdjL.cn
http://qzuCYZUV.wzdjL.cn
http://0wN6OqMz.wzdjL.cn
http://n7d8v1yH.wzdjL.cn
http://xdrfDLQA.wzdjL.cn
http://bSVq3Mu9.wzdjL.cn
http://x8vQqKGc.wzdjL.cn
http://34R4j2Pk.wzdjL.cn
http://SZOVAUqQ.wzdjL.cn
http://RbzeNsv5.wzdjL.cn
http://1Nhqhd1i.wzdjL.cn
http://zVqbrH4k.wzdjL.cn
http://U0joWMox.wzdjL.cn
http://crIQbIbt.wzdjL.cn
http://XnVCPEdm.wzdjL.cn
http://PsWFjWmM.wzdjL.cn
http://gwzWuBnY.wzdjL.cn
http://Xc9Iqp4A.wzdjL.cn
http://6T4D2NyO.wzdjL.cn
http://6wAsZx10.wzdjL.cn
http://EQl8GzrK.wzdjL.cn
http://OQe6Es2I.wzdjL.cn
http://Sr8gc6RG.wzdjL.cn
http://mYWHstFW.wzdjL.cn
http://3LaH2peR.wzdjL.cn
http://BMbd1uJi.wzdjL.cn
http://hovjt4H6.wzdjL.cn
http://EOAaNziJ.wzdjL.cn
http://3bq9cyjG.wzdjL.cn
http://384LOCNs.wzdjL.cn
http://www.dtcms.com/a/387810.html

相关文章:

  • [新启航]深孔加工尺寸精度检测方法 - 激光频率梳 3D 轮廓测量
  • MySQL 进阶:多表联合查询与数据备份恢复
  • 【LeetCode每日一题】:移除链表元素
  • 工业大数据时代时序数据库选型指南:为何Apache IoTDB成为首选?
  • Java 中 ArrayList 与 LinkedList 的深度对比:从原理到实战选择
  • 向量检索服务 DashVector产品功能
  • Spring-Cloud-Alibaba:2023.0.1.X引起的dashscope-sdk-java依赖冲突问题
  • vue 知识点
  • 深入理解 Linux 进程调度:从策略到实现的全方位解析
  • 【技术架构】从单机到微服务:Java 后端架构演进与技术选型核心方案
  • Java异常报错: java.io.IOException: Broken pipe
  • [Linux]学习笔记系列 -- lib/kobject.c 内核对象(Kernel Object) 设备模型的核心基石
  • 专题:Python实现贝叶斯线性回归与MCMC采样数据可视化分析2实例|附代码数据
  • IEEE 802.1X和**IEEE 802.11之间的关联和作用
  • 【Linux】【底层解析向】Linux Shell 核心功能拆解:环境变量不生效原因 + $?/echo/alias 底层逻辑
  • UV紫外卤素灯太阳光模拟器的原理
  • RAG简单构建(ollama+uv+deepseek)
  • 告别冰冷AI音!B站开源IndexTTS2模型,零样本克隆+情感解耦,玩法超多!
  • pytorch中.pt和.pth文件区别
  • 目标计数(3)Object Counting: You Only Need to Look at One
  • 拖拽移动并监听点击事件
  • Hibernate 和 MyBatis差异分析
  • RAG 核心技术深度剖析:架构设计与性能优化实战指南
  • Java全栈学习笔记36
  • python 任务管理器
  • AI 驱动智能驾驶:L4 级技术落地瓶颈、车企博弈与用户信任构建
  • VS Code和Cursor扩展主机在过去5分钟内意外终止了3次问题解决方案
  • 【TestCenter】创建DHCP Server和DHCP Client
  • 内存泄漏系列专题分析之三十五:开机内存性能优化之一:Camx进程启动提前加载so库
  • 知微传感Dkam系列3D相机SDK例程篇:CSharp设置相机工作模式