O(1) 时间插入、删除和获取随机元素

数组 哈希表

题目描述

实现一个支持如下操作的数据结构:

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

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

思路分析

我们需要支持 O(1) 的插入、删除和随机访问。常规的数组支持随机访问和插入,但删除会导致元素移动,无法满足 O(1)。哈希表支持 O(1) 的插入和删除,但不支持随机访问。

因此,结合两者:

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

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

示例代码


/**
 * 随机集合类,支持 O(1) 时间的插入、删除和获取随机元素。
 */
export class RandomizedSet {
  /**
   * 存储元素的数组。
   */
  private nums: number[];

  /**
   * 存储元素值到其在数组中索引的映射。
   */
  private map: Map<number, number>;

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

  /**
   * 插入一个元素,如果元素已存在则返回 false,否则插入并返回 true。
   * @param val 要插入的元素
   * @returns 是否插入成功
   */
  insert(val: number): boolean {
    // 如果元素已存在,插入失败
    if (this.map.has(val)) {
      return false;
    }
    // 记录元素值和其索引
    this.map.set(val, this.nums.length);
    // 将元素添加到数组末尾
    this.nums.push(val);
    return true;
  }

  /**
   * 删除一个元素,如果元素不存在则返回 false,否则删除并返回 true。
   * @param val 要删除的元素
   * @returns 是否删除成功
   */
  remove(val: number): boolean {
    // 如果元素不存在,删除失败
    if (!this.map.has(val)) {
      return false;
    }

    // 获取要删除元素的索引
    const removeIndex = this.map.get(val)!;
    // 获取数组最后一个元素
    const lastNum = this.nums[this.nums.length - 1];
    // 用最后一个元素覆盖要删除的位置
    this.nums[removeIndex] = lastNum;
    // 更新最后一个元素在 map 中的索引
    this.map.set(lastNum, removeIndex);

    // 移除数组最后一个元素
    this.nums.pop();
    // 从 map 中删除该元素
    this.map.delete(val);
    return true;
  }

  /**
   * 获取一个随机元素。
   * @returns 随机元素
   */
  getRandom(): number {
    // 生成随机索引
    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(2)true[1, 2]{1: 0, 2: 1}
remove(1)true[2]{2: 0}
getRandom()2[2]{2: 0}

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