LeetCode系列文章
实现支持下列接口的「快照数组」- S n a p s h o t A r r a y SnapshotArray SnapshotArray:
- S n a p s h o t A r r a y ( i n t l e n g t h ) SnapshotArray(int\space length) SnapshotArray(int length) - 初始化一个与指定长度相等的类数组的数据结构。初始时,每个元素都等于0。
- v o i d s e t ( i n d e x , v a l ) void\space set(index,\space val) void set(index, val) - 会将指定索引 i n d e x index index 处的元素设置为 v a l val val。
- i n t s n a p ( ) int\space snap() int snap() - 获取该数组的快照,并返回快照的编号 s n a p _ i d snap\_id snap_id(快照号是调用 s n a p ( ) snap() snap() 的总次数减去1)。
- i n t g e t ( i n d e x , s n a p _ i d ) int\space get(index,\space snap\_id) int get(index, snap_id) - 根据指定的 s n a p _ i d snap\_id snap_id 选择快照,并返回该快照指定索引 i n d e x index index 的值。
输入: [ “ S n a p s h o t A r r a y " , “ s e t " , “ s n a p " , “ s e t " , “ g e t " ] [“SnapshotArray", “set", “snap", “set", “get"] [“SnapshotArray",“set",“snap",“set",“get"]
[ [ 3 ] , [ 0 , 5 ] , [ ] , [ 0 , 6 ] , [ 0 , 0 ] ] [ [3], [0, 5], [ ], [0, 6], [0, 0] ] [[3],[0,5],[],[0,6],[0,0]]
输出: [ n u l l , n u l l , 0 , n u l l , 5 ] [null, null, 0, null, 5] [null,null,0,null,5]
解释:
S n a p s h o t A r r a y ∗ o b j = n e w S n a p s h o t A r r a y ( 3 ) ; / / 初 始 化 一 个 长 度 为 3 的 快 照 数 组 SnapshotArray*\space obj = new\space SnapshotArray(3);\space \space//初始化一个长度为3的快照数组 SnapshotArray∗ obj=new SnapshotArray(3); //初始化一个长度为3的快照数组
o b j − > s e t ( 0 , 5 ) ; / / 令 a r r a y [ 0 ] = 5 obj->set(0, 5);\space \space//令array[0] = 5 obj−>set(0,5); //令array[0]=5
o b j − > s n a p ( ) ; / / 获 取 快 照 , 返 回 s n a p i d = 0 obj->snap();\space \space//获取快照,返回snap_id = 0 obj−>snap(); //获取快照,返回snapid=0
o b j − > s e t ( 0 , 6 ) ; obj->set(0, 6); obj−>set(0,6);
o b j − > g e t ( 0 , 0 ) ; / / 获 取 s n a p _ i d = 0 的 快 照 中 a r r a y [ 0 ] 的 值 , 返 回 5 obj->get(0, 0);\space \space//获取snap\_id = 0的快照中array[0]的值,返回5 obj−>get(0,0); //获取snap_id=0的快照中array[0]的值,返回5
首先,模拟快照数组是最容易想到的一种方法。
在C++中,可以用vector来充当这个普通数组,该普通数组当中存储的元素类型是int。而由于调用 s n a p snap snap 函数获取到的快照号,是从0开始向后依次递增,因此也可以用一个vector来存储每一个快照号对应的快照数组备份,该vector的下标代表的是某一快照号,而vector容器当中存储的元素则是一个个的普通数组,即存储元素的类型是vector
但实际我们并不推荐模拟快照数组这个方法,因为每次调用 s n a p snap snap 函数都会将当前快照数组当中的每一个元素进行备份,此时就会导致大量不必要的备份。
快照数组可以根据快照号获取到曾经已经被修改过的元素值,这也就意味着:
一、将元素值与时间进行“绑定”
此时我们要做的就是在调用 s e t set set 函数设置元素值时,需要将该元素值与当前时间“绑定”后再进行设置。而要与时间“绑定”其实很简单,我们可以将调用 s e t set set 函数的次数 t i m e time time 进行记录,当需要设置元素值时设置的就不是一个简单的 v a l val val,而是一个 < t i m e , v a l > <time,val> 键值对。
而由于我们不能将之前设置的元素值进行覆盖,因此我们可以将快照数组当中的每一个位置看作一个哈希桶,当我们要设置快照数组当中的某一元素值时,就将对应的 < t i m e , v a l > <time,val> 键值对挂到对应的哈希桶下面即可。
例如,按照所给示例设置快照数组后,快照数组的布局如下:
由于初始时快照数组当中每个元素的值都要初始化为0,因此 t i m e time time 为0快照数组中每个元素的值都是0,而由于后续调用了两次 s e t set set 函数将快照数组中第0个元素先后设置为了5和6,因此在第0号桶下还挂上了 < 1 , 5 > <1, 5> <1,5> 和 < 2 , 6 > <2, 6> <2,6>键值对。
二、将快照号与时间进行“绑定”
我们最终是要通过快照号来获取指定索引的元素,而现在我们只是将元素值与 t i m e time time 进行了“绑定”,因此接下来还需要将快照号与 t i m e time time 进行“绑定”。
由于调用 s n a p snap snap 函数获取到的快照号是从0开始向后依次递增的,因此我们可以通过数组的方式建立快照号与 t i m e time time 的映射关系,其中快照号就作为数组的下标,而快照号对应的 t i m e time time 就作为该下标对应的元素值。
因此当调用 s n a p snap snap 函数时,我们需要将当前的 t i m e time time 值存储到一个数组当中。比如第一次调用 s n a p snap snap 函数时,快照号为0, t i m e time time 的值为3;第二次调用 s n a p snap snap 函数时,快照号为1, t i m e time time 的值为5;第三次调用 s n a p snap snap 函数时,快照号为2, t i m e time time 的值为8,那么此时该数组的布局如下:
三、获取指定快照号对应索引的元素
现在将元素值和快照号都与 t i m e time time 进行了“绑定”,那当我们要获取指定快照号对应索引 i n d e x index index 的元素时,是不是就是去快照数组中第 i n d e x index index 号哈希桶下面找到对应 t i m e time time 值的元素值就行了呢?
理论上是这样的,例如在下面这个快照数组当中我们要获取快照号为0时第0个元素的值,此时就应该在第0个哈希桶下找到 t i m e time time 为2的键值对对应的元素值,也就是6。
但如果我们要获取快照号为0时第1个元素的值呢?此时在第1号哈希桶下是找不到 t i m e time time 为2的键值对的,在第1号哈希桶下没有 t i m e time time 为1和2对应的键值对,也就意味着在 t i m e time time 为1和2时快照数组中第1个元素的值没有发生变化,此时我们就应该将 t i m e time time 值为0的元素值进行返回。
因此当我们要获取指定快照号对应索引 i n d e x index index 的元素时,应该在快照数组中第 i n d e x index index 号哈希桶下面找到第一个不大于对应 t i m e time time 值的元素值进行返回。
动图演示:
代码如下:
上述代码中将 < t i m e , v a l > <time,val> 键值对类型进行了定义,并对该类型的 < < < 运算符进行了重载,因为代码中调用了 l o w e r _ b o u n d lower\_bound lower_bound 函数,该函数会用 < < < 符号对数据进行比较。
实际这里我们并不是一定要进行键值对类型的定义,由于这里的每个 < t i m e , v a l > <time,val> 键值对当中存储的 t i m e time time 都一定是不同的,因此可以直接使用 p a i r pair pair 类型,因为 p a i r pair pair 类型的变量在比较时就是默认先比较第一个成员的大小,此时也能达到比较键值对中的 t i m e time time 的目的。
说明一下: