加载中...
381-O(1) 时间插入、删除和获取随机元素 - 允许重复(Insert Delete GetRandom O(1) - Duplicates allowed)
发表于:2021-12-03 | 分类: 困难
字数统计: 2k | 阅读时长: 10分钟 | 阅读量:

原文链接: https://leetcode-cn.com/problems/insert-delete-getrandom-o1-duplicates-allowed

英文原文

RandomizedCollection is a data structure that contains a collection of numbers, possibly duplicates (i.e., a multiset). It should support inserting and removing specific elements and also removing a random element.

Implement the RandomizedCollection class:

  • RandomizedCollection() Initializes the empty RandomizedCollection object.
  • bool insert(int val) Inserts an item val into the multiset, even if the item is already present. Returns true if the item is not present, false otherwise.
  • bool remove(int val) Removes an item val from the multiset if present. Returns true if the item is present, false otherwise. Note that if val has multiple occurrences in the multiset, we only remove one of them.
  • int getRandom() Returns a random element from the current multiset of elements. The probability of each element being returned is linearly related to the number of same values the multiset contains.

You must implement the functions of the class such that each function works on average O(1) time complexity.

Note: The test cases are generated such that getRandom will only be called if there is at least one item in the RandomizedCollection.

 

Example 1:

Input
["RandomizedCollection", "insert", "insert", "insert", "getRandom", "remove", "getRandom"]
[[], [1], [1], [2], [], [1], []]
Output
[null, true, false, true, 2, true, 1]

Explanation
RandomizedCollection randomizedCollection = new RandomizedCollection();
randomizedCollection.insert(1);   // return true since the collection does not contain 1.
                                  // Inserts 1 into the collection.
randomizedCollection.insert(1);   // return false since the collection contains 1.
                                  // Inserts another 1 into the collection. Collection now contains [1,1].
randomizedCollection.insert(2);   // return true since the collection does not contain 2.
                                  // Inserts 2 into the collection. Collection now contains [1,1,2].
randomizedCollection.getRandom(); // getRandom should:
                                  // - return 1 with probability 2/3, or
                                  // - return 2 with probability 1/3.
randomizedCollection.remove(1);   // return true since the collection contains 1.
                                  // Removes 1 from the collection. Collection now contains [1,2].
randomizedCollection.getRandom(); // getRandom should return 1 or 2, both equally likely.

 

Constraints:

  • -231 <= val <= 231 - 1
  • At most 2 * 105 calls in total will be made to insert, remove, and getRandom.
  • There will be at least one element in the data structure when getRandom is called.

中文题目

设计一个支持在平均 时间复杂度 O(1) , 执行以下操作的数据结构。

注意: 允许出现重复元素。

  1. insert(val):向集合中插入元素 val。
  2. remove(val):当 val 存在时,从集合中移除一个 val。
  3. getRandom:从现有集合中随机获取一个元素。每个元素被返回的概率应该与其在集合中的数量呈线性相关。

示例:

// 初始化一个空的集合。
RandomizedCollection collection = new RandomizedCollection();

// 向集合中插入 1 。返回 true 表示集合不包含 1 。
collection.insert(1);

// 向集合中插入另一个 1 。返回 false 表示集合包含 1 。集合现在包含 [1,1] 。
collection.insert(1);

// 向集合中插入 2 ,返回 true 。集合现在包含 [1,1,2] 。
collection.insert(2);

// getRandom 应当有 2/3 的概率返回 1 ,1/3 的概率返回 2 。
collection.getRandom();

// 从集合中删除 1 ,返回 true 。集合现在包含 [1,2] 。
collection.remove(1);

// getRandom 应有相同概率返回 1 和 2 。
collection.getRandom();

通过代码

高赞题解

方法一:哈希表

思路与算法

为了使得 $O(1)$ 时间内能够随机获取一个元素,我们将每个数值(可以重复)存储在一个列表 $\textit{nums}$ 中。这样,获取随机元素时,只需要随机生成一个列表中的索引,就能够得到一个随机元素。

这样做的问题在于:列表中的随机删除并不是 $O(1)$ 的。然而我们可以发现,列表中元素的顺序是无关紧要的,只要它们正确地存在于列表中即可。因此,在删除元素时,我们可以将被删的元素与列表中最后一个元素交换位置,随后便可以在 $O(1)$ 时间内,从列表中去除该元素。

这需要我们额外维护数值在列表中每一次出现的下标集合。对于数值 $\textit{val}$ 而言,记其下标集合为 $S_{idx}$。

在删除时,我们找出 $val$ 出现的其中一个下标 $i$,并将 $\textit{nums}[i]$ 与 $\textit{nums}[\textit{nums}.\textit{length}-1]$ 交换。随后,将 $i$ 从 $S_{val}$ 中去除,并将 $S_{\textit{nums}[\textit{nums}.\textit{length}-1]}$ 中原有的 $\textit{nums}[\textit{nums}.\textit{length}-1]$ 替换成 $i$。由于集合的每个操作都是 $O(1)$ 的,因此总的平均时间复杂度也是 $O(1)$ 的。

代码

