[维护数组的长度,实现随机] 剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器

1,099 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

每日刷题 2022.08.03

题目

  • 设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构:
    • insert(val):当元素 val 不存在时返回 true ,并向集合中插入该项,否则返回 false 。
    • remove(val):当元素 val 存在时返回 true ,并从集合中移除该项,否则返回 false 。
    • getRandom:随机返回现有集合中的一项。每个元素应该有 相同的概率 被返回。

示例

输入: inputs = ["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"]
[[], [1], [2], [2], [], [1], [2], []]
输出: [null, true, false, true, 2, true, false, 2]
解释:
RandomizedSet randomSet = new RandomizedSet();  // 初始化一个空的集合
randomSet.insert(1); // 向集合中插入 1 , 返回 true 表示 1 被成功地插入
randomSet.remove(2); // 返回 false,表示集合中不存在 2 
randomSet.insert(2); // 向集合中插入 2 返回 true ,集合现在包含 [1,2] 
randomSet.getRandom(); // getRandom 应随机返回 1 或 2  
randomSet.remove(1); // 从集合中移除 1 返回 true 。集合现在包含 [2] 
randomSet.insert(2); // 2 已在集合中,所以返回 false 
randomSet.getRandom(); // 由于 2 是集合中唯一的数字,getRandom 总是返回 2 

解题思路

  • 分析题目含义:需要在时间复杂度O(1)的情况下实现:插入、删除、随机获取集合中的一项,这三种操作。
  • 首先想到的是使用map集合,但是好像map集合没有办法在o(1)时间内获取集合中的一项,因此放弃了使用map集合。后续想到示例中所给的数据在集合中都只能出现一次,那么可以使用set集合来进行存储,最后随机获取的时候,可以将set集合转换成数组,再通过随机数组的下标来获取元素值。
  • 使用set的做法可行,但是时间复杂度较高。思考可能是因为将集合set转换成数组,每次都需要将数组全部遍历一遍,这样的话时间复杂度就不再是o(1)而是o(n)

巧妙的解法

  • 使用map集合 + 数组,最开始也想到了使用map,但是止步于随机获取集合中的一项这个条件,的确单靠map集合是无法实现的,因此后面考虑到了结合数组arr来实现.
  • 使用map集合key记录当前的值,value记录当前值在数组中所对应的下标idx
  • 重点:如何处理随机的呢?如果要删除一个元素del,那么就将当前arr数组中最后一个元素换到del的位置上,删除掉原本的del的信息即可。下次再随机数组长度内的下标就可以o(1)的时间复杂度获取到元素
  • 始终维护arr数组的长度,删除arr中的一个元素,就将arr尾部的元素和其替换。需要注意,如果当前需要删除的元素就是arr数组尾部的元素,那么就不需要修改信息,直接将其删除就可以了。

AC代码

/**
 * Initialize your data structure here.
 */
var RandomizedSet = function() {
  // 新的做法,使用map和数组结合
  // map集合中记录当前的值和下标
  this.map = new Map();
  // 存放值
  this.arr = [];
  this.idx = -1;
};

/**
 * 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) {
  // console.log('insert::', this.map)
  if(this.map.has(val)) return false;
  this.idx++;
  this.arr[this.idx] = val;
  this.map.set(val, this.idx);
  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.has(val)) return false;
  // 存在的时候
  // 删除
  const col = this.map.get(val);
  if(col === this.idx) {
    // 当前的就是最后一个元素
    this.arr.pop();
    this.map.delete(val);
  } else {
    this.map.delete(val);
    const cur = this.arr[this.idx];
    this.arr[col] = cur;
    this.map.set(cur, col);
  }
  this.idx--;
  return true;
};

/**
 * Get a random element from the set.
 * @return {number}
 */
RandomizedSet.prototype.getRandom = function() {
  // const n = this.arr.length;
  // console.log(this.arr)
  const r = Math.floor(Math.random() * (this.idx + 1));
  return this.arr[r];
};