O(1) 时间插入、删除和获取随机元素-支持重复元素

数组 哈希表

题目描述

实现一个支持重复元素的集合,要求支持如下操作:

  • 插入元素
  • 删除元素(只删除一个)
  • 随机返回一个元素

所有操作要求平均时间复杂度为 O(1)

思路分析

由于集合允许重复元素,单纯用哈希表或数组都无法高效支持所有操作。

  • 用数组 nums 存储所有元素,支持 O(1) 随机访问。
  • 用哈希表 map 记录每个元素在数组中的所有索引(用 Set 存储),支持 O(1) 查找和删除。
  • 删除时,将待删除元素与数组最后一个元素交换,然后弹出最后一个元素,并更新哈希表中相关索引。

这样,所有操作都能在常数时间内完成。

示例代码


/**
 * 支持重复元素的随机集合
 */
export class RandomizedSet {
  /** 存储所有元素 */
  private nums: number[];
  /** 记录每个元素在数组中的所有索引 */
  private map: Map<number, Set<number>>;

  /** 初始化数据结构 */
  constructor() {
    this.nums = [];
    this.map = new Map();
  }

  /**
   * 插入元素 val
   * @param val 要插入的元素
   * @returns 是否首次插入该元素
   */
  insert(val: number): boolean {
    const has = this.map.has(val);
    if (!has) {
      this.map.set(val, new Set());
    }
    // 将元素添加到数组末尾,并记录其索引
    this.nums.push(val);
    this.map.get(val)!.add(this.nums.length - 1);
    return !has;
  }

  /**
   * 删除一个 val(只删一个)
   * @param val 要删除的元素
   * @returns 是否删除成功
   */
  remove(val: number): boolean {
    if (!this.map.has(val)) {
      return false;
    }
    // 取出 val 的一个索引
    const indexSet = this.map.get(val)!;
    const removeIndexResult = indexSet.values().next();
    if (removeIndexResult.done) {
      // 理论上不会发生,防御性处理
      return false;
    }
    const removeIndex = removeIndexResult.value as number;
    const lastIndex = this.nums.length - 1;
    const lastNum = this.nums[lastIndex];

    // 用最后一个元素覆盖要删除的位置
    this.nums[removeIndex] = lastNum;

    // 更新 lastNum 的索引集合
    if (this.map.has(lastNum)) {
      const lastNumSet = this.map.get(lastNum)!;
      lastNumSet.add(removeIndex);
      lastNumSet.delete(lastIndex);
    }

    // 移除 val 的该索引
    indexSet.delete(removeIndex);
    if (indexSet.size === 0) {
      this.map.delete(val);
    }

    // 移除数组最后一个元素
    this.nums.pop();
    return true;
  }

  /**
   * 随机返回一个元素
   * @returns 随机元素
   */
  getRandom(): number {
    // 生成 [0, nums.length) 的随机索引
    const randomIndex = Math.floor(Math.random() * this.nums.length);
    return this.nums[randomIndex];
  }
}

复杂度分析

  • 时间复杂度:所有操作均为 O(1)
  • 空间复杂度O(n),其中 n 为元素数量。

示例演示

以一组操作为例:

操作结果数组内容哈希表内容
insert(1)true[1]{1: [0]}
insert(1)false[1, 1]{1: [0, \1]}
insert(2)true[1, 1, 2]{1: [0, 1], 2: [2]}
remove(1)true[2, 1]{1: [1], 2: [0]}
getRandom()1/2[2, 1]{1: [1], 2: [0]}

每次删除时,将被删除元素与最后一个元素交换,保证数组紧凑且哈希表索引正确。