【困难】力扣算法题解析LeetCode381:O(1) 时间插入、删除和获取随机元素 - 允许重复

LeetCode381题解:O(1)时间复杂度算法实现

关注文末推广名片,即可免费获得本题测试源码

题目来源:LeetCode 381: O(1) 时间插入、删除和获取随机元素 - 允许重复

问题抽象: 设计一个支持 重复元素 的数据结构,要求在 O(1) 平均时间复杂度 内完成以下操作:

  1. 功能定义

    • insert(val):向集合插入元素 val(允许重复);
    • remove(val):从集合中移除一个 val(若存在多个则移除一个;若不存在则忽略);
    • getRandom()等概率随机返回 集合中的一个元素(每个元素实例独立参与随机,如 [1,1,2]1 的概率为 2/3)。
  2. 操作约束

    • insertremove 平均时间复杂度 O(1)
    • getRandom 严格 O(1)(需直接通过索引访问);
    • 空间复杂度 O(n)n 为元素总数,含重复项)。
  3. 数据结构要求

    • 动态数组:存储所有元素实例(允许重复),支持 O(1) 随机访问;
    • 哈希映射:键为元素值,值为该元素在数组中的 索引集合(如 1: {0,2});
    • 索引交换技巧
      • remove 时用数组末尾元素覆盖待删位置,更新索引映射(避免移动元素);
  4. 边界处理

    • 空集合调用 getRandom → 抛出异常或返回 -1(题目指定);
    • 删除不存在的值 → 返回 false(操作无效);
    • 插入重复值 → 数组追加新元素,索引集合更新;
    • 特殊用例:
      • 插入 [1,1,2] 后,getRandom 返回 1 的概率为 2/3
      • 删除 1 后,集合为 [2,1](末尾 1 覆盖被删位置);
      • 删除最后一个元素 → 直接移除并清空索引。

输入:通过方法调用操作(val 为整数)。
输出

  • insert:返回 bool(是否成功插入,通常恒为 true);
  • remove:返回 bool(是否成功移除);
  • getRandom:返回随机元素值(整数)。

解题思路

本题要求设计一个数据结构,支持在平均 O(1) 时间内完成插入、删除和获取随机元素操作,且允许元素重复。核心挑战在于如何在 O(1) 时间内删除指定元素(尤其是中间元素)并维护随机访问能力。

核心设计
  1. 动态数组(ArrayList):存储所有元素,支持 O(1) 的随机访问(通过索引)。
  2. 哈希表(HashMap):键为元素值,值为该元素在动态数组中的所有位置索引(使用 HashSet 存储,实现 O(1) 的插入和删除)。
  3. 删除策略:删除元素时,将待删除元素与数组末尾元素交换,然后删除末尾元素,避免移动其他元素,保持 O(1) 时间复杂度。
操作细节
  • 插入(insert)
    1. 将元素添加到动态数组末尾。
    2. 在哈希表中记录该元素的新位置(即数组末尾索引)。
    3. 返回布尔值:若元素首次插入返回 true,重复插入返回 false。
  • 删除(remove)
    1. 从哈希表中获取待删除元素的位置集合,移除一个位置(任意)。
    2. 将数组末尾元素移动到待删除元素的位置。
    3. 更新原末尾元素的位置集合:移除原末尾位置,添加新位置(若移动位置非原末尾)。
    4. 删除数组末尾元素。
    5. 若元素位置集合为空,从哈希表移除该元素。
  • 获取随机元素(getRandom):生成随机索引,返回数组中对应位置的元素。

代码实现(Java版)🔥点击下载源码

class RandomizedCollection {
    private List<Integer> list; // 存储元素的动态数组
    private Map<Integer, Set<Integer>> indexMap; // 元素值到位置索引集合的映射
    private Random rand; // 随机数生成器

    public RandomizedCollection() {
        list = new ArrayList<>();
        indexMap = new HashMap<>();
        rand = new Random();
    }
    
    public boolean insert(int val) {
        list.add(val); // 元素添加到数组末尾
        Set<Integer> positions = indexMap.getOrDefault(val, new HashSet<>());
        positions.add(list.size() - 1); // 记录新位置
        indexMap.put(val, positions);
        return positions.size() == 1; // 首次插入返回true,重复插入返回false
    }
    
    public boolean remove(int val) {
        if (!indexMap.containsKey(val) || indexMap.get(val).isEmpty()) {
            return false; // 元素不存在,删除失败
        }
        Set<Integer> positions = indexMap.get(val);
        int pos = positions.iterator().next(); // 获取任意一个位置
        positions.remove(pos); // 从集合中移除该位置
        
        int lastVal = list.get(list.size() - 1); // 获取末尾元素
        list.set(pos, lastVal); // 将末尾元素移动到待删除位置
        
        Set<Integer> lastSet = indexMap.get(lastVal);
        lastSet.remove(list.size() - 1); // 移除原末尾位置
        if (pos < list.size() - 1) { // 若移动位置非原末尾,则添加新位置
            lastSet.add(pos);
        }
        
        list.remove(list.size() - 1); // 删除末尾元素
        if (positions.isEmpty()) {
            indexMap.remove(val); // 若位置集合为空,移除该元素
        }
        return true;
    }
    
    public int getRandom() {
        return list.get(rand.nextInt(list.size())); // 随机索引访问
    }
}

代码说明

  1. 数据结构

    • list:动态数组,存储所有元素,支持 O(1) 随机访问。
    • indexMap:哈希表,键为元素值,值为该元素在 list 中的位置集合(HashSet 实现 O(1) 的增删)。
    • rand:随机数生成器,用于生成随机索引。
  2. 插入操作(insert)

    • 将元素添加到 list 末尾(O(1))。
    • indexMap 中记录新位置(O(1))。
    • 返回值:若元素首次插入返回 true,重复插入返回 false
  3. 删除操作(remove)

    • 检查元素是否存在(O(1))。
    • 获取并移除该元素的任意位置(O(1))。
    • 将末尾元素移动到待删除位置(O(1))。
    • 更新原末尾元素的位置集合(O(1))。
    • 删除末尾元素(O(1))。
    • 若元素无其他位置,从 indexMap 中移除(O(1))。
  4. 获取随机元素(getRandom)

    • 生成随机索引(O(1))。
    • 返回 list 中对应位置的元素(O(1))。

提交详情(执行用时、内存消耗)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

达文汐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值