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

【LeetCode 每日一题】2349. 设计数字容器系统

Problem: 2349. 设计数字容器系统

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度
    • 空间复杂度

整体思路

这个 NumberContainers 类的设计目标是实现一个特殊的数据容器,它需要支持两种核心操作:

  1. change(index, number):在指定的 index 处放置一个 number。这个操作可能会覆盖该索引上原有的数字。
  2. find(number):找出存储着特定 number 的所有索引中,最小的那个索引。

为了高效地实现这两个操作,该类巧妙地使用了两种 HashMap 结合一种 TreeSet 的数据结构组合。

  1. 数据结构的选择与职责

    • Map<Integer, Integer> indexToValue: 这个 HashMap 负责直接映射。它的键是 index,值是在该索引处存储的 number
      • 职责:提供 O(1) 平均时间复杂度的能力来查询任意 index 上的值。这在 change 操作中用于查找需要被替换的旧值。
    • Map<Integer, TreeSet<Integer>> valueToIndices: 这是整个设计的核心。它是一个嵌套的数据结构。
      • 外层 HashMap:键是 number,值是一个 TreeSet。它负责将一个数字与所有存储它的索引关联起来。
      • 内层 TreeSetTreeSet 是一个基于红黑树实现的、自动保持元素有序的集合。
      • 职责
        1. 高效地存储一个数字出现的所有索引。
        2. 自动对这些索引进行升序排序
        3. 提供 O(log N) 时间复杂度的 addremove 操作。
        4. 提供 O(1) 时间复杂度的 first() 操作,以快速获取最小的索引,这正是 find 操作所需要的。
  2. change(index, number) 操作的逻辑

    • 处理旧值:首先,需要检查 index 处之前是否已经有值。通过 indexToValue.get(index) 查询。
      • 如果存在一个 oldValue,就必须从 valueToIndicesoldValue 对应的 TreeSet 里移除 index。这是为了保持数据的一致性,因为 index 处的值即将改变。
    • 更新新值
      • indexToValue 中,更新或插入 (index, number) 的映射。
      • valueToIndices 中,首先要找到 number 对应的 TreeSet
        • computeIfAbsent(number, k -> new TreeSet<>()) 是一个优雅的写法:如果 numberTreeSet 不存在,就创建一个新的;如果存在,就返回现有的。
        • 然后,将 index 添加到这个 TreeSet 中。TreeSet 会自动将其放在正确的位置以维持排序。
  3. find(number) 操作的逻辑

    • 查找索引集合:直接从 valueToIndices 中获取 number 对应的 TreeSet
    • 处理不存在的情况:如果获取到的 TreeSetnull(表示这个 number 从未被存储过)或者 isEmpty()(表示这个 number 曾经存在但现在所有位置都被替换了),则根据题目要求返回 -1
    • 返回最小索引:如果 TreeSet 存在且不为空,调用其 first() 方法。由于 TreeSet 是有序的,first() 能在 O(1) 时间内返回其中最小的元素,即最小的索引。

完整代码

import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;class NumberContainers {// 映射1:索引 → 当前存储的数字。// 提供 O(1) 时间复杂度的索引值查询。private final Map<Integer, Integer> indexToValue = new HashMap<>();// 映射2:数字 → 所有包含该数字的索引集合(使用 TreeSet 自动升序排列)。// Key: number, Value: a sorted set of indices where this number is located.private final Map<Integer, TreeSet<Integer>> valueToIndices = new HashMap<>();public NumberContainers() {// 构造函数,此处无需特殊初始化}/*** 在指定索引处更改或放置一个数字。* @param index  要操作的索引* @param number 要放置的数字*/public void change(int index, int number) {// 步骤 1: 处理可能存在的旧值// 从 indexToValue 中查找该索引之前存储的值Integer oldValue = indexToValue.get(index);if (oldValue != null) {// 如果存在旧值,则必须从旧值对应的索引集合中移除当前索引valueToIndices.get(oldValue).remove(index);}// 步骤 2: 更新两个映射以反映新值// 更新 index -> number 的映射indexToValue.put(index, number);// 更新 number -> {..., index, ...} 的映射// computeIfAbsent: 如果 number 还没有对应的 TreeSet,则创建一个新的// .add(index): 将当前索引添加到该 TreeSet 中,它会自动保持排序valueToIndices.computeIfAbsent(number, k -> new TreeSet<>()).add(index);}/*** 查找存储指定数字的最小索引。* @param number 要查找的数字* @return 最小的索引;如果该数字不存在,则返回 -1。*/public int find(int number) {// 从 valueToIndices 中获取该数字对应的所有索引TreeSet<Integer> indices = valueToIndices.get(number);// 检查两种“不存在”的情况:// 1. 该数字从未被 change 过 (indices == null)// 2. 该数字曾经存在,但所有位置都已被其他数字覆盖 (indices.isEmpty())if (indices == null || indices.isEmpty()) {return -1;} else {// TreeSet.first() 可以在 O(1) 时间内返回集合中最小的元素return indices.first();}}
}/*** Your NumberContainers object will be instantiated and called as such:* NumberContainers obj = new NumberContainers();* obj.change(index,number);* int param_2 = obj.find(number);*/

