1.题目描述

设计一个支持在平均 时间复杂度 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();

2.解题思路

该题是之前那道Insert Delete GetRandom O(1)的拓展——允许插入重复的数字。

解题思路和之前的一样,使用的数据结构有两个:

(1)一个数组nums,用来保存每一个插入的val(可重复),与数组下标由一一对应的关系,便于getRandom返回时,每个元素被返回的概率应该与其在集合中的数量呈线性相关。

(2)一个哈希结构,unordered_map<int, unordered_set<int>>,建立的是val值和val所有出现位置集合之间的哈希映射。

只是写法略有不同,不同点如下:

  1. 由“一对一映射”变换成“一对多映射”;

因为有重复数字,不能像之前那样建立每个数字和其坐标的一对一映射,而是建立数字和其所有出现位置的集合之间的映射。

为了严格的遵守O(1)的时间复杂度,集合使用的unordered_set,其插入删除操作都是常量级的。

“一对一映射”
a——>1; //(a,1)
b——>2; //(b,2)

“一对多(集合)映射” (int ——> unordered_set<int>)
a——>{1,3,5} ; //(a,1),(a,3),(a,5)
b——>{2,4}; //(b,2),(b,4)

2.对于insert函数,将要插入的数字val加入数组nums中,并且将val在数组中的 位置加入m[val]数组的末尾。另外,判断是否有重复只要看m[val]数组中val值的个数是1个还是多个。(m是一个上面提及的unordered_map对象,m[val]是unordered_set集合,本质上是数组实现的集合)

