当前位置: 首页 > article >正文

LeetCode题练习与总结:O(1) 时间插入、删除和获取随机元素--380

一、题目描述

实现RandomizedSet 类:

  • RandomizedSet() 初始化 RandomizedSet 对象
  • bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。
  • bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false 。
  • int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。

你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1) 。

示例:

输入
["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"]
[[], [1], [2], [2], [], [1], [2], []]
输出
[null, true, false, true, 2, true, false, 2]

解释
RandomizedSet randomizedSet = new RandomizedSet();
randomizedSet.insert(1); // 向集合中插入 1 。返回 true 表示 1 被成功地插入。
randomizedSet.remove(2); // 返回 false ,表示集合中不存在 2 。
randomizedSet.insert(2); // 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。
randomizedSet.getRandom(); // getRandom 应随机返回 1 或 2 。
randomizedSet.remove(1); // 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。
randomizedSet.insert(2); // 2 已在集合中,所以返回 false 。
randomizedSet.getRandom(); // 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。

提示:

  • -2^31 <= val <= 2^31 - 1
  • 最多调用 insertremove 和 getRandom 函数 2 * 10^5 次
  • 在调用 getRandom 方法时,数据结构中 至少存在一个 元素。

二、解题思路

为了实现 RandomizedSet 类,我们需要保证 insertremove 和 getRandom 操作的平均时间复杂度为 O(1)。为了达到这个目标,我们可以使用以下数据结构:

  1. 动态数组(ArrayList):用于存储集合中的元素,这样可以在 O(1) 时间内通过索引访问到任何元素。
  2. 哈希表(HashMap):用于存储每个元素值与其在动态数组中索引的映射,这样可以在 O(1) 时间内检查元素是否存在以及获取其索引。

以下是解题思路:

  • 插入操作(insert)

    • 使用哈希表检查元素是否已存在。
    • 如果不存在,将其添加到动态数组的末尾,并在哈希表中记录其索引。
  • 删除操作(remove)

    • 使用哈希表检查元素是否存在以及其索引。
    • 如果存在,将数组中该元素与最后一个元素交换位置,然后删除数组末尾的元素,并更新哈希表中的索引映射。
  • 获取随机元素(getRandom)

    • 由于数组中的元素是连续存储的,我们可以通过生成一个随机索引来直接访问数组中的随机元素。

三、具体代码

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

public class RandomizedSet {
    private Map<Integer, Integer> indexMap;
    private List<Integer> nums;
    private Random rand;

    public RandomizedSet() {
        indexMap = new HashMap<>();
        nums = new ArrayList<>();
        rand = new Random();
    }
    
    public boolean insert(int val) {
        if (indexMap.containsKey(val)) {
            return false;
        }
        indexMap.put(val, nums.size());
        nums.add(val);
        return true;
    }
    
    public boolean remove(int val) {
        if (!indexMap.containsKey(val)) {
            return false;
        }
        int lastElement = nums.get(nums.size() - 1);
        int idxToRemove = indexMap.get(val);
        nums.set(idxToRemove, lastElement);
        indexMap.put(lastElement, idxToRemove);
        nums.remove(nums.size() - 1);
        indexMap.remove(val);
        return true;
    }
    
    public int getRandom() {
        return nums.get(rand.nextInt(nums.size()));
    }
}

这段代码实现了 RandomizedSet 类,满足了题目要求的 O(1) 时间复杂度。每次插入和删除操作都会更新哈希表和动态数组,而获取随机元素则直接从动态数组中随机选择一个索引。

四、时间复杂度和空间复杂度

1. 时间复杂度
  • 构造函数 RandomizedSet():

    • 初始化 indexMap 和 nums 的时间复杂度是 O(1)。
    • 初始化 rand 的时间复杂度也是 O(1)。
    • 因此,总的时间复杂度是 O(1)。
  • 方法 insert(int val):

    • 检查元素是否存在于哈希表中,时间复杂度是 O(1)。
    • 如果元素不存在,则添加到动态数组和哈希表中,添加到数组和哈希表的操作时间复杂度都是 O(1)。
    • 因此,总的时间复杂度是 O(1)。
  • 方法 remove(int val):

    • 检查元素是否存在于哈希表中,时间复杂度是 O(1)。
    • 如果元素存在,则需要交换待删除元素与数组末尾元素的位置,并更新哈希表,这些操作的时间复杂度都是 O(1)。
    • 删除数组末尾元素,时间复杂度是 O(1)。
    • 从哈希表中删除元素,时间复杂度也是 O(1)。
    • 因此,总的时间复杂度是 O(1)。
  • 方法 getRandom():

    • 生成随机索引,时间复杂度是 O(1)。
    • 通过索引访问数组元素,时间复杂度是 O(1)。
    • 因此,总的时间复杂度是 O(1)。
