哈希表专题总结:从困惑到掌握容器选择

在这里插入图片描述

目录


学习记录

  • 刷题周期: 4天分散学习(10.2、10.8-10.9、10.19)
  • 完成题量: 8题全部AC
  • 题目分布: 基础哈希5题 + 前缀和+哈希表3题

刷题记录

Day01(10.2):第一次见哈希表

题目: LeetCode 1 - 两数之和

当时在学双指针,这道题用暴力法做了,后来知道哈希表能O(n)解决。那时候还不太懂哈希表,只知道能快速查找。

状态: 暴力法AC,哈希表看懂了但没实践


Day07-08(10.8-10.9):前缀和+哈希表(3题)

这几天学前缀和的时候,遇到了3道"前缀和+哈希表"的题:

  • LeetCode 560 - 和为K的子数组
  • LeetCode 974 - 和可被K整除的子数组
  • LeetCode 525 - 连续数组

状态: 看了讲解才做出来,hash里面存什么、查什么,当时很迷糊

当时的疑惑:

  1. 为什么要用 hash[sum-k] 而不是直接算?
  2. hash[0] = 1 是什么意思?
  3. 余数为什么能判断整除?

Day19(10.19):哈希表专题突破(5题)

专门学哈希表:

  • LeetCode 1 - 两数之和(重做)
  • 面试题 01.02 - 判定是否互为字符重排
  • LeetCode 217 - 存在重复元素
  • LeetCode 219 - 存在重复元素 II
  • LeetCode 49 - 字母异位词分组

完成情况:

  • 第2题:3分12秒AC
  • 第3题:1分38秒AC ⚡
  • 第4题:5分钟AC(遇到括号bug)
  • 第5题:经过详细引导后AC

状态: 终于搞懂了什么时候用set、什么时候用map、什么时候用数组模拟


我的学习过程

第一阶段:懵懵懂懂(Day01-Day08)

那时候只知道哈希表"快",但不知道为什么快,也不知道什么时候该用。

我的问题:

  • unordered_map和unordered_set有什么区别?
  • 什么时候用数组模拟哈希表?
  • 前缀和+哈希表到底在干什么?

第二阶段:踩坑实践(Day19)

踩了很多坑:

  1. 单引号vs变量 - 's[i]' 写成了多字符常量
  2. 数组类型 - char hash[26] 存计数会溢出
  3. 数组比较 - 数组不能直接用 == 比较
  4. abs括号位置 - abs(a-b<=k) 运算顺序错了
  5. 嵌套数据结构 - vector<vector<string>> 理解不了

第三阶段:理解本质(Day19晚)

终于搞懂了:

  • 哈希表的本质是用空间换时间
  • 容器选择的关键是看你要存什么
  • "边遍历边查找"是哈希表的核心技巧

哈希表的核心概念(我的理解)

什么是哈希表?

我的理解就是:一个能O(1)快速查找的容器,用来建立映射关系。

关键是:

  • 要存什么信息?
  • 查找的时候要什么?
  • 怎么保证不重复统计?

哈希表的三种用途

通过这8道题,我发现哈希表主要有3种用法:

1. 判断存在性(去重、检测重复)

用什么容器: unordered_set<T>

特点: 只存元素,不存其他信息

适用: 判断是否重复、是否存在

模板:

unordered_set<int> hash;
for(auto x : nums) {
    if(hash.count(x)) return true;  // 重复了
    hash.insert(x);
}
return false;

我做过的题:

  • LeetCode 217 - 存在重复元素
2. 统计频次/建立映射

用什么容器: unordered_map<Key, Value>

特点: 存键值对,可以是元素→次数、元素→下标等

适用: 需要统计次数、需要记录位置

模板:

// 统计频次
unordered_map<int, int> hash;  // <元素, 次数>
for(auto x : nums) {
    hash[x]++;
}

// 元素与下标
unordered_map<int, int> hash;  // <元素, 下标>
for(int i = 0; i < n; i++) {
    hash[nums[i]] = i;
}

我做过的题:

  • LeetCode 1 - 两数之和(元素→下标)
  • LeetCode 219 - 存在重复元素II(元素→下标)
  • LeetCode 560 - 和为K的子数组(前缀和→次数)
  • LeetCode 974 - 和可被K整除的子数组(余数→次数)
  • LeetCode 525 - 连续数组(前缀和→位置)
