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

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) 的访问操作并不符合我们的需求,所以我们还是选择数组来作为存储数据的容器。

插入

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

在数组最后插入元素

在数组其他位置插入元素

获取随机元素

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

删除

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

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

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

那我们再来看看 API 是怎么用的:

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

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

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

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

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

代码

js 复制代码
class RandomizedSet {
    constructor() {
        // store the actual values
        this.array = [];
        // store the value-> index mapping
        this.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)

总结

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

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

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

相关推荐
羊小猪~~2 分钟前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
王哈哈^_^27 分钟前
【数据集】【YOLO】【VOC】目标检测数据集,查找数据集,yolo目标检测算法详细实战训练步骤!
人工智能·深度学习·算法·yolo·目标检测·计算机视觉·pyqt
星沁城29 分钟前
240. 搜索二维矩阵 II
java·线性代数·算法·leetcode·矩阵
脉牛杂德1 小时前
多项式加法——C语言
数据结构·c++·算法
legend_jz1 小时前
STL--哈希
c++·算法·哈希算法
kingmax542120081 小时前
初三数学,最优解问题
算法
一直学习永不止步1 小时前
LeetCode题练习与总结:赎金信--383
java·数据结构·算法·leetcode·字符串·哈希表·计数
小刘|2 小时前
《Java 实现希尔排序:原理剖析与代码详解》
java·算法·排序算法
jjyangyou2 小时前
物联网核心安全系列——物联网安全需求
物联网·算法·安全·嵌入式·产品经理·硬件·产品设计
van叶~2 小时前
算法妙妙屋-------1.递归的深邃回响:二叉树的奇妙剪枝
c++·算法