时空复杂度

  • Nchange 操作的总调用次数。
  • Mfind 操作的总调用次数。
  • 在任意时刻,设某个数字 k 对应的索引数量为 C_k

时间复杂度

  1. change(index, number)

    • indexToValue.get(index): O(1) 平均。
    • valueToIndices.get(oldValue): O(1) 平均。
    • TreeSet.remove(index): O(log C_{oldValue})。C_{oldValue} 是旧值对应的索引数。
    • indexToValue.put(index, number): O(1) 平均。
    • valueToIndices.computeIfAbsent(number, ...): O(1) 平均。
    • TreeSet.add(index): O(log C_{number})。C_{number} 是新值对应的索引数。
    • 综合change 操作的时间复杂度主要由 TreeSet 的操作决定,为 O(log K),其中 K 是与操作相关的数字(oldValuenumber)出现的次数。
  2. find(number)

    • valueToIndices.get(number): O(1) 平均。
    • TreeSet.isEmpty(): O(1)。
    • TreeSet.first(): O(1)。
    • 综合find 操作的时间复杂度为 O(1) 平均。

空间复杂度

  1. 主要存储开销:由两个 Map 决定。
  2. indexToValue: 存储了所有被 change 操作接触过的唯一 index。如果 change 操作涉及 U 个不同的索引,则其空间为 O(U)
  3. valueToIndices:
    • 外层 Map 的键是所有出现过的唯一 number
    • 内层的 TreeSet 存储了这些 number 对应的所有 index
    • 所有 TreeSet 中元素的总数,等于 indexToValue 中元素的总数,即 U
    • 因此,valueToIndices 的总空间复杂度也是 O(U)

综合分析
类的总空间复杂度为 O(U),其中 U 是被 change 操作触及过的唯一索引的总数。

http://www.dtcms.com/a/395175.html

相关文章:

  • i.MX6ULL移植内核6.6(一)修改网络驱动和LCD驱动
  • vue-router(vue 路由)基本使用指南(一)
  • 酒店台账报表:押金原路退回与收支自动化指南-东方仙盟自动化
  • ⸢ 伍-Ⅰ⸥ ⤳ 默认安全治理实践:软件供应链安全治理
  • LeetCode 刷题【88. 合并两个有序数组、89. 格雷编码】
  • 《青花》歌曲,使用3D表现出意境
  • 【Linux篇】- Ext系列文件系统
  • C++符号表
  • Pythoner 的Flask项目实践-带折叠菜单的响应式多页面应用签到墙(源码)
  • 异常:java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
  • Java-131 深入浅出 MySQL MyCat 深入解析 schema.xml 配置详解:逻辑库、逻辑表、数据节点全攻略
  • Vmware CentOS Docker Daemon配置代理
  • 【JAVA】java多态
  • 【AI分析进行时】大模型显存需求估算与国内开源模型实践指南
  • C++基础:(一)C++入门知识介绍(上)
  • Python项目的多语言翻译babel
  • python flask框架详解
  • 基于STM32单片机的家庭医护血氧体温血压吃药监测APP系统
  • 整合亮数据Bright Data与Dify构建自动化分析系统
  • Browser-Use+cpolar:企业网页操作自动化的无界解决方案
  • 深入理解 Elasticsearch:核心原理、性能优化与高频面试题解析
  • 【C++】Lambda表达式参数问题
  • 数学金融方向要额外学什么课?这个专业对编程和建模能力要求高吗?
  • 第二部分:VTK核心类详解(第54章 vtkVariantArray变体数组类)
  • 【2025最新】ArcGIS for JS点聚合功能实现
  • Leecode hot100 - 114. 二叉树展开为链表 基础方法到提高方法
  • 把 iOS 混淆纳入自动化测试与 CICD 从构建、回归到灰度的工程化实战
  • 初识Redis:解锁高性能缓存的魔法钥匙
  • 基于传递矩阵法计算多层结构声表面波声速
  • 中间件和分类