3. 分组(嵌套结构)

用什么容器: unordered_map<Key, vector<T>>

特点: 一个key对应一组数据

适用: 需要把相同特征的元素分到一组

模板:

unordered_map<string, vector<string>> hash;
for(auto& s : strs) {
    string key = getKey(s);  // 提取特征
    hash[key].push_back(s);   // 分组
}

// 提取结果
vector<vector<string>> ret;
for(auto& [k, v] : hash) {
    ret.push_back(v);
}

我做过的题:

  • LeetCode 49 - 字母异位词分组

典型题目分类

类型1:基础哈希表

LeetCode 1. 两数之和 ⭐⭐⭐

这是哈希表最经典的题目。

题目: 找两个数相加等于target,返回下标。

我的思路演变:

第一次(Day01): 暴力法,O(n²)

for(int i = 0; i < n; i++) {
    for(int j = i+1; j < n; j++) {
        if(nums[i] + nums[j] == target) 
            return {i, j};
    }
}

第二次(Day19): 哈希表,O(n)

unordered_map<int, int> hash;  // <元素值, 下标>
for(int i = 0; i < n; i++) {
    int complement = target - nums[i];
    if(hash.count(complement))
        return {hash[complement], i};
    hash[nums[i]] = i;  // 先查找后插入
}
return {};

关键点:

  1. 先查找后插入 - 防止同一个元素被使用两次
  2. “边遍历边查找” - 不需要先全部插入再查找
  3. 用空间换时间 - 从O(n²)到O(n)

面试题 01.02. 判定是否互为字符重排

题目: 判断两个字符串能否通过重新排列得到。

我的思路: 统计每个字符出现的次数,看是否相等。

方法一:unordered_map

unordered_map<char, int> hash1, hash2;
for(auto ch : s1) hash1[ch]++;
for(auto ch : s2) hash2[ch]++;
return hash1 == hash2;  // map可以直接比较

方法二:数组模拟(只有小写字母)

int hash1[26] = {0}, hash2[26] = {0};
for(auto ch : s1) hash1[ch - 'a']++;
for(auto ch : s2) hash2[ch - 'a']++;

// 数组不能直接比较,要逐个元素比
for(int i = 0; i < 26; i++) {
    if(hash1[i] != hash2[i]) return false;
}
return true;

方法三:单数组优化

if(s1.size() != s2.size()) return false;

int hash[26] = {0};
// s1加,s2减
for(int i = 0; i < s1.size(); i++) {
    hash[s1[i] - 'a']++;
    hash[s2[i] - 'a']--;
}

// 检查是否全为0
for(int i = 0; i < 26; i++) {
    if(hash[i] != 0) return false;
}
return true;

我的收获:

  • 数组模拟比map快,但只适用于字符范围确定的情况
  • "加减抵消法"是一个很巧妙的技巧
  • 数组不能直接用 == 比较!

我踩的坑:

  1. 's[i]' 写成了单引号 → 多字符常量
  2. char hash[26] → 应该用 int hash[26]
  3. hash1[s1[1]] → 应该用循环变量 i

LeetCode 217. 存在重复元素

最简单的一道题。

题目: 判断数组中是否有重复元素。

我的解法(1分38秒AC):

unordered_map<int, int> hash;
for(int i = 0; i < nums.size(); i++) {
    hash[nums[i]]++;
    if(hash[nums[i]] > 1) return true;
}
return false;

更简洁的写法:

unordered_set<int> hash;
for(auto x : nums) {
    if(hash.count(x)) return true;
    hash.insert(x);
}
return false;

关键理解:

  • 只需要判断是否存在,不需要统计次数
  • unordered_set 更合适
  • for(auto x : nums) 比下标访问更简洁

LeetCode 219. 存在重复元素 II ⭐⭐

题目: 判断是否存在两个相同元素,且下标距离 ≤ k。

我的解法(5分钟,遇到bug):

unordered_map<int, int> hash;  // <元素值, 下标>
for(int i = 0; i < nums.size(); i++) {
    if(hash.count(nums[i]) && abs(hash[nums[i]] - i) <= k)
        return true;
    hash[nums[i]] = i;  // 更新为最新下标
}
return false;

我踩的坑:

// ❌ 错误
abs(hash[nums[i]] - i <= k)
// 计算顺序:(- i <= k) → bool → abs(bool)