3.对于remove函数,这是重难点。

  • 首先判断有无,如果哈希表有无val,没有直接返回false; 否则进行下一步;
  • 更新哈希表。取出nums的尾元素,把尾元素哈希表中的位置数组(集合,其中表征位置的数字是递增序列)中的最后一个元素更新为m[val]的尾元素,这样就可以删掉m[val]的尾元素了,如果m[val]只有一个元素,直接删除这个映射;
  • 对于数组nums,都是将数组最后一个位置的元素和要删除的元素交换位置,然后删除最后一个位置上的元素;(如果要删除的元素恰好是数组nums最后一个元素,直接删除即可。
假定依次插入的值:
(a,0)
(b,1)
(a,2)
(a,3)
(b,4) 数组nums中: 哈希表中:
[0,a] a——>{0,2,3}
[1,b]
[2,a] b——>{1,4}
[3,a]
[4,b] 调用remove(a)结果是:(删除了(a,3)) 数组nums中: 哈希表中:
[0,a] a——>{0,2}
[1,b]
[2,a] b——>{1,3}
[3,b]

  

3.示例代码

class RandomizedCollection {
public:
/** Initialize your data structure here. */
RandomizedCollection() {} /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
bool insert(int val) {
m[val].insert(nums.size());
//nums.size()初始值为0,随着元素一个个插入nums,size++
nums.push_back(val);
return m[val].size()==;
//val值的个数为1,返回true; val值的个数大于1,返回false
} /** Removes a value from the set. Returns true if the set contained the specified element. */
bool remove(int val) {
//每当要删除一个值val,用nums的最后一个值last_val替换要删除的填,最后删除的nums的最后一个值
//更新m[val]和m[last_val]两个集合中的值,m[val]最后一个元素弹出,同时将该元素替换m[last_val]中的最后的一个元素(删除最后一个,插入idx)
if(m[val].empty()) return false;
int idx = *m[val].begin();
m[val].erase(idx); //要删除的不是nums的最后一个元素
if(nums.size()- != idx){
int t = nums.back();//取nums的最后一个元素
nums[idx] = t;
m[t].erase(nums.size()-);
m[t].insert(idx);
} nums.pop_back();
return true;
} /** Get a random element from the set. */
int getRandom() {
if (nums.size() == ) {
return NULL;
}
int randomIndex = (int) (rand() % nums.size()); // 0 ~ size -1
return nums[randomIndex];
} //建立两个数据结构,一个是vector<int>,另一个是unordered_map<int,unordered_set<int>>;
private:
vector<int> nums;
unordered_map<int,unordered_set<int>> m;
};

4.Leetcode上用时更少的范例

这个虽然是当前我提交时,看到的运行时间最短的实现方法,但是这个方法严格来讲不是最好的,因为priority_queue<int>的增删的时间复杂度不是O(1)的,而是O(logN)。这里可以参考文末的参考博客,作者Grandyang一开始用的是优先队列(priority_queue),后面在博友的建议,改用了集合unordered_set。

提速的原因之一在于末尾的“static const auto io_sync_off = []()”的代码,原因可以参考我的另一篇博文Leetcode 295. 数据流的中位数

class RandomizedCollection
{
public:
RandomizedCollection()
{
srand(time(nullptr));
} bool insert(int val)
{
map[val].push(vOrders.size());
vOrders.push_back(val);
return map[val].size() <= ;
} bool remove(int val)
{
if ( map[val].empty() )
return false;
auto &heap = map[val];
int i = heap.top();
heap.pop();
int val2 = vOrders.back();
if ( val != val2 )
{
vOrders[i] = val2;
auto &heap2 = map[val2];
heap2.pop();
heap2.push(i);
}
vOrders.pop_back();
return true;
} int getRandom()
{
return vOrders[rand() % vOrders.size()];
} protected:
vector<int> vOrders;
unordered_map<int, priority_queue<int>> map;
}; static const auto io_sync_off = []()
{
// turn off sync
std::ios::sync_with_stdio(false);
// untie in/out streams
std::cin.tie(nullptr);
return nullptr;
}();

5.补充说明

这篇博客更多是学习大牛Grandyang的Leetcode All in One的博客集,绝大部分内容是参考的他的博客,链接附在文末。

这道题是hard级别的,我个人习惯通过写博客的方式来学习和加深对于问题的理解,用自己能够快速理解的方式呈现出来,方便自己日后的回顾(写一篇一年后能看懂的博客),如果有朋友因博文缺乏足够的原创内容而不满,还请多多谅解。毕竟,每个人都有自己的Style!

参考资料:

1.[LeetCode] Insert Delete GetRandom O(1) - Duplicates allowed 常数时间内插入删除和获得随机数 - 允许重复

Leetcode 381. O(1) 时间插入、删除和获取随机元素 - 允许重复的更多相关文章

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

    381. O(1) 时间插入.删除和获取随机元素 - 允许重复 设计一个支持在平均 时间复杂度 O(1) 下, 执行以下操作的数据结构. 注意: 允许出现重复元素. insert(val):向集合中插 ...

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

    381. O(1) 时间插入.删除和获取随机元素 - 允许重复 LeetCode_381 题目详情 题解分析 代码实现 package com.walegarrett.interview; impor ...

  3. LeetCode 381. Insert Delete GetRandom O(1) - Duplicates allowed O(1) 时间插入、删除和获取随机元素 - 允许重复(C++/Java)

    题目: Design a data structure that supports all following operations in averageO(1) time. Note: Duplic ...

  4. 381 Insert Delete GetRandom O(1) - Duplicates allowed O(1) 时间插入、删除和获取随机元素 - 允许重复

    设计一个支持在平均 时间复杂度 O(1) 下, 执行以下操作的数据结构.注意: 允许出现重复元素.    insert(val):向集合中插入元素 val.    remove(val):当 val ...

  5. [Swift]LeetCode381. O(1) 时间插入、删除和获取随机元素 - 允许重复 | Insert Delete GetRandom O(1) - Duplicates allowed

    Design a data structure that supports all following operations in averageO(1) time. Note: Duplicate ...

  6. Java实现 LeetCode 380 常数时间插入、删除和获取随机元素

    380. 常数时间插入.删除和获取随机元素 设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构. insert(val):当元素 val 不存在时,向集合中插入该项. remove( ...

  7. LeetCode380 常数时间插入、删除和获取随机元素

    LeetCode380 常数时间插入.删除和获取随机元素 题目要求 设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构. insert(val):当元素 val 不存在时,向集合中插 ...

  8. LeetCode 380. Insert Delete GetRandom O(1) 常数时间插入、删除和获取随机元素(C++/Java)

    题目: Design a data structure that supports all following operations in averageO(1) time. insert(val): ...

  9. LeetCode 哈希表 380. 常数时间插入、删除和获取随机元素(设计数据结构 List HashMap底层 时间复杂度)

    比起之前那些问计数哈希表的题目,这道题好像更接近哈希表的底层机制. java中hashmap的实现是通过List<Node>,即链表的list,如果链表过长则换为红黑树,如果容量不足(装填 ...

随机推荐

  1. 【转】MMORPG游戏服务器技能系统设计:表格字段与技能程序框架

    本文主要从一个程序员的角度阐述一下mmorpg服务器技能系统的程序框架设计,最近在做这个,就当做一个总结吧,其中某些概念可能没有解释清楚,欢迎大家拍砖讨论~ 技能其实是战斗系统的一个组成部分,战斗基本 ...

  2. UVa 1586 - Molar Mass - ACM/ICPC Seoul 2007 - C语言

    关键在于判断数字是两位数还是单位数,其他部分没有难度. #include"stdio.h" #include"string.h" #include"c ...

  3. NMAP-主机扫描

    1.全面扫描 2.扫描指定段 3.ping扫描 只进行ping操作,十分隐蔽 4.无ping扫描 适用于防火墙禁止ping 5.TCP SYN扫描 6.TCP ACK扫描 7.UDP扫描 8.ICMP ...

  4. Spring Boot - Filter实现简单的Http Basic认证

    Copy自http://blog.csdn.net/sun_t89/article/details/51916834 @SpringBootApplicationpublic class Spring ...

  5. Python如何运行

    Python是一种解释型语言,在执行Python的时,解释器将源代码source code翻译成字节码byte code,然后byte code交给Python虚拟机PVM去执行,整个流程如下图所示: ...

  6. iOS 出现错误reason: image not found的解决方案

    在制作framework时遇到真机运行时导致的reason: image not found允许崩溃的问题,下面是我的解决方案: 首先我们分析一下出现这种情况的原因,原因就是framework找不到镜 ...

  7. AppScan工作原理&操作教程

    一.AppScan的工作原理 对一个综合性的大型网站来说,可能存在成千上万的页面.以登录界面为例,至少要输入用户名和密码,即该页面存在两个字段,当提交了用户名和密码等登录信息,网站需要检查是否正确,这 ...

  8. phpcms退出 提示 :退出成功0 。 的解决办法

    打开/phpcms/modules/member/index.php 搜索如下代码: showmessage(L('logout_success').$synlogoutstr, $forward); ...

  9. == 和equal的区别?-005

    1,== 和equal的区别? ==比较两个值是否相等,equal比较对对象的引用是否一致 举例: int a = 2; int b = 2; System.err.println(a == b);/ ...

  10. arp获取

    getarp.c /* getarp.c -- This simple program uses an IOCTL socket call to read an entry */ /* from th ...