2. 空间复杂度
  • 构造函数 RandomizedSet():

    • indexMap 和 nums 都是动态数据结构,它们的空间复杂度取决于集合中元素的数量。
    • rand 是一个轻量级的对象,它占用的空间可以认为是 O(1)。
    • 因此,总的空间复杂度是 O(n),其中 n 是集合中元素的数量。
  • 方法 insert(int val):

    • 没有使用额外的空间,除了在 nums 和 indexMap 中存储元素。
    • 因此,空间复杂度是 O(1)。
  • 方法 remove(int val):

    • 同样,没有使用额外的空间。
    • 因此,空间复杂度是 O(1)。
  • 方法 getRandom():

    • 没有使用额外的空间。
    • 因此,空间复杂度是 O(1)。
3. 总结
  • 时间复杂度:

    • 构造函数:O(1)
    • insert(int val):O(1)
    • remove(int val):O(1)
    • getRandom():O(1)
  • 空间复杂度:

    • 构造函数:O(n)
    • insert(int val):O(1)
    • remove(int val):O(1)
    • getRandom():O(1)

其中 n 是集合中元素的数量。注意,虽然单个操作的空间复杂度是 O(1),但是随着集合中元素数量的增加,整体空间需求会线性增长。

五、总结知识点

  • 类定义:

    • public class RandomizedSet 定义了一个公共类 RandomizedSet
  • 成员变量:

    • private Map<Integer, Integer> indexMap; 定义了一个私有成员变量 indexMap,用于存储元素值与其在动态数组中的索引的映射。
    • private List<Integer> nums; 定义了一个私有成员变量 nums,用于存储集合中的元素。
    • private Random rand; 定义了一个私有成员变量 rand,用于生成随机数。
  • 构造函数:

    • public RandomizedSet() 是类的构造函数,用于初始化成员变量。
  • 哈希表(HashMap):

    • indexMap 是一个 HashMap,它提供了 O(1) 时间复杂度的查找、插入和删除操作。
  • 动态数组(ArrayList):

    • nums 是一个 ArrayList,它支持动态数组操作,如添加、删除和通过索引访问元素。
  • 随机数生成(Random):

    • rand 是 Random 类的一个实例,用于生成随机数。
  • 方法定义:

    • public boolean insert(int val) 定义了一个公共方法,用于向集合中插入一个元素。
    • public boolean remove(int val) 定义了一个公共方法,用于从集合中移除一个元素。
    • public int getRandom() 定义了一个公共方法,用于从集合中随机获取一个元素。
  • 条件判断:

    • if (indexMap.containsKey(val)) 用于检查元素是否已经在集合中。
    • if (!indexMap.containsKey(val)) 用于检查元素是否不在集合中。
  • 哈希表操作:

    • indexMap.put(val, nums.size()); 将元素值和其在数组中的索引插入哈希表。
    • indexMap.remove(val); 从哈希表中移除一个元素。
  • 数组操作:

    • nums.add(val); 在数组的末尾添加一个元素。
    • nums.set(idxToRemove, lastElement); 通过索引设置数组中的元素。
    • nums.remove(nums.size() - 1); 移除数组末尾的元素。
  • 随机数的使用:

    • rand.nextInt(nums.size()); 生成一个介于 0(包含)和 nums.size()(不包含)之间的随机整数,用于随机访问数组元素。
  • 返回值:

    • return true; 和 return false; 分别用于表示操作成功或失败。
    • return nums.get(rand.nextInt(nums.size())); 返回一个随机选择的数组元素。

以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。


http://www.kler.cn/a/382852.html

相关文章:

  • CesiumJS 案例 P15:检测标记、鼠标点击移动标记、鼠标拖动标记
  • 13-鸿蒙开发中的综合实战:华为登录界面
  • 猎板PCB2到10层数的科技进阶与应用解析
  • 修改elementUI等UI组件样式的5种方法总结,哪些情况需要使用/deep/, :deep()等方式来穿透方法大全
  • 嵌入式Linux入门具备:C语言基础与基本驱动学习(2):Linux GIibc IO基础
  • 【测试工具】Fastbot 客户端稳定性测试
  • xshell连接不上linux的原因
  • 【EMNLP2024】阿里云人工智能平台 PAI 多篇论文入选 EMNLP2024
  • 架构评估的方法
  • CentOS8.4 部署 k8s 1.31.2
  • XPath 实例
  • 【论文复现】KAN卷积:医学图像分割新前沿
  • 初级图像处理工具
  • 【C++刷题】力扣-#717-1比特与2比特字符
  • 【JavaScript】axios 二次封装拦截器(接口、实例、全局)
  • STM32HAL-最简单的长、短、多击按键框架(多按键)
  • hive切换表底层文件类型以及分隔符
  • I.MX6U 裸机开发2. 芯片简介、汇编基础及GPIO操作准备工作
  • 动态切换策略模式在Spring Boot项目中的实践与应用
  • 鸿蒙开发:自定义一个车牌省份简称键盘
  • Java 中的 transient 关键字:深入解析与实战
  • WebGUI之Gradio:Gradio 5的简介、安装和使用方法、案例应用之详细攻略
  • Redis - List 列表
  • 使用Golang实现开发中常用的【并发设计模式】
  • 【系统集成项目管理工程师教程】第12章 执行过程组
  • 关于基于AGI和大模型技术下养老服务高质量发展解决方案项目,以及实现代码过程实战