算法基础-哈希表

哈希表

哈希表

哈希表可以理解为一个加强版的数组。

数组可以通过索引在

O(1) 的时间复杂度内查找到对应元素,索引是一个非负整数。

哈希表是类似的,可以通过 key 在

O(1) 的时间复杂度内查找到这个 key 对应的 value。key 的类型可以是数字、字符串等多种类型。

怎么做的?特别简单,哈希表的底层实现就是一个数组(我们不妨称之为 table)。它先把这个 key 通过一个哈希函数(我们不妨称之为 hash)转化成数组里面的索引,然后增删查改操作和数组基本相同:

c 复制代码
// 哈希表伪码逻辑
class MyHashMap {

private:
    vector<void*> table;

public:
    // 增/改,复杂度 O(1)
    void put(auto key, auto value) {
        int index = hash(key);
        table[index] = value;
    }

    // 查,复杂度 O(1)
    auto get(auto key) {
        int index = hash(key);
        return table[index];
    }

    // 删,复杂度 O(1)
    void remove(auto key) {
        int index = hash(key);
        table[index] = nullptr;
    }

private:
    // 哈希函数,把 key 转化成 table 中的合法索引
    // 时间复杂度必须是 O(1),才能保证上述方法的复杂度都是 O(1)
    int hash(auto key) {
        // ...
    }
};

哈希冲突

上面给出了 hash 函数的实现,那么你肯定也会想到,如果两个不同的 key 通过哈希函数得到了相同的索引,怎么办呢?这种情况就叫做「哈希冲突」。

扩容和负载因子

1、哈希函数设计的不好,导致 key 的哈希值分布不均匀,很多 key 映射到了同一个索引上。

2、哈希表里面已经装了太多的 key-value 对了,这种情况下即使哈希函数再完美,也没办法避免哈希冲突。

对于第一个问题没什么好说的,开发编程语言标准库的大佬们已经帮你设计好了哈希函数,你只要调用就行了。

对于第二个问题是我们可以控制的,即避免哈希表装太满,这就引出了「负载因子」的概念。

两数之和

两数之和

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hashtable;
        for (int i = 0; i < nums.size(); ++i) {
            int complement = target - nums[i];
            if (hashtable.find(complement) != hashtable.end()) {
                // 如果找到了,返回它的下标和当前下标
                return {hashtable[complement], i};
            }
            hashtable[ums[i]]n = i;
        }
        return {}; // 题目保证有解,这就只是为了语法正确
    }
};

49. 字母异位词分组

49. 字母异位词分组

第一个for循环先把元素按照排序的方式归类好例如 eat tae都归类为aet

注意先复制一份,这样不会修改原数组

在push进哈希表中达到这个效果mp = { "aet": ["eat", "tea"] }

然后剔除标签(it.second)即可

重点讲一下mp[key].push_back(s);,首先在map中查找key查不查的到都返回一个vector的引用

因为哈希表的value类型是vector< string > 然后利用vector的push_back插入s

cpp 复制代码
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        unordered_map<string,vector<string>> mp;
        for(const string& s:strs){
            string key = s;
            sort(key.begin(),key.end());
            mp[key].push_back(s);
        }
        vector<vector<string>> res;
        for(auto& it : mp){
            res.push_back(move(it.second));
        }
        return res;
    }
};

128. 最长连续序列

128. 最长连续序列

和上文不一样使用单独用于查找的unordered_set

先把数据都插入进去

然后遍历一下,如果这个数字的前一位没查到,就可以证明他可以当车头

记录此刻的头部和长度

如果能查到后一位

本身和长度就都加一(车头和后一节车厢连起来了)

这一列车厢全都连接完毕后,记录一下此刻的最大长度

cpp 复制代码
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> num_set;
        for(int& num:nums){
            num_set.insert(num);
        }
        int maxlongnum = 0;

        for(int num:num_set){
            if(!num_set.count(num-1)){
                int cur = num;
                int cur_long = 1;

                while(num_set.count(cur+1)){
                    cur += 1;
                    cur_long += 1;

                }
                maxlongnum = max(maxlongnum,cur_long);
            }
        }
        return maxlongnum;
    }
};

<unordered_map>和<unordered_set>

<unordered_set>

. std::unordered_set (哈希集合)

  • 定义:只存 Key,不存 Value。
  • 特性:去重(Key 唯一)、无序。
  • 用途:黑名单检测、去重、快速查找是否存在。
操作 代码示例 返回值解释 (重点) 补充
插入 st.insert(key) 返回 pair<iterator, bool>。 1. first: 指向元素的迭代器。2. second: true 表示插入成功(原集合无此数),false 表示失败(已存在)。
查找 st.find(key) 返回 iterator。 如果找到,指向该元素;如果没找到,返回 st.end()。
计数 st.count(key) 返回 size_t (整数)。 因去重,只能返回 1 (存在) 或 0 (不存在)。
删除 st.erase(key) 返回 size_t。 成功删除返回 1,没删掉(本来就不在)返回 0。
cpp 复制代码
#include <iostream>
#include <unordered_set>
using namespace std;