// ✅ 正确
abs(hash[nums[i]] - i) <= k
// 计算顺序:(- i) → abs(差值) → (<= k)

关键点:

  1. 必须每次都更新下标 - 保证比较的是最近的距离
  2. abs()的括号位置 - 运算符优先级问题

测试用例模拟:

输入:nums = [1,2,3,1,2,3], k = 2

i=0: hash[1]=0
i=1: hash[2]=1
i=2: hash[3]=2
i=3: 1在hash中,|0-3|=3 > 2,不满足,更新hash[1]=3
i=4: 2在hash中,|1-4|=3 > 2,不满足,更新hash[2]=4
i=5: 3在hash中,|2-5|=3 > 2,不满足,更新hash[3]=5

结果:false

LeetCode 49. 字母异位词分组 ⭐⭐

这题比较难,涉及到嵌套数据结构。

题目: 把字母异位词分到一组。

我的理解过程:

一开始不知道怎么写,不懂 vector<vector<string>> 是什么意思,也不知道怎么分组。

经过详细演示后,我理解了:

  • 异位词的特点:排序后相同
  • 用哈希表自动分组:相同key的自动归到一起
  • vector<vector<string>> 就是二维数组

完整代码:

vector<vector<string>> groupAnagrams(vector<string>& strs) {
    unordered_map<string, vector<string>> hash;
    
    // 1. 分组
    for(auto& s : strs) {
        string key = s;
        sort(key.begin(), key.end());  // 排序后作为key
        hash[key].push_back(s);         // 加入对应的组
    }
    
    // 2. 提取结果
    vector<vector<string>> ret;
    for(auto& [key, val] : hash) {
        ret.push_back(val);  // val就是一组异位词
    }
    return ret;
}

演示过程:

输入:["eat", "tea", "tan", "ate", "nat", "bat"]

处理"eat": key="aet" → hash["aet"] = ["eat"]
处理"tea": key="aet" → hash["aet"] = ["eat", "tea"]
处理"tan": key="ant" → hash["ant"] = ["tan"]
处理"ate": key="aet" → hash["aet"] = ["eat", "tea", "ate"]
处理"nat": key="ant" → hash["ant"] = ["tan", "nat"]
处理"bat": key="abt" → hash["abt"] = ["bat"]

最终:
hash = {
    "aet": ["eat", "tea", "ate"],
    "ant": ["tan", "nat"],
    "abt": ["bat"]
}

提取结果:
[["eat","tea","ate"], ["tan","nat"], ["bat"]]

我的收获:

  1. 排序识别异位词 - 字母相同,排序后一样
  2. 哈希表自动分组 - 相同key的自动归到一起
  3. vector vs pair - vector只有1个类型参数,pair有2个
  4. 数据结构理解 - vector<vector<T>> 是二维数组

数据结构补充:

// ✅ 正确
vector<int>                   // int数组
vector<vector<int>>           // 二维数组
pair<int, int>                // 两个int
vector<pair<int, int>>        // pair数组

// ❌ 错误
vector<int, int>              // 不存在!
vector<vector<int, int>>      // 不存在!

类型2:前缀和+哈希表

这是我最困惑的部分,搞了好几天才理解。

为什么要用哈希表?

一开始我的疑问:

  • 前缀和不是用数组吗?为什么要用哈希表?
  • hash[sum-k] 是什么意思?
  • 为什么能快速找到符合条件的子数组?

后来我理解了:

前缀和有两种应用场景:

1. 查询具体区间的和 → 用数组

vector<int> dp(n+1);
for(int i = 1; i <= n; i++) {
    dp[i] = dp[i-1] + nums[i-1];
}
// 查询 [left, right]
int sum = dp[right+1] - dp[left];

2. 找符合条件的子数组个数 → 用哈希表

unordered_map<int, int> hash;  // 前缀和 → 次数
hash[0] = 1;

int sum = 0, ret = 0;
for(int i = 0; i < n; i++) {
    sum += nums[i];
    if(hash.count(sum - k)) {
        ret += hash[sum - k];
    }
    hash[sum]++;
}
return ret;

关键区别:

  • 数组:知道下标,查询和
  • 哈希表:知道和,查询有多少个

LeetCode 560. 和为K的子数组 ⭐⭐⭐

题目: 统计有多少个子数组的和等于k。

我的理解过程:

