1. 题目来源
题单:
- 待补充
2. 题目解析
其实这个题目抽象一下的话在项目中也能出现,可能日常项目中没有算法基础的话,就很容易直接去进行新内存开辟、重复遍历等操作。尤其是在哈希表删除后,又需要进行一个相同概率返回的这个操作的时候。常见就有重新遍历哈希表到临时数组对象中,然后再依靠 rand 函数进行返回。
那么 O ( n ) O(n) O(n) 次的查询情况下每一个遍历都是 O ( n ) O(n) O(n) 的,那么整体的时间复杂度就是妥妥的 O ( n 2 ) O(n^2) O(n2) 的,在高并发场景下,这就是妥妥的项目坑点啊。
思路:
- 构建一个哈希表、一个数组。哈希表用来存放 val 及数组中的该 val 的下标。数组用来存放全部元素, 便于最后通过 rand 函数进行相同概率的直接返回,避免遍历哈希表。
- 主要是删除元素的时候,哈希表元素与数组元素怎么快速做到
O
(
1
)
O(1)
O(1) 删除。
- 我们只需要将数组最后一个元素与待删除元素调换位置,然后将最后一个元素 pop 出去就可以做到 O ( 1 ) O(1) O(1) 的删除了。
- 同样上述的思想,在堆排序的向上调整、向下调整中也有用到。
- 在如何 O ( 1 ) O(1) O(1) 删除单链表中的某一个节点元素的时候也有用到。
具体的看下如下代码即可,代码好理解,但如何运用到项目中去显著提高业务响应速度是值得我们去深思斟酌的。
- 时间复杂度: O ( 1 ) O(1) O(1)
- 空间复杂度: O ( n ) O(n) O(n)
type RandomizedSet struct {
m map[int]int // val--idx 存 val 值,及在 s 中的下标位置
s []int // val 存 val 值
}
func Constructor() RandomizedSet {
r := RandomizedSet {
s: make([]int, 0, 32),
m: make(map[int]int),
}
return r
}
func (r *RandomizedSet) Insert(val int) bool {
if _, ok := r.m[val]; ok {
return false
}
r.m[val] = len(r.s) // 新插入的 val 元素在 s 中的下标为总长度,及尾插
r.s = append(r.s, val)
return true
}
func (r *RandomizedSet) Remove(val int) bool {
id, ok := r.m[val]
if !ok {
return false
}
last := len(r.m) - 1 // 拿到最后一个元素的下标
r.s[id] = r.s[last] // 在数组中,用最后一个元素值覆盖掉要删除的元素值,方便等会直接删除最后一个元素就行
r.m[r.s[last]] = id // 在哈希表中,重新建立索引关系,最后一个元素的哈希值应该是待删除元素的数组中的下标位置
r.s = r.s[:last] // 切片操作,删除最后一个元素即可
delete(r.m, val) // 哈希表操作,删除哈希表元素即可
return true
}
func (r *RandomizedSet) GetRandom() int {
return r.s[rand.Intn(len(r.s))] // 随机化操作
}
/**
* Your RandomizedSet object will be instantiated and called as such:
* obj := Constructor();
* param_1 := obj.Insert(val);
* param_2 := obj.Remove(val);
* param_3 := obj.GetRandom();
*/