void testSet() {
    unordered_set<int> st;
    
    // 1. 插入并检查返回值
    auto res = st.insert(10);
    if (res.second) cout << "10 插入成功" << endl;
    
    st.insert(10); // 再次插入,静默失败,不会报错
    
    // 2. 查找
    if (st.find(20) == st.end()) {
        cout << "20 不在集合里" << endl;
    }
}

std::unordered_map (哈希映射)

  • 定义:存储 Key-Value 对(键值对)。
  • 特性:Key 唯一,Value 可以重复。通过 Key 找 Value。
  • 用途:字典、缓存、计数器(统计词频)。
操作 代码示例 返回值解释 (重点) 补充
下标访问 mp[key] 返回 Value 的引用。 ⚠️ 副作用:如果 Key 不存在,会自动插入一个默认值(0 或空串),然后返回这个新值的引用。
插入 mp.insert({k, v}) 返回 pair<iterator, bool>。 逻辑同 set,如果 Key 已存在,不会更新 Value,而是保持原样并返回 false。
查找 mp.find(key) 返回 iterator。 it->first 是 Key,it->second 是 Value。没找到返回 mp.end()。
安全访问 mp.at(key) 返回 Value 的引用。 如果 Key 不存在,抛出 std::out_of_range 异常。
cpp 复制代码
#include <iostream>
#include <unordered_map>
using namespace std;

void testMap() {
    unordered_map<string, int> mp;
    
    // 1. 下标法(最常用,但这行代码其实包含了 查找+插入)
    mp["apple"] = 5; 
    
    // 2. 累加(引用引用的好处)
    mp["apple"]++; // apple 变成 6
    
    // 3. insert 的坑:不会覆盖!
    mp.insert({"apple", 100}); 
    cout << mp["apple"] << endl; // 输出还是 6,不是 100!
    
    // 4. 正确的查找方式
    if (mp.find("banana") == mp.end()) {
        cout << "没有香蕉" << endl;
    }
}

面试->底层原理

当你声明一个 unordered_map 时,内存里到底发生了什么?

  1. 桶数组 (Buckets)
    STL 内部维护了一个指针数组(通常称为 Buckets)。数组的每个位置存储一个指针,指向一个节点。
  2. 哈希函数 (Hash Function)
    当你执行 mp["abc"] = 1:
    计算 "abc" 的哈希值(比如得到 999999)。
    计算下标:index = hash_value % bucket_count(假设数组长 10,则 index = 9)。
    去访问数组的第 9 号位置。
  3. 冲突解决 (Linked List)
    如果第 9 号位置已经有数据了(比如 "xyz" 也在第 9 号),由于 C++ 采用拉链法:
    它会遍历第 9 号位置挂着的链表。
    检查 Key 是否相同。
    如果 Key 相同,更新 Value。
    如果 Key 不同,将新节点挂在链表的末尾(或头部,视实现而定)。
  4. 重哈希 (Rehashing)
    如果数据越来越多,链表会越来越长,查询就会退化成 O ( N ) O(N) O(N)。
    为了避免这种情况,有一个指标叫 Load Factor (负载因子),默认为 1.0。
    当 元素数量 > 桶数量 * 1.0 时,哈希表会自动扩容。
    扩容过程: 创建一个更大的数组(通常是 2 倍),把旧表里的所有元素重新计算哈希值,放入新表。这是一个耗时操作。

其他类型的哈希表 (Variant)

C++ STL 还提供了允许 Key 重复的哈希表。

  1. std::unordered_multiset
    区别:允许插入重复的 Key。
    用途:如果你只是一堆数据往里扔,想知道每个数出现了几次,但又不想用 Map 存 {num, count},可以直接扔进这里面。
    API 差异:count(key) 会返回真实的个数(可能大于 1)。erase(key) 会删除所有等于该 Key 的元素。
  2. std::unordered_multimap
    区别:一个 Key 可以对应多个 Value。
    场景:比如建立"作者 -> 书籍"的索引,一个作者可以写多本书。
    结构:{ "曹雪芹": "红楼梦" }, { "曹雪芹": "石头记" } 可以共存。
相关推荐
渐暖°2 小时前
【leetcode算法从入门到精通】9. 回文数
算法·leetcode·职场和发展
星火开发设计2 小时前
using 关键字:命名空间的使用与注意事项
开发语言·c++·学习·算法·编程·知识
ZPC82102 小时前
机器人手眼标定
人工智能·python·数码相机·算法·机器人
知我心·2 小时前
Java实现常见算法
算法
HalvmånEver2 小时前
Linux:线程创建与终止下(线程六)
linux·运维·算法
80530单词突击赢2 小时前
C++类与对象进阶核心技巧
算法
我是咸鱼不闲呀2 小时前
力扣Hot100系列18(Java)——[技巧]总结 (只出现一次的数字,多数元素,颜色分类,下一个排列,寻找重复数)
java·算法·leetcode
mit6.8242 小时前
贪心构造+枚举子集+有向图判环
算法
孞㐑¥2 小时前
算法—字符串
开发语言·c++·经验分享·笔记·算法