oracale delete 返回-2_算法面试题——set常数时间内实现add、delete、has

set是ES6当中一个新的数据结构,set类似于数组,内部元素可以是基础类型,或者是对象,但是必须是唯一的。

对于set,如果想模拟实现它的add、delete、has方法,可以使用一个内部array来保存数据:indexOf来返回是否has某个元素;add新元素的时候使用indexOf查找元素是否已经存在,再决定是否插入;delete的时候,也是使用indexOf来找到元素的index,然后使用splice方法删除。

不过indexOf的时间复杂度是O(n)。那么有没有O(1)时间复杂度的模拟方法呢?

最近发现leetcode有道380. Insert Delete GetRandom O(1)。这不就是模拟了set的add、delete、has么?

题目阅读

首先看leetcode这道题的题目:

Design a data structure that supports all following operations in average O(1) time.
insert(val): Inserts an item val to the set if not already present.
remove(val): Removes an item val from the set if present.
getRandom: Returns a random element from current set of elements. 
Each element must have the same probability of being returned.
Example:
// Init an empty set. RandomizedSet randomSet = new RandomizedSet();  
// Inserts 1 to the set. Returns true as 1 was inserted successfully. randomSet.insert(1);  
// Returns false as 2 does not exist in the set. randomSet.remove(2);  
// Inserts 2 to the set, returns true. Set now contains [1,2]. randomSet.insert(2);  
// getRandom should return either 1 or 2 randomly. randomSet.getRandom();  
// Removes 1 from the set, returns true. Set now contains [2]. randomSet.remove(1);  
// 2 was already in the set, so return false. randomSet.insert(2);  
// Since 2 is the only number in the set, getRandom always return 2. randomSet.getRandom();

要求常数时间内插入、删除和获取随机数。前两个要求不就是set的add和delete么?这道题的实现方法很有趣,因此,拿出来和大家分享一下。

那我们看看这道题的解法是什么:

解题

首先是这个set的初始化,我们要使用一个数组this.set来保存插入set的元素,然后,关键来了,我们还需要一个Map类型的变量来保存数组每个元素的index。

/**
 * Initialize your data structure here.
 */
var RandomizedSet = function() {
    this.set = [];
    this.map = new Map();
};

然后,实现insert这个方法。和使用indexOf的方法很类似,我们先要找到这个元素是否已经存在于this.set这个数组中。但是,我们不使用indexOf方法,而是在this.map中查找这个元素是否已经存在:

if (this.map.get(val) === undefined) return false;

如果不存在,那我们就需要把这个元素push到this.set当中,然后也需要把这个元素和其index放到this.map当中,最后insert代码如下所示:

/**
 * Inserts a value to the set. Returns true if the set did not already contain the specified element. 
 * @param {number} val
 * @return {boolean}
 */
RandomizedSet.prototype.insert = function(val) {
    if (this.map.get(val) !== undefined) return false;
    this.set.push(val);
    this.map.set(val, this.set.length-1);
    return true;
};

第三个要求是实现delete方法,这也是这道题目最tricky的地方。

首先,还是需要判断元素是否存在this.set当中,删除一个不存在的元素是没有任何意义的,具体做法和上面insert相同,不再赘述。

之后,我们要找到需要删除的元素的位置,那么,如果不使用indexOf,我们怎么知道这个元素的位置呢?聪明的朋友已经想到了,this.map不就保存着这个元素的位置么?问题又来了,把这个元素直接删掉了之后,那它后面的元素的位置不都依次减1了么?

trick就在这里:我们用最后一个元素,替换掉需要删掉的元素,然后,把最后一个元素的index更改为被删掉的元素的index,最后,把最后那个元素删掉,这样,后面的元素的位置就不会依次减一。

代码如下:

/**
 * Removes a value from the set. Returns true if the set contained the specified element. 
 * @param {number} val
 * @return {boolean}
 */
RandomizedSet.prototype.remove = function(val) {
    if (this.map.get(val) === undefined) return false;
    // 把最后一个元素填充到被删掉元素的位置
    let last = this.set[this.set.length-1];
    this.set.splice(this.map.get(val), 1, last);
    // 把最后一个元素删掉
    this.set.pop();
    // 把最后一个元素的index改掉
    this.map.set(last, this.map.get(val));
    this.map.delete(val);
    return true;
};

getRandom跟set关系不大,不再详述。

最终的代码如下:

/**
 * Initialize your data structure here.
 */
var RandomizedSet = function() {
    this.set = [];
    this.map = new Map();
};

/**
 * Inserts a value to the set. Returns true if the set did not already contain the specified element. 
 * @param {number} val
 * @return {boolean}
 */
RandomizedSet.prototype.insert = function(val) {
    if (this.map.get(val) !== undefined) return false;
    this.set.push(val);
    this.map.set(val, this.set.length-1);
    return true;
};

/**
 * Removes a value from the set. Returns true if the set contained the specified element. 
 * @param {number} val
 * @return {boolean}
 */
RandomizedSet.prototype.remove = function(val) {
    if (this.map.get(val) === undefined) return false;
    let last = this.set[this.set.length-1];
    this.set.splice(this.map.get(val), 1, last);
    this.set.pop();
    this.map.set(last, this.map.get(val));
    this.map.delete(val);
    return true;
};

/**
 * Get a random element from the set.
 * @return {number}
 */
RandomizedSet.prototype.getRandom = function() {
    let length = this.set.length;
    let index = Math.floor(Math.random() * Math.floor(length));
    return this.set[index];
};

/** 
 * Your RandomizedSet object will be instantiated and called as such:
 * var obj = new RandomizedSet()
 * var param_1 = obj.insert(val)
 * var param_2 = obj.remove(val)
 * var param_3 = obj.getRandom()
 */

小结

小结一下吧,这个方法好么?当然好啦,时间复杂度刷刷降为O(1)。那这个方法不好么?当然也不好,空间复杂度上去了,因为我们需要this.map来存储数据,而且Map是ES6的方法,为了实现ES6中的set,使用另一个ES6的方法,这显得不是很妥当。不过这道题给我们提供了一个空间换时间的方法,还是很值得和大家分享的。最后,祝大家前端学习一切顺利。

关注我的公众号:前端三剑客

661e215f3bf22a759af7317f91daa80f.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值