最开始: 完全不懂,为什么要查 hash[sum-k]

后来理解:

如果 sum[i] - sum[j] = k
那么 sum[j] = sum[i] - k

所以在i位置,我要找之前有多少个前缀和是 sum[i] - k
这些前缀和对应的位置到i,和都是k

代码:

unordered_map<int, int> hash;  // 前缀和 → 次数
hash[0] = 1;  // 虚拟起点

int sum = 0, ret = 0;
for(auto x : nums) {
    sum += x;
    if(hash.count(sum - k)) {
        ret += hash[sum - k];
    }
    hash[sum]++;
}
return ret;

手动模拟:

输入:nums = [1, 1, 1], k = 2

初始:hash = {0: 1}, sum = 0, ret = 0

i=0, x=1:
  sum = 1
  查找 hash[1-2=-1],不存在
  hash[1]++
  hash = {0:1, 1:1}, ret = 0

i=1, x=1:
  sum = 2
  查找 hash[2-2=0],存在!次数是1
  ret += 1
  hash[2]++
  hash = {0:1, 1:1, 2:1}, ret = 1

i=2, x=1:
  sum = 3
  查找 hash[3-2=1],存在!次数是1
  ret += 1
  hash[3]++
  hash = {0:1, 1:1, 2:1, 3:1}, ret = 2

结果:2
解释:[1,1] 和 [1,1] 两个子数组

关键点:

  1. hash[0] = 1 表示"前0个元素的和是0",处理从头开始的子数组
  2. 先查找后插入,不会重复统计
  3. hash里存的是历史所有前缀和及其次数

LeetCode 974. 和可被K整除的子数组 ⭐⭐⭐

题目: 统计有多少个子数组的和能被k整除。

我的错误思路:

一开始我想用循环查找 sum - k, sum - 2k, sum - 3k

// ❌ 错误代码
for(int i = 1; i*k < sum; i++) {
    if(hash.count(sum - k*i)) ret += hash[sum - k*i];
}

问题:

  1. 循环条件 i*k < sum 不对,sum可能是负数
  2. 会漏掉很多情况
  3. 时间复杂度变成O(n²)

正确思路:

如果 (sum[i] - sum[j]) % k == 0
那么 sum[i] % k == sum[j] % k

只要两个前缀和的余数相同,它们之间的子数组就能被k整除

代码:

unordered_map<int, int> hash;  // 余数 → 次数
hash[0] = 1;

int sum = 0, ret = 0;
for(auto x : nums) {
    sum += x;
    int r = (sum % k + k) % k;  // 处理负数
    if(hash.count(r)) {
        ret += hash[r];
    }
    hash[r]++;
}
return ret;

关键点:

  1. 存的是余数,不是前缀和本身
  2. 负数取模:(sum % k + k) % k
    • C++中负数取模结果可能是负数
    • 例如:-1 % 5 = -1,应该是4
    • (-1 % 5 + 5) % 5 = 4

为什么这样能找到所有情况?

例如:sum = 14, k = 5

我之前的错误方法:查找 hash[9], hash[4]
漏掉了:hash[19], hash[24], ...

正确方法:查找 hash[14%5=4]
找到所有余数为4的前缀和,无论它们是4、9、14、19、24...

LeetCode 525. 连续数组 ⭐⭐⭐

题目: 找到含有相同数量0和1的最长子数组。

思维转换: 把0看作-1,问题变成:找和为0的最长子数组。

代码:

unordered_map<int, int> hash;  // 前缀和 → 第一次出现的位置
hash[0] = -1;  // 虚拟起点

int sum = 0, maxLen = 0;
for(int i = 0; i < n; i++) {
    sum += (nums[i] == 1 ? 1 : -1);
    
    if(hash.count(sum)) {
        maxLen = max(maxLen, i - hash[sum]);
    } else {
        hash[sum] = i;  // 只记录第一次出现的位置
    }
}
return maxLen;

关键点:

  1. 只记录第一次出现的位置 - 为了让长度最大
  2. hash[0] = -1 - 处理从头开始的子数组

手动模拟:

输入:nums = [0, 1, 0]
转换后:[-1, 1, -1]

初始:hash = {0: -1}, sum = 0, maxLen = 0

i=0, nums[0]=0 → -1:
  sum = -1
  hash中没有-1
  hash[-1] = 0
  hash = {0:-1, -1:0}, maxLen = 0

