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
三、主体思路
1、模拟
首先,模拟快照数组是最容易想到的一种方法。
- 可以将一个普通数组看作快照数组,调用 s e t set set 函数设置快照数组当中的元素值,就是设置该普通数组当中的元素值。
- 而当调用 s n a p snap snap 函数获取数组的快照时,需要将当前快照数组中各个元素的值进行备份,因为后续可能会调用 g e t get get 函数获取该快照当中的某一元素值。
- 当调用 g e t get get 函数获取指定快照当中的某一元素值时,就可以根据快照号找到对应的快照数组,然后将对应的元素值进行返回即可。
在C++中,可以用vector来充当这个普通数组,该普通数组当中存储的元素类型是int。而由于调用 s n a p snap snap 函数获取到的快照号,是从0开始向后依次递增,因此也可以用一个vector来存储每一个快照号对应的快照数组备份,该vector的下标代表的是某一快照号,而vector容器当中存储的元素则是一个个的普通数组,即存储元素的类型是vector<int>。
但实际我们并不推荐模拟快照数组这个方法,因为每次调用 s n a p snap snap 函数都会将当前快照数组当中的每一个元素进行备份,此时就会导致大量不必要的备份。
2、哈希+二分法
快照数组可以根据快照号获取到曾经已经被修改过的元素值,这也就意味着:
- 在调用 s e t set set 函数设置元素值时,不应该只是简单的设置元素值,而应该通过某种方式将当前的时间点也设置进去,也就是将设置的元素值在某种意义上与时间进行“绑定”。
- 并且在设置元素值时,不应该将之前设置的元素值进行覆盖,这样我们才能找到之前设置过的元素值。
- 这样一来,在调用 g e t get get 函数获取元素值时就可以获取在之前任意时间点设置过的元素值。
一、将元素值与时间进行“绑定”
此时我们要做的就是在调用 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> <time,val> 键值对。
而由于我们不能将之前设置的元素值进行覆盖,因此我们可以将快照数组当中的每一个位置看作一个哈希桶,当我们要设置快照数组当中的某一元素值时,就将对应的 < t i m e , v a l > <time, val> <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 值的元素值进行返回。
动图演示:
四、代码实现
1、模拟
代码如下:
2、哈希+二分法
代码如下:
上述代码中将
<
t
i
m
e
,
v
a
l
>
<time, val>
<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>
<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 的目的。
说明一下:
- 代码中的 l o w e r _ b o u n d lower\_bound lower_bound 函数只能找出不小于 t i m e time time 的键值对,而我们是要找出不大于 t i m e time time 的键值对,因此我们需要先找到不小于 t i m e + 1 time+1 time+1 的键值对,此时该键值对的前一个键值对就是不大于 t i m e time time 的键值对。