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

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

✨核心逻辑

本题要求所有操作的时间复杂度均为 O(1),因此采用了 变长数组 + 哈希表 的组合数据结构:

  1. 数组(List)负责常数级随机访问:数组支持通过下标 O(1) 的访问,正好可以满足 getRandom() 返回随机元素的需求。
  2. 哈希表(Map)负责常数级查找:哈希表存储了元素值 val 到它在数组中对应下标 index 的映射关系,使得 insertremove 操作可以快速定位元素。
  3. 巧妙处理删除(交换法):常规删除数组中间元素会导致后续元素平移,时间复杂度为 O(N)。为了做到 O(1) 删除,我们将待删除元素数组末尾元素进行交换,然后直接删除末尾元素,并同步更新哈希表内的索引映射,完美避开了数组平移的开销。

🔥代码实现(含详细变量注释)

class RandomizedSet {
    // list:变长数组,用于存储集合中的元素,保证 getRandom() 能够在 O(1) 时间内随机访问
    List<Integer> list = new ArrayList<>();
    // map:哈希表,用于存储元素值 val 到其在 list 中对应下标 index 的映射,保证查找和删除是 O(1)
    Map<Integer, Integer> map = new HashMap<>();
    // rand:随机数生成器,用于在 getRandom() 时生成随机的数组下标
    Random rand = new Random();

    public RandomizedSet() {
    }
    
    public boolean insert(int val) {
        // 如果 map 中已经存在该元素,说明插入失败,直接返回 false
        if (map.containsKey(val)) {
            return false;
        }
        // 如果不存在,获取当前 list 的长度(即新元素应该存放的索引)
        int size = list.size();
        // 将新元素追加到数组的末尾
        list.add(val);
        // 在哈希表中记录元素和它的索引位置
        map.put(val, size);
        return true;
    }
    
    public boolean remove(int val) {
        if (map.containsKey(val)) {
            // 核心技巧:不能直接对 list 按索引 remove,否则后面的元素会向前平移,导致时间复杂度变为 O(N)
            
            // 获取数组末尾的那个元素值
            int last = list.get(list.size() - 1);
            // 获取当前要删除的元素 val 在数组中的索引
            int index = map.get(val);

            // 1. 在哈希表中,将末尾元素 last 的索引更新为当前要删除元素的索引 index
            map.put(last, index);
            // 2. 在数组中,将末尾元素 last 直接覆盖到当前要删除元素的索引位置
            list.set(index, last);
            
            // 3. 删除数组的最后一个元素(原本的 val 已经被覆盖,现在移走的是 原本 last 的冗余副本)
            list.remove(list.size() - 1);
            // 4. 从哈希表中移除元素 val 的记录
            map.remove(val);
            return true;
        } else {
            // 如果 map 中不存在该元素,说明删除失败,返回 false
            return false;
        }
    }
    
    public int getRandom() {
        // rand.nextInt(list.size()) 生成一个 [0, list.size()) 范围内的随机下标
        // 然后直接从数组中取出该下标的元素返回
        return list.get(rand.nextInt(list.size()));
    }
}
  • ⏱️复杂度分析
    • 时间复杂度:O(1)。insert() 操作是数组末尾追加和哈希表更新;remove() 操作是数组元素交换和哈希表更新;getRandom() 操作直接通过随机下标访问数组。所有方法均只有常数次操作。

    • 空间复杂度:O(N),其中 N 是集合中元素的数量。底层数据结构使用了一个变长数组和一个哈希表,用于存储所有插入的元素。

🔍总结一下:

这类实现类的题:都是基于底层的数据结构来实现的,选对数据结构,进行操作,实现数据的一致性就行了