【算法挑战】常数时间插入、删除和获取随机元素(含解析、源码)

news/2024/11/8 16:41:50/

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

https://leetcode-cn.com/problems/insert-delete-getrandom-o1/

题目描述

设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构。insert(val):当元素 val 不存在时,向集合中插入该项。
remove(val):元素 val 存在时,从集合中移除该项。
getRandom:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。
示例 :// 初始化一个空的集合。
RandomizedSet randomSet = new RandomizedSet();// 向集合中插入 1 。返回 true 表示 1 被成功地插入。
randomSet.insert(1);// 返回 false ,表示集合中不存在 2 。
randomSet.remove(2);// 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。
randomSet.insert(2);// getRandom 应随机返回 1 或 2 。
randomSet.getRandom();// 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。
randomSet.remove(1);// 2 已在集合中,所以返回 false 。
randomSet.insert(2);// 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。
randomSet.getRandom();

思路

首先得考虑的是,用数组还是用链表来存,先来复习一下数组和链表常见操作的时间复杂度吧。

数组操作时间复杂度
随机访问 O ( 1 ) O(1) O(1)
插入数值到数组 O ( N ) O(N) O(N)
插入数值到数组最后 O ( 1 ) O(1) O(1)
从数组删除数值 O ( N ) O(N) O(N)
从数组最后删除数值 O ( 1 ) O(1) O(1)
链表操作时间复杂度
访问 O ( N ) O(N) O(N)
插入数值到链表 O ( N ) O(N) O(N)
插入数值到链表开头 O ( 1 ) O(1) O(1)
从链表删除数值 O ( N ) O(N) O(N)
从链表开头删除数值 O ( 1 ) O(1) O(1)

很显然,链表时间复杂度为 O ( N ) O(N) O(N) 的访问操作并不符合我们的需求,所以我们还是选择数组来作为存储数据的容器。

插入

首先要实现常数时间插入元素,我们只能在数组最后插入。

在数组最后插入元素

Untitled-2020-06-05-2052

在数组其他位置插入元素

insert-o(n)

获取随机元素

这个就很简单了,因为数组是可以通过下标随机访问的,我们只需要生成一个 0 ~ N-1 的随机数即可,N 为数组长度。

删除

删除元素的操作可以分为两种:

  1. 删除末尾元素,时间复杂度为 O(1)

remove-last

  1. 删除非末尾元素,因为删除位置之后的每个元素都要向前移动一步,所以时间复杂度是 O(N)

remove-o(n)

显然,如果我们想实现题目要求的 O(1) 时间的删除,只能在数组末尾进行删除操作。具体做法就是把要删除的元素和末尾的元素换个位置,然后再从数组末尾删除。

remove-o(1)
那我们再来看看 API 是怎么用的:

  • set.insert(2) 表示往集合中插入数值 2,成功插入返回 true,如果 2 已经存在集合中返回 false
  • set.remove(2) 表示从集合中删除数值 2,成功删除返回 true,如果 2 不存在集合中返回 false

可以看到这两个方法的参数都是值,而在数组中,要在常数时间内找到一个元素,必须要知道它的下标。所以显然我们还需要一个结构来记录集合中的值和它所在的数组下标的关系,这样一系列 值->下标 的对应关系,你应该能想到用一个哈希表来记录。

hashmap 2

数组中存放着真正的值,而哈希表中存放着每个值所对应的数组下标。

但是,还有一个问题,还记得删除操作么?我们是先把要删除的元素和最后的元素换了位置再删除,换了位置后,两个元素的下标也变了。

remove-swap

所以很显然的,删除某个元素后,我们的哈希表也需要更新。

update-hashmap

代码

