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

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

🔍 题目链接

🎯 题目核心要求

  1. insert(val):元素不存在则插入,返回 true;存在返回 false,要求 O(1)
  2. remove(val):元素存在则删除,返回 true;不存在返回 false,要求 O(1)
  3. getRandom():等概率随机返回集合内任意元素,要求 O(1)

💡 解题思路

单纯 HashMap 无法按下标随机取值;普通数组删除中间元素需要整体移位,耗时 O(n)。

数组 + HashMap 组合方案,实现三大操作全部 O(1) ✨

  1. 数组 nums:存放真实元素,依靠下标实现随机取值,保证抽取概率均等
  2. HashMap map:key=存入的数值,value=数值在数组中的下标,快速判重、快速定位位置
  3. 🧩 删除核心优化技巧:
    • 获取待删元素下标 loc、数组最后有效下标 idx
    • loc != idx:把数组末尾元素覆盖到 loc 位置,同步修改 map 里末尾元素对应的下标
    • 有效下标 idx 自减,逻辑丢弃原数组末尾(待删元素已被覆盖失效)
    • 全程无数组移位,删除操作 O(1)

✅ 完整可直接提交代码

java 复制代码
class RandomizedSet {
    // 定长数组存放元素,题目数据范围不超过200000
    static int[] nums = new int[200010];
    Random random = new Random();
    // key:存储的值,value:值在nums数组中的下标
    Map<Integer, Integer> map;
    // 当前数组最后一个有效元素下标,初始-1代表集合为空
    int idx = -1;

    public RandomizedSet() {
        map = new HashMap<>();
    }
    
    public boolean insert(int val) {
        // 已存在直接返回false
        if(map.containsKey(val)) return false;
        idx++;
        nums[idx] = val;
        map.put(val, idx);
        return true;
    }
    
    public boolean remove(int val) {
        // 不存在直接返回false
        if(!map.containsKey(val)) return false;
        // 取出待删除元素下标,并从map移除该key
        int loc = map.remove(val);
        // 如果待删元素不是数组最后一位,交换末尾元素到loc位置
        if(loc != idx){
            int lastVal = nums[idx];
            nums[loc] = lastVal;
            map.put(lastVal, loc);
        }
        // 有效下标前移一位,逻辑删除末尾数据
        idx--;
        return true;
    }
    
    public int getRandom() {
        // 随机取 [0, idx] 下标,每个元素等概率抽取
        return nums[random.nextInt(idx + 1)];
    }
}

🔍 关键细节笔记

1. 📦 成员变量说明

  • nums:静态数组,提前分配足够空间,省去动态扩容开销;存储所有有效数字
  • map:哈希映射,记录数值对应数组下标,O(1) 判断存在 + O(1) 定位下标
  • idx 有效尾下标:集合空时 idx=-1,集合总元素数量 = idx + 1
  • Random:生成均匀随机下标,保障 getRandom 每个元素抽取概率完全相等

2. ➕ insert 执行流程

  1. 哈希表判断 val 是否存在,重复直接返回 false
  2. 有效下标 idx 自增 1
  3. 数组存入 val,哈希表记录 val 对应的新下标

3. 🗑️ remove 核心交换逻辑(重难点)

示例:数组 [1,2,3,4],idx=3,删除2(loc=1)

  1. map 删除 key=2,拿到位置 loc=1
  2. 末尾值 4 覆盖 nums1,数组变为 [1,4,3,4]
  3. map 更新 key=4 的下标为 1
  4. idx 减为 2,有效数组仅保留前三位 [1,4,3]
    优势:无需数组整体前移,删除操作稳定 O(1)

边界:如果待删元素刚好是数组最后一个元素(loc == idx),无需覆盖、更新map,直接 idx-- 即可

4. 🎲 getRandom 等概率原理

random.nextInt(n) 均匀返回 [0, n-1] 的随机整数

当前有效元素总数 total = idx + 1,调用 nextInt(idx+1) 得到 0 ~ idx 的随机下标,数组每个下标对应唯一元素,抽取概率完全均等

⏱️ 复杂度分析

  • 时间复杂度:insert / remove / getRandom 均为 O(1)
  • 空间复杂度:O(N),N 为集合内存储元素数量

⚠️ 高频易错点总结

  1. remove 中必须先执行 map.remove(val) 获取 loc,再处理末尾元素交换
  2. 交换末尾元素后,一定要更新 map 中末尾值对应的下标,否则下标映射错乱
  3. getRandom 随机数范围是 idx+1,不能只写 idx,否则取不到最后一个有效元素
  4. 静态数组 nums 全局共用不影响多组用例:每个实例的 idx 独立,旧无效数据不会被访问到