恶意网站的防治wordpress博客模板安装失败
Problem: 2349. 设计数字容器系统
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度
- 空间复杂度
整体思路
这个 NumberContainers 类的设计目标是实现一个特殊的数据容器,它需要支持两种核心操作:
change(index, number):在指定的index处放置一个number。这个操作可能会覆盖该索引上原有的数字。find(number):找出存储着特定number的所有索引中,最小的那个索引。
为了高效地实现这两个操作,该类巧妙地使用了两种 HashMap 结合一种 TreeSet 的数据结构组合。
-
数据结构的选择与职责:
Map<Integer, Integer> indexToValue: 这个HashMap负责直接映射。它的键是index,值是在该索引处存储的number。- 职责:提供 O(1) 平均时间复杂度的能力来查询任意
index上的值。这在change操作中用于查找需要被替换的旧值。
- 职责:提供 O(1) 平均时间复杂度的能力来查询任意
Map<Integer, TreeSet<Integer>> valueToIndices: 这是整个设计的核心。它是一个嵌套的数据结构。- 外层
HashMap:键是number,值是一个TreeSet。它负责将一个数字与所有存储它的索引关联起来。 - 内层
TreeSet:TreeSet是一个基于红黑树实现的、自动保持元素有序的集合。 - 职责:
- 高效地存储一个数字出现的所有索引。
- 自动对这些索引进行升序排序。
- 提供 O(log N) 时间复杂度的
add和remove操作。 - 提供 O(1) 时间复杂度的
first()操作,以快速获取最小的索引,这正是find操作所需要的。
- 外层
-
change(index, number)操作的逻辑:- 处理旧值:首先,需要检查
index处之前是否已经有值。通过indexToValue.get(index)查询。- 如果存在一个
oldValue,就必须从valueToIndices中oldValue对应的TreeSet里移除index。这是为了保持数据的一致性,因为index处的值即将改变。
- 如果存在一个
- 更新新值:
- 在
indexToValue中,更新或插入(index, number)的映射。 - 在
valueToIndices中,首先要找到number对应的TreeSet。computeIfAbsent(number, k -> new TreeSet<>())是一个优雅的写法:如果number的TreeSet不存在,就创建一个新的;如果存在,就返回现有的。- 然后,将
index添加到这个TreeSet中。TreeSet会自动将其放在正确的位置以维持排序。
- 在
- 处理旧值:首先,需要检查
-
find(number)操作的逻辑:- 查找索引集合:直接从
valueToIndices中获取number对应的TreeSet。 - 处理不存在的情况:如果获取到的
TreeSet是null(表示这个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);*/
时空复杂度
- 设
N为change操作的总调用次数。 - 设
M为find操作的总调用次数。 - 在任意时刻,设某个数字
k对应的索引数量为C_k。
时间复杂度
-
change(index, number):indexToValue.get(index): O(1) 平均。valueToIndices.get(oldValue): O(1) 平均。TreeSet.remove(index): O(logC_{oldValue})。C_{oldValue}是旧值对应的索引数。indexToValue.put(index, number): O(1) 平均。valueToIndices.computeIfAbsent(number, ...): O(1) 平均。TreeSet.add(index): O(logC_{number})。C_{number}是新值对应的索引数。- 综合:
change操作的时间复杂度主要由TreeSet的操作决定,为 O(log K),其中K是与操作相关的数字(oldValue或number)出现的次数。
-
find(number):valueToIndices.get(number): O(1) 平均。TreeSet.isEmpty(): O(1)。TreeSet.first(): O(1)。- 综合:
find操作的时间复杂度为 O(1) 平均。
空间复杂度
- 主要存储开销:由两个
Map决定。 indexToValue: 存储了所有被change操作接触过的唯一index。如果change操作涉及U个不同的索引,则其空间为 O(U)。valueToIndices:- 外层
Map的键是所有出现过的唯一number。 - 内层的
TreeSet存储了这些number对应的所有index。 - 所有
TreeSet中元素的总数,等于indexToValue中元素的总数,即U。 - 因此,
valueToIndices的总空间复杂度也是 O(U)。
- 外层
综合分析:
类的总空间复杂度为 O(U),其中 U 是被 change 操作触及过的唯一索引的总数。