i=1, nums[1]=1 → 1:
  sum = 0
  hash中有0!位置是-1
  maxLen = max(0, 1-(-1)) = 2
  不更新hash(保持第一次出现的位置)
  hash = {0:-1, -1:0}, maxLen = 2

i=2, nums[2]=0 → -1:
  sum = -1
  hash中有-1!位置是0
  maxLen = max(2, 2-0) = 2
  不更新hash
  hash = {0:-1, -1:0}, maxLen = 2

结果:2
解释:[0,1] 或 [1,0] 长度为2

我踩的坑总结

1. 语法错误(基础但致命)

单引号vs变量
// ❌ 错误
hash['s[i]' - 'a']++  // 's[i]'是多字符常量

// ✅ 正确
hash[s[i] - 'a']++    // s[i]是变量
数组类型选择
// ❌ 错误
char hash[26] = {0};  // char范围太小

// ✅ 正确
int hash[26] = {0};   // int足够存计数
循环变量笔误
// ❌ 错误
for(int i = 0; i < n; i++) {
    hash[s[1]]++;  // 永远是第二个字符
}

// ✅ 正确
for(int i = 0; i < n; i++) {
    hash[s[i]]++;  // 循环变量i
}

2. 数组比较方式

int hash1[26] = {0}, hash2[26] = {0};

// ❌ 错误
if(hash1 == hash2) return true;  // 比较的是地址

// ✅ 正确
for(int i = 0; i < 26; i++) {
    if(hash1[i] != hash2[i]) return false;
}
return true;

为什么?

  • 数组名是指针,hash1 == hash2 比较的是地址
  • unordered_map可以直接用 == 比较
  • 这是C++ STL容器的便利性

3. abs()括号位置 ⭐⭐⭐

// ❌ 错误
abs(hash[nums[i]] - i <= k)
// 计算顺序:
// 1. hash[nums[i]] - i <= k  → bool(true/false)
// 2. abs(bool)                → abs(0或1)
// 完全错了!

// ✅ 正确
abs(hash[nums[i]] - i) <= k
// 计算顺序:
// 1. hash[nums[i]] - i       → 差值
// 2. abs(差值)               → 绝对值
// 3. 绝对值 <= k             → 判断

教训: 涉及运算符优先级时,务必用括号明确表达意图!


4. 更新下标的必要性

// LeetCode 219
for(int i = 0; i < n; i++) {
    if(hash.count(nums[i]) && abs(hash[nums[i]] - i) <= k)
        return true;
    hash[nums[i]] = i;  // 必须每次都更新!
}

为什么?

  • 只有保存最近一次的下标,才能找到最小的距离
  • 例如:nums = [1, 0, 1, 1], k = 1
    • i=2: 距离i=0太远,但更新后
    • i=3: 距离i=2刚好满足

5. 数据结构理解

vector只有1个类型参数
// ✅ 正确
vector<int>                   // int数组
vector<string>                // string数组
vector<vector<int>>           // 二维数组

// ❌ 错误
vector<int, int>              // 不存在!
vector<vector<int, int>>      // 不存在!
pair有2个类型参数
// ✅ 正确
pair<int, int>                // 两个int
vector<pair<int, int>>        // pair数组
为什么不用pair<string, string>?
  • pair只能存2个值,固定的
  • 每组异位词数量是不固定的,可能1个、2个、3个…
  • 所以要用 vector<string>,可以存任意多个

典型模板总结

模板1:边遍历边查找(两数之和)

unordered_map<int, int> hash;  // <元素值, 下标>
for(int i = 0; i < n; i++) {
    int target = k - nums[i];
    if(hash.count(target)) {
        // 找到了
        return {hash[target], i};
    }
    hash[nums[i]] = i;  // 先查找后插入
}

关键: 先查找后插入,避免找到自己


模板2:统计频次

unordered_map<T, int> hash;  // <元素, 次数>
for(auto x : arr) {
    hash[x]++;
}

模板3:加减抵消法(字符重排)

int hash[26] = {0};
// s1加,s2减
for(int i = 0; i < n; i++) {
    hash[s1[i] - 'a']++;
    hash[s2[i] - 'a']--;
}

// 检查是否全为0
for(int i = 0; i < 26; i++) {
    if(hash[i] != 0) return false;
}
return true;

模板4:排序识别(字母异位词)

