📖 哈希表专题怎么用?
哈希表用空间换时间——把 O(n²) 的查找降到 O(1)。面试中凡是"快速查找/计数"的题,第一反应就是哈希表。
本专题包含 4 道题,都是外企面试高频真题。每道题按统一结构讲解:
- 题目描述 — 用人话翻译,不用读三遍才懂
- 生活类比 — 把抽象问题变成熟悉的生活场景
- 暴力思路 → 优化思路 — 面试官想看到的思考过程
- IDE 代码 — 带注释的 JS 实现 + 行号
- 复杂度分析 — 时间/空间,面试必问
- 面试加分项 — 能说出来的额外思考
复习建议:先看生活类比理解问题本质,再看思路建立解题框架,最后手写代码 3 遍形成肌肉记忆。面试时先口述思路,确认无误后再写代码。
1两数之和 (Two Sum) Easy
题目(人话版)
给你一个数组 [2, 7, 11, 15] 和一个目标值 9。找到数组里哪两个数加起来等于 9,返回它们的下标。
答案:2 + 7 = 9,所以返回 [0, 1](2 在位置 0,7 在位置 1)。
💡 生活类比:你兜里有几张钞票 [2元, 7元, 11元, 15元],你想凑出 9 元买杯咖啡。怎么找?
最笨的方法:两张两张试(暴力法)。聪明的方法:每拿出一张,先算"我还需要多少"(9 - 2 = 还差 7),然后看 7 在不在兜里(哈希表)。
暴力思路(O(n²))
两个 for 循环,每对数字都试一遍。能做出来,但面试官会追问"能不能更快?"
优化思路:哈希表(O(n))
遍历到 2
→
还差 9-2=7
→
7 在 Map 里吗?不在
→
存入 Map{2:0}
遍历到 7
→
还差 9-7=2
→
2 在 Map 里吗?在!位置 0
→
返回 [0, 1]
twoSum.js
function twoSum(nums, target) {
const map = new Map(); // 哈希表:存"值→下标"
for (let i = 0; i < nums.length; i++) {
const need = target - nums[i]; // 我还差多少?
if (map.has(need)) {
return [map.get(need), i]; // 找到了!
}
map.set(nums[i], i); // 没找到,把自己存进去
}
}
| 方法 | 时间 | 空间 | 面试评价 |
| 暴力双重循环 | O(n²) | O(1) | 能做出来,但不够 |
| 哈希表一次遍历 | O(n) | O(n) | ✅ 标准答案 |
面试加分项:主动说出"哈希表用空间换时间——多用了 O(n) 空间存 Map,但把时间从 O(n²) 降到 O(n)。" 然后追问:"如果数组是有序的,可以用双指针做到 O(1) 空间,但需要先排序 O(n log n)。" 这两句话一出,面试官就知道你真的懂。
2有效的字母异位词 (Valid Anagram) Easy
题目(人话版)
判断两个字符串是不是"字母异位词"——用的字母一模一样,只是顺序不同。"listen" 和 "silent" → ✅。"hello" 和 "world" → ❌。
💡 生活类比:就像两副拼图——碎片完全一样,只是拼出来的图案不同。验证方法:把两副都拆碎,数每种碎片有多少个,完全一样就是异位词。
方法一:排序比较法
anagram-sort.js
function isAnagram(s, t) {
return s.split('').sort().join('') === t.split('').sort().join('');
}
// "listen" → sort → "eilnst"
// "silent" → sort → "eilnst" 相同!✅
一行代码,但排序要 O(n log n) 时间。
方法二:哈希表计数法(O(n))
anagram-count.js
function isAnagram(s, t) {
if (s.length !== t.length) return false;
const count = {}; // 字母 → 出现次数
for (const char of s) count[char] = (count[char] || 0) + 1;
for (const char of t) {
if (!count[char]) return false; // 这个字母不够了
count[char]--;
}
return true;
}
时间 O(n),空间 O(1)(只有 26 个字母,哈希表大小固定)。
3数组去重/判断有重复 (Contains Duplicate) Easy
题目(人话版)
判断数组里有没有重复元素。[1,2,3,1] → true(1 重复了)。[1,2,3,4] → false。
💡 生活类比:就像点名——每个人喊到的时候,你在花名册上打个勾。如果有人喊到时你发现已经勾过了——重复了!
方法一:Set 一行法(最简单)
hasDuplicate-set.js
function containsDuplicate(nums) {
// Set 自动去重,去重后长度变短了 = 有重复
return new Set(nums).size !== nums.length;
}
方法二:边遍历边检查(可以提前退出)
hasDuplicate-early.js
function containsDuplicate(nums) {
const seen = new Set();
for (const num of nums) {
if (seen.has(num)) return true; // 发现重复!立刻退出
seen.add(num);
}
return false;
}
// 优势:如果第2个就重复了,不用遍历完整个数组
两种都是 O(n) 时间 O(n) 空间。第二种可以提前退出。
面试追问:"能不能 O(1) 空间?" 答:先排序 O(n log n),然后遍历看相邻是否相同 O(1) 空间。但排序会改变原数组,要和面试官确认。
4第一个唯一字符 (First Unique Character) Easy
题目(人话版)
找字符串里第一个只出现一次的字符,返回它的下标。"loveleetcode" → 答案 2('v' 是第一个只出现一次的,下标 2)。
💡 生活类比:你在一群人里找"第一个没双胞胎的人"。怎么做?先数每个人有几个双胞胎(统计频率),然后再从左到右走一遍,第一个频率=1的就是答案。
思路:两遍遍历
第一遍:统计每个字母出现次数→
第二遍:找到第一个次数=1的
firstUniqChar.js
function firstUniqChar(s) {
const count = {};
// 第一遍:统计频率
for (const char of s) {
count[char] = (count[char] || 0) + 1;
}
// 第二遍:找第一个频率=1的
for (let i = 0; i < s.length; i++) {
if (count[s[i]] === 1) return i;
}
return -1;
}
// "loveleetcode"
// count: {l:2, o:2, v:1, e:4, t:1, c:1, d:1}
// 第一个 count=1 的是 v(下标 2)
时间 O(n),空间 O(1)(26 个字母,哈希表大小固定)。
为什么是两遍遍历?因为"第一个"是有顺序的——如果你只统计频率,不知道哪个在前哪个在后。必须第二遍按原始顺序走一遍,才能找到"第一个"符合条件的。
📋 哈希表专题速查表
| # | 题目 | 难度 |
| 1 | 两数之和 (Two Sum) | Easy |
| 2 | 有效的字母异位词 (Valid Anagram) | Easy |
| 3 | 数组去重/判断有重复 (Contains Duplicate) | Easy |
| 4 | 第一个唯一字符 (First Unique Character) | Easy |
考点核心:哈希表专题的核心就是"用对工具,降复杂度"。
面试时拿到题先想:"这道题能用哈希表吗?"
如果能,先说思路再写代码,面试官就知道你的分析能力。
🔗 其他考点专题
→ 双指针专题:回文检测 (Valid Palindrome), 反转字符串 (Reverse String), 合并两个有序数组 (Merge Sorted Arrays), 移动零 (Move Zeroes)
→ 动态规划专题:斐波那契数列 (Fibonacci), 爬楼梯 (Climbing Stairs), 最大子序和 (Max Subarray)
→ 位运算专题:只出现一次的数字 (Single Number), 缺失数字 (Missing Number)
→ 栈与滑动窗口专题:有效括号 (Valid Parentheses), 最长不重复子串 (Longest Substring)
→ 综合技巧专题:FizzBuzz, 买卖股票的最佳时机 (Best Time to Buy/Sell Stock), 多数元素 (Majority Element), 旋转数组 (Rotate Array), 二分查找 (Binary Search)
← 返回图解算法与模式