LeetCode 380 - O(1) 时间插入、删除和获取随机元素
文章目录
- 摘要
- 描述
- 题解答案
- 题解代码分析
- 代码解析
- 示例测试及结果
- 运行结果
- 时间复杂度
- 空间复杂度
- 总结
摘要
在很多系统设计里,我们经常会遇到“集合”这种数据结构:它需要支持快速插入、删除和查找操作。但有时候,还会额外要求一个功能——随机获取集合里的元素,并且每个元素都有相同的概率被返回。
LeetCode 第 380 题就是这样一个有意思的问题:设计一个数据结构,能够在 平均 O(1) 的时间内完成 插入、删除、随机获取 三个操作。本文会从需求出发,带你一步步拆解这道题,并写出可运行的 Swift 代码。
描述
题目要求实现一个类 RandomizedSet
,包含以下方法:
-
insert(val)
- 如果
val
不存在,插入集合并返回true
;否则返回false
。
- 如果
-
remove(val)
- 如果
val
存在,从集合中删除并返回true
;否则返回false
。
- 如果
-
getRandom()
- 从集合中等概率返回一个元素。
要求:每个操作的平均时间复杂度必须是 O(1)。
示例运行效果:
输入
["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"]
[[], [1], [2], [2], [], [1], [2], []]输出
[null, true, false, true, 2, true, false, 2]
题解答案
直觉上,如果只用数组,随机获取很方便,但删除就可能是 O(n),因为要移动元素。
如果只用哈希表,插入和删除很快,但随机取元素就不好办。
所以,我们需要结合 数组 + 哈希表 的优点:
- 数组(list):方便随机访问,
getRandom()
可以在 O(1) 时间完成。 - 哈希表(dict/map):存储每个元素在数组中的索引,保证
insert
和remove
能在 O(1) 完成。
关键技巧是:
- 删除时,先把要删除的元素与数组最后一个元素交换位置,然后删除最后一个元素,就能避免 O(n) 的移动操作。
题解代码分析
下面是 Swift 的完整实现:
import Foundationclass RandomizedSet {private var dict: [Int: Int] // 记录 val -> indexprivate var list: [Int] // 存储元素数组init() {dict = [:]list = []}// 插入操作func insert(_ val: Int) -> Bool {if dict[val] != nil {return false}list.append(val)dict[val] = list.count - 1return true}// 删除操作func remove(_ val: Int) -> Bool {guard let index = dict[val] else {return false}let last = list.last!// 把最后一个元素放到 index 位置list[index] = lastdict[last] = index// 删除最后一个元素list.removeLast()dict.removeValue(forKey: val)return true}// 随机获取func getRandom() -> Int {return list.randomElement()!}
}
代码解析
-
dict 哈希表
- 记录每个元素在数组中的位置,保证 O(1) 查找。
-
list 数组
- 存放所有元素,支持 O(1) 随机访问。
-
insert(val)
- 如果元素已存在,直接返回
false
。 - 否则,把元素插入数组尾部,同时在 dict 中记录索引。
- 如果元素已存在,直接返回
-
remove(val)
- 查找元素索引,如果不存在,返回
false
。 - 如果存在,把该元素与数组最后一个元素交换,更新哈希表,然后删除数组尾部。
- 这样避免了 O(n) 的删除操作。
- 查找元素索引,如果不存在,返回
-
getRandom()
- 直接用 Swift 内置的
randomElement()
从数组中等概率获取一个元素。
- 直接用 Swift 内置的
示例测试及结果
我们来写一段 Demo 测试一下:
let randomizedSet = RandomizedSet()print(randomizedSet.insert(1)) // true
print(randomizedSet.remove(2)) // false
print(randomizedSet.insert(2)) // true
print(randomizedSet.getRandom())// 1 或 2
print(randomizedSet.remove(1)) // true
print(randomizedSet.insert(2)) // false (因为 2 已存在)
print(randomizedSet.getRandom())// 必然是 2
运行结果
true
false
true
2
true
false
2
效果和题目描述完全一致。
时间复杂度
- insert: O(1)
- remove: O(1)
- getRandom: O(1)
因此整体时间复杂度就是 O(1)。
空间复杂度
- 使用了一个数组
list
和一个哈希表dict
,都最多存储n
个元素。 - 空间复杂度为 O(n)。
总结
这道题的精髓在于 数组 + 哈希表 的巧妙组合:
- 数组让我们能快速获取随机元素;
- 哈希表让我们能快速查找元素位置;
- 删除操作通过“交换 + 删除尾部”优化到 O(1)。
在实际开发中,这种设计思路同样很实用,比如:
- 游戏中随机抽卡系统(快速插入、移除、随机获取奖励项);
- 缓存系统中随机淘汰策略(随机挑选一个元素删除);
- 动态集合管理(需要等概率抽取元素)。