unordered_map<string, vector<string>> hash;
for(auto& s : strs) {
    string key = s;
    sort(key.begin(), key.end());  // 排序后作为key
    hash[key].push_back(s);         // 分组
}

// 提取结果
vector<vector<string>> ret;
for(auto& [k, v] : hash) {
    ret.push_back(v);
}

模板5:前缀和+哈希表(子数组个数)

unordered_map<int, int> hash;  // 前缀和/余数 → 次数
hash[0] = 1;  // 虚拟起点

int sum = 0, ret = 0;
for(auto x : nums) {
    sum += x;
    
    // 查找符合条件的前缀和
    if(hash.count(sum - k)) {  // 或其他条件
        ret += hash[sum - k];
    }
    
    hash[sum]++;  // 或 hash[sum%k]++
}
return ret;

变体:

  • 和为k:查找 hash[sum - k]
  • 整除k:查找 hash[(sum%k+k)%k],存余数
  • 和为0:查找 hash[sum]

模板6:前缀和+哈希表(最长子数组)

unordered_map<int, int> hash;  // 前缀和 → 第一次出现的位置
hash[0] = -1;  // 虚拟起点

int sum = 0, maxLen = 0;
for(int i = 0; i < n; i++) {
    sum += nums[i];
    
    if(hash.count(sum)) {
        maxLen = max(maxLen, i - hash[sum]);
    } else {
        hash[sum] = i;  // 只记录第一次
    }
}
return maxLen;

关键: 只记录第一次出现的位置,让长度最大


容器选择决策树

需要存储什么?
├─ 只判断存在性 
│  └─ unordered_set<T>
│
├─ 需要统计次数
│  └─ unordered_map<T, int>
│
├─ 元素与下标
│  └─ unordered_map<T, int>
│
├─ 前缀和问题
│  ├─ 查询区间和 → vector<int>(数组)
│  ├─ 子数组个数 → unordered_map<int, int>(前缀和→次数)
│  └─ 最长子数组 → unordered_map<int, int>(前缀和→位置)
│
└─ 分组(嵌套)
   └─ unordered_map<T, vector<T>>

我的理解

1. 哈希表的本质

用空间换时间:

  • 暴力法:O(n²),遍历查找
  • 哈希表:O(n),O(1)查找

建立映射关系:

  • 元素 → 下标
  • 元素 → 次数
  • 元素 → 位置
  • 特征 → 一组元素

2. "边遍历边查找"的精髓

不需要:

  1. 先全部插入
  2. 再遍历查找

而是:

  1. 遍历的同时查找
  2. 查找完再插入
  3. 一次遍历搞定

3. 前缀和+哈希表的理解

为什么不用数组?

  • 数组:知道下标,查询和
  • 哈希表:知道和,查询个数/位置

hash里存什么?

  • 找个数:存 “前缀和 → 次数”
  • 找长度:存 “前缀和 → 位置”

查什么?

  • 和为k:查 hash[sum - k]
  • 整除k:查 hash[sum % k](存余数)
  • 和为0:查 hash[sum]

4. 容器选择的关键

问自己三个问题:

  1. 我要存什么?(决定value的类型)
  2. 我用什么作为key?(决定key的类型)
  3. 一个key对应几个value?(决定用set还是map)

我的薄弱环节

  1. 语法细节 - 单引号、数组类型、循环变量(已克服)
  2. 数组比较 - 不能直接用 ==(已理解)
  3. 运算符优先级 - abs()括号位置(已掌握)
  4. 数据结构理解 - vector vs pair(已搞懂)
  5. ⚠️ 前缀和+哈希表 - 有时还是要想一会儿(需要继续练)

后记

从Day01第一次见哈希表,到Day19完全掌握,用了17天。

中间经历了:

  • Day01:懵懂,只知道"快"
  • Day07-08:前缀和+哈希表,很迷糊
  • Day19:5道题突破,终于理解了

最大的收获:

  1. 容器选择 - 知道什么时候用set、map、数组
  2. “边遍历边查找” - 哈希表的核心技巧
  3. 前缀和+哈希表 - 从困惑到理解
  4. 踩坑经验 - 语法细节、运算符优先级

现在看到哈希表的题,能很快判断:

  • 用什么容器
  • 存什么、查什么
  • 怎么避免重复

哈希表这个专题,算是真的理解了。

更多推荐