Description
不使用任何內建 Hash Table 函式庫,設計一個 HashSet。實作
add(key)、remove(key)、contains(key)方法。值的範圍為[0, 10^6]。
Example:
Input: [“MyHashSet”, “add”, “add”, “contains”, “contains”, “add”, “contains”, “remove”, “contains”] > [[], [1], [2], [1], [3], [2], [2], [2], [2]] Output: [null, null, null, true, false, null, true, null, false]
Intuition#
核心思路:使用 Bucket + Linked List(Chaining)處理碰撞,或在值域有限時直接用 Boolean 陣列。
- 最簡單的做法是用大小 10^6+1 的布林陣列,但浪費空間
- 更通用的做法是用 Hash Function + Chaining(鏈結串列處理碰撞)
Approaches#
1: Boolean Array(直接映射)#
- 概念: 建立大小為 10^6+1 的布林陣列,直接以 key 為索引
- 時間複雜度: 所有操作
O(1) - 空間複雜度:
O(10^6)- 固定大小
class MyHashSet() {
private val set = BooleanArray(1000001)
fun add(key: Int) {
set[key] = true
}
fun remove(key: Int) {
set[key] = false
}
fun contains(key: Int): Boolean {
return set[key]
}
}⭐2: Hash Function + Chaining#
- 概念: 用固定大小的 bucket 陣列,每個 bucket 是一個 LinkedList。用取模作為 Hash Function,碰撞時在同一 bucket 的鏈結串列中查找。
- 時間複雜度: 平均
O(1),最壞O(n/k)(k 為 bucket 數) - 空間複雜度:
O(n + k)- n 為元素數,k 為 bucket 數
class MyHashSet() {
private val size = 1000
private val buckets = Array(size) { mutableListOf<Int>() }
private fun hash(key: Int): Int = key % size
fun add(key: Int) {
val bucket = buckets[hash(key)]
if (key !in bucket) {
bucket.add(key)
}
}
fun remove(key: Int) {
buckets[hash(key)].remove(key)
}
fun contains(key: Int): Boolean {
return key in buckets[hash(key)]
}
}補充:BST Bucket
用 TreeSet(BST)代替 LinkedList 作為 bucket,可以將最壞情況從 O(n/k) 改善到 O(log(n/k))。
class MyHashSet() {
private val size = 1000
private val buckets = Array(size) { java.util.TreeSet<Int>() }
private fun hash(key: Int): Int = key % size
fun add(key: Int) {
buckets[hash(key)].add(key)
}
fun remove(key: Int) {
buckets[hash(key)].remove(key)
}
fun contains(key: Int): Boolean {
return buckets[hash(key)].contains(key)
}
}🔑 Takeaways#
- Pattern: Hash Table 的底層實作——Hash Function + 碰撞處理
- 關鍵技巧: Chaining(鏈結串列)是最常見的碰撞處理方式;bucket 數量選質數可以減少碰撞;面試中展示 Chaining 比直接陣列映射更能體現資料結構設計能力