哈希表知识点

哈希表 (Hash Table)

哈希表通过哈希函数将键映射到桶中,实现 $O(1)$ 平均时间复杂度的插入、删除和查找。

本质:用空间换时间,将遍历查找的 $O(n)$ 降为 $O(1)$。


1
2
3
4
5
6
// 经典:是否存在两数和为 target
unordered_set<int> seen;
for (int x : nums) {
    if (seen.count(target - x)) return true;
    seen.insert(x);
}
1
2
unordered_map<int, int> freq;
for (int x : nums) freq[x]++;
1
2
3
4
5
6
7
unordered_map<int, int> pos;  // 值 → 最后一次出现的下标
for (int i = 0; i < n; i++) {
    if (pos.count(nums[i])) {
        // 之前出现过
    }
    pos[nums[i]] = i;
}
1
2
3
4
5
6
7
8
9
// 双射:两个方向都要映射
unordered_map<char, char> s2t, t2s;
for (int i = 0; i < s.size(); i++) {
    char a = s[i], b = t[i];
    if ((s2t.count(a) && s2t[a] != b) || (t2s.count(b) && t2s[b] != a))
        return false;
    s2t[a] = b;
    t2s[b] = a;
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 拉链法替代方案,缓存友好
const int N = 20007;  // 质数,开 2~3 倍
int h[N], nil = 0x3f3f3f3f;

int find(int x) {
    int k = (x % N + N) % N;
    while (h[k] != nil && h[k] != x) {
        if (++k == N) k = 0;
    }
    return k;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 滚动哈希:用于字符串匹配 / 最长重复子串
typedef unsigned long long ull;
const ull P = 131;  // 基数,131 或 13331

vector<ull> h(n + 1), p(n + 1);
p[0] = 1;
for (int i = 0; i < n; i++) {
    h[i+1] = h[i] * P + s[i];
    p[i+1] = p[i] * P;
}
// 子串 [l, r] 的哈希值
ull hash = h[r+1] - h[l] * p[r - l + 1];

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// --- unordered_map ---
unordered_map<int, int> mp;
mp.count(key);        // 是否存在(0 或 1)
mp[key] = val;        // 插入/修改
mp.erase(key);        // 删除
mp.find(key);         // 返回迭代器,不存在返回 mp.end()
for (auto& [k, v] : mp) { }  // 遍历

// --- unordered_set ---
unordered_set<int> st;
st.insert(x);
st.erase(x);
st.count(x);

// 自定义哈希(pair 等)
struct pair_hash {
    template<class T1, class T2>
    size_t operator()(const pair<T1, T2>& p) const {
        return hash<T1>{}(p.first) ^ (hash<T2>{}(p.second) << 1);
    }
};
unordered_map<pair<int,int>, int, pair_hash> mp;

// 用数组替代哈希表(值域小时更快)
int cnt[100001] = {0};  // 省去哈希开销

题目关键点笔记
1. 两数之和哈希表入门,边遍历边查—
3. 无重复字符的最长子串滑动窗口 + 哈希表存位置—
49. 字母异位词分组排序后字符串作 key—
128. 最长连续序列只从序列起点开始计数—
146. LRU 缓存哈希表 + 双向链表—
205. 同构字符串双射映射—
347. 前 K 个高频元素频率统计 + 堆/桶排序—
454. 四数相加 II分组 + 哈希表,$O(n^2)$—
560. 和为 K 的子数组前缀和 + 哈希表题解
705. 设计哈希集合手写哈希表实现—
525. 连续数组0→-1 转化 + 哈希表题解
1124. 表现良好的最长时间段前缀和 + 哈希表—

  1. 哈希表 vs 数组:值域小(< 10^6)时优先用数组,常数小且无冲突
  2. unordered_map vs map:不需要有序时用 unordered_map,$O(1)$ vs $O(\log n)$
  3. key 不存在时 mp[key] 会插入默认值,判断存在用 count() 或 find()
  4. 避免哈希退化:unordered_map 最坏 $O(n)$,竞赛中可能被卡,可自定义哈希函数
  5. 两数之和类问题,先查再插,避免自己和自己配对
  6. 双射问题(同构、单词模式),必须同时维护两个方向的映射

相关内容