class RandomizedSet {constructor() {// store the actual valuesthis.array = [];// store the value-> index mappingthis.map = {};}insert(val) {if (val in this.map) return false;this.array.push(val);this.map[val] = this._size() - 1;return true;}remove(val) {if (!(val in this.map)) return false;const index = this.map[val];const lastIndex = this._size() - 1;if (index < lastIndex) {this._swap(index, lastIndex);this.map[this.array[index]] = index;}this.array.pop();delete this.map[val];return true;}getRandom() {const size = this._size();if (size === 0) return false;let randomIndex = Math.floor(Math.random() * size);return this.array[randomIndex];}_size() {return this.array.length;}_swap(a, b) {const temp = this.array[b];this.array[b] = this.array[a];this.array[a] = temp;}
}

Originally posted by @suukii in https://github.com/leetcode-pp/91alg-1/issues/23#issuecomment-640231502

[参考题解](Originally posted by @azl397985856 in https://github.com/leetcode-pp/91alg-1/issues/23#issuecomment-640155651)

总结

以上就是本文所有内容了,希望能对你有所帮助,能够解决常数时间插入、删除和获取随机元素问题。

如果你喜欢本文,也请务必点赞、收藏、评论、转发,这会对我有非常大的帮助。请我喝杯冰可乐也是极好的!

已完结,欢迎持续关注。下次见~


http://www.ppmy.cn/news/1195560.html

相关文章

在Node.js中,什么是中间件(middleware)?它们的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

P3817 小A的糖果

Portal. 贪心。 注意到这里的盒子不会被删除&#xff0c;只会改变盒子的值。问题立刻简单化了。对于一组相邻的糖果个数和大于 x x x 的盒子组&#xff0c;优先吃掉靠后的盒子。 证明正确性也很显然&#xff0c;因为减少后面的盒子的糖果数可以使得后面的情况更优。 #incl…

【进程控制⑦】:制作简易shell理解shell运行原理

【进程控制⑦】&#xff1a;制作简易shell&&理解shell运行原理 一.交互问题&#xff0c;获取命令行二.字串分割问题&#xff0c;解析命令行三.指令的判断四.普通命令的执行五.shell原理本质 一.交互问题&#xff0c;获取命令行 shell刚启动时就会出现一行命令行&#x…

新手学计算机编程入门,自学编程入门从哪里入手开始学习

新手学计算机编程入门&#xff0c;自学编程入门从哪里入手开始学习 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#xff0c;而且可以开发大型的软件&#xff0c;向如图这个…

文字的力量

不知道以前的时代的年轻人有没有这样的感受。现在我觉得自己是不是出现了认知偏差&#xff0c;发现在很多描写现在的二十几岁年轻人的成长经历的文字下面都会出现很多共鸣&#xff0c;包括我自己也有&#xff0c;就让我有一个错觉:是不是中国所有的和我同龄的年轻人都是这样过来…

动态规划30(Leetcode123买股票的最佳时机3)

1107 代码&#xff1a; class Solution {public int maxProfit(int[] prices) {int n prices.length;int[][] dp new int[n][5];dp[0][0] 0;dp[0][1] -prices[0];dp[0][2] 0;dp[0][3] -prices[0];dp[0][4] 0;for(int i1;i<n;i){dp[i][0] dp[i-1][0];dp[i][1] Mat…

c#的反编译工具ISPY和net reflector 使用比较

我有一份Asp.net程序需要修改&#xff0c;但没有源码&#xff0c;只有dll&#xff0c;需要使用反编译工具回复源码&#xff0c;尝试使用了市面上的两种主流的工具ISPY和net reflector &#xff0c;最终用ISPY恢复了源码。 比较 ISPY 恢复的代码和实际有差距&#xff0c;但还能…

git关联远程仓库自己分支自用

初始化仓库 cassielDESKTOP-KPKFOEU MINGW64 /d/code/api_test_202310232022 (tong) $ git init Reinitialized existing Git repository in D:/code/api_test_202310232022/.git/关联远程仓库并创建本地分支 cassielDESKTOP-KPKFOEU MINGW64 /d/code/api_test_202310232022 …