[sol1-C++]
class RandomizedCollection { public: unordered_map<int, unordered_set<int>> idx; vector<int> nums; /** Initialize your data structure here. */ RandomizedCollection() { } /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */ bool insert(int val) { nums.push_back(val); idx[val].insert(nums.size() - 1); return idx[val].size() == 1; } /** Removes a value from the collection. Returns true if the collection contained the specified element. */ bool remove(int val) { if (idx.find(val) == idx.end()) { return false; } int i = *(idx[val].begin()); nums[i] = nums.back(); idx[val].erase(i); idx[nums[i]].erase(nums.size() - 1); if (i < nums.size() - 1) { idx[nums[i]].insert(i); } if (idx[val].size() == 0) { idx.erase(val); } nums.pop_back(); return true; } /** Get a random element from the collection. */ int getRandom() { return nums[rand() % nums.size()]; } };
[sol1-Java]
class RandomizedCollection { Map<Integer, Set<Integer>> idx; List<Integer> nums; /** Initialize your data structure here. */ public RandomizedCollection() { idx = new HashMap<Integer, Set<Integer>>(); nums = new ArrayList<Integer>(); } /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */ public boolean insert(int val) { nums.add(val); Set<Integer> set = idx.getOrDefault(val, new HashSet<Integer>()); set.add(nums.size() - 1); idx.put(val, set); return set.size() == 1; } /** Removes a value from the collection. Returns true if the collection contained the specified element. */ public boolean remove(int val) { if (!idx.containsKey(val)) { return false; } Iterator<Integer> it = idx.get(val).iterator(); int i = it.next(); int lastNum = nums.get(nums.size() - 1); nums.set(i, lastNum); idx.get(val).remove(i); idx.get(lastNum).remove(nums.size() - 1); if (i < nums.size() - 1) { idx.get(lastNum).add(i); } if (idx.get(val).size() == 0) { idx.remove(val); } nums.remove(nums.size() - 1); return true; } /** Get a random element from the collection. */ public int getRandom() { return nums.get((int) (Math.random() * nums.size())); } }
[sol1-JavaScript]
/** * Initialize your data structure here. */ var RandomizedCollection = function() { this.idx = new Map(); this.nums = []; }; /** * Inserts a value to the collection. Returns true if the collection did not already contain the specified element. * @param {number} val * @return {boolean} */ RandomizedCollection.prototype.insert = function(val) { this.nums.push(val); const set = this.idx.has(val) ? this.idx.get(val) : new Set(); set.add(this.nums.length - 1); this.idx.set(val, set); return set.size === 1; }; /** * Removes a value from the collection. Returns true if the collection contained the specified element. * @param {number} val * @return {boolean} */ RandomizedCollection.prototype.remove = function(val) { if (!this.idx.has(val)) { return false; } let i = undefined; for (const it of this.idx.get(val)) { if (!i) { i = it; break; } } const lastNum = this.nums[this.nums.length - 1]; this.nums[i] = lastNum; this.idx.get(val).delete(i); this.idx.get(lastNum).delete(this.nums.length - 1); if (i < this.nums.length - 1) { this.idx.get(lastNum).add(i); } if (this.idx.get(val).size === 0) { this.idx.delete(val); } this.nums.pop(); return true; }; /** * Get a random element from the collection. * @return {number} */ RandomizedCollection.prototype.getRandom = function() { return this.nums[Math.floor(Math.random() * this.nums.length)]; };
[sol1-Golang]
type RandomizedCollection struct { idx map[int]map[int]struct{} nums []int } /** Initialize your data structure here. */ func Constructor() RandomizedCollection { return RandomizedCollection{ idx: map[int]map[int]struct{}{}, } } /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */ func (r *RandomizedCollection) Insert(val int) bool { ids, has := r.idx[val] if !has { ids = map[int]struct{}{} r.idx[val] = ids } ids[len(r.nums)] = struct{}{} r.nums = append(r.nums, val) return !has } /** Removes a value from the collection. Returns true if the collection contained the specified element. */ func (r *RandomizedCollection) Remove(val int) bool { ids, has := r.idx[val] if !has { return false } var i int for id := range ids { i = id break } n := len(r.nums) r.nums[i] = r.nums[n-1] delete(ids, i) delete(r.idx[r.nums[i]], n-1) if i < n-1 { r.idx[r.nums[i]][i] = struct{}{} } if len(ids) == 0 { delete(r.idx, val) } r.nums = r.nums[:n-1] return true } /** Get a random element from the collection. */ func (r *RandomizedCollection) GetRandom() int { return r.nums[rand.Intn(len(r.nums))] }

复杂度分析

  • 时间复杂度:$O(1)$。
  • 空间复杂度:$O(N)$,其中 $N$ 为集合中的元素数目。

统计信息

通过次数 提交次数 AC比率
21574 49101 43.9%

提交历史

提交时间 提交结果 执行时间 内存消耗 语言

相似题目

题目 难度
O(1) 时间插入、删除和获取随机元素 中等
上一篇:
380-O(1) 时间插入、删除和获取随机元素(Insert Delete GetRandom O(1))
下一篇:
382-链表随机节点(Linked List Random Node)
本文目录
本文目录