一、解题思维总结
1. 何时使用哈希表?
| 场景 | 解法 |
|---|---|
| 判断元素是否存在 | Set |
| 统计元素频次 | Map/Object |
| 查找满足条件的元素对 | Map + 差值技巧 |
| 分组(异位词等) | Map + 排序键 |
| 需要 O(1) 查询 | 哈希表替代数组遍历 |
2. 复杂度分析
- 哈希表操作:O(1) 平均时间复杂度
- 排序:O(n log n)
- 空间换时间:用额外 O(n) 空间换取 O(n) 时间优化
3. 常用技巧
- 对象初始化:
count[char] = (count[char] || 0) + 1 - 快速去重:
[...new Set(array)]或Array.from(new Set(array)) - 对象遍历:
Object.entries()、Object.keys()、Object.values() - 数组填充:
new Array(n).fill(0)
二、哈希表基础与应用
技巧一:使用 Set 进行快速查找
适用场景:需要判断元素是否已存在、去重等场景
核心优势:
Set.has()和Set.add()都是 O(1) 时间复杂度- 相比数组的
includes()或indexOf()(O(n))效率更高
典型例题:存在重复元素
function containsDuplicate(nums) {
const seen = new Set();
for (const num of nums) {
if (seen.has(num)) {
return true;
}
seen.add(num);
}
return false;
}
Map vs Set 对比:
| 操作 | Map | Set |
|---|---|---|
| 添加 | set(key, value) | add(key) |
| 查找 | has(key) | has(key) |
| 获取 | get(key) | - |
| 删除 | delete(key) | delete(key) |
| 清空 | clear() | clear() |
| 大小 | size | size |
技巧二:哈希表记录频次
适用场景:统计字符/数字出现次数、比较两个集合的元素频次
核心要点:
- 遍历数组时,计算目标值与当前元素的差值
- 在哈希表中查找差值是否存在
- 将当前元素存入哈希表供后续查找
- 用空间换时间,将 O(n²) 降为 O(n)
典型例题1:有效的字母异位词
function isAnagram(s, t) {
if (s.length !== t.length) return false;
const count = {};
for (let char of s) {
count[char] = (count[char] || 0) + 1;
}
for (let char of t) {
if (!count[char]) return false;
count[char]--;
}
return true;
}
进阶技巧 - 使用数组优化(固定字符集):
function isAnagram(s, t) {
if (s.length !== t.length) return false;
const counter = new Array(26).fill(0);
for (let char of s) {
counter[char.charCodeAt(0) - 'a'.charCodeAt(0)]++;
}
for (let char of t) {
const index = char.charCodeAt(0) - 'a'.charCodeAt(0);
if (--counter[index] < 0) return false;
}
return true;
}
典型例题2:前 K 个高频元素
function topKFrequent(nums, k) {
const frequencyMap = {};
nums.forEach(num => {
frequencyMap[num] = (frequencyMap[num] || 0) + 1;
});
const freqArray = Object.entries(frequencyMap)
.map(([num, freq]) => [parseInt(num), freq]);
freqArray.sort((a, b) => b[1] - a[1]);
return freqArray.slice(0, k).map(item => item[0]);
}
技巧三:哈希表实现两数之和模式
适用场景:在数组中查找满足特定条件的元素对
典型例题:两数之和
function twoSum(nums, target) {
const hashTable = {};
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (complement in hashTable) {
return [hashTable[complement], i];
}
hashTable[nums[i]] = i;
}
return null;
}
技巧四:排序后作为哈希键
适用场景:需要将变位词/异位词分组
核心思路:将字符串排序后,异位词会得到相同的结果,以此作为哈希键
典型例题:字母异位词分组
function groupAnagrams(strs) {
const map = {};
strs.forEach(str => {
const sortedStr = str.split('').sort().join('');
if (!map[sortedStr]) {
map[sortedStr] = [];
}
map[sortedStr].push(str);
});
return Object.values(map);
}
三、数组技巧
技巧一:前缀积与后缀积
适用场景:需要计算"除自身以外"的累积结果
核心要点:
- 从左到右计算前缀积
- 从右到左计算后缀积,同时与前缀积相乘得到结果
- 初始值为 1(1 与任何数相乘等于原数)
典型例题:除自身以外数组的乘积
function productExceptSelf(nums) {
const n = nums.length;
const result = [];
let prefix = 1;
for (let i = 0; i < n; i++) {
result[i] = prefix;
prefix *= nums[i];
}
let suffix = 1;
for (let i = n - 1; i >= 0; i--) {
result[i] *= suffix;
suffix *= nums[i];
}
return result;
}
技巧二:排序 + 遍历
适用场景:需要找出连续序列、区间问题等
典型例题:最长连续序列
function longestConsecutive(nums) {
if (nums.length === 0) return 0;
const arr = Array.from(new Set(nums)).sort((a, b) => a - b);
let maxLen = 0;
let curLen = 1;
for (let i = 1; i < arr.length; i++) {
if (arr[i] === arr[i - 1] + 1) {
curLen++;
} else {
maxLen = Math.max(maxLen, curLen);
curLen = 1;
}
}
return Math.max(maxLen, curLen);
}
四、易错点提醒
-
sort() 默认排序:默认按字符串 Unicode 排序,数字需要传入比较函数
[10, 2, 1].sort(); // [1, 10, 2] ❌ [10, 2, 1].sort((a, b) => a - b); // [1, 2, 10] ✅ -
哈希表 vs 数组选择:
- 字符集固定且小(如26个字母)→ 用数组
- 字符集不固定或很大 → 用哈希表
-
Object 与 Map 的选择:
- 键为字符串/数字 → Object 更简洁
- 键为对象/需要保持插入顺序 → 用 Map
-
遍历对象的安全检查:
for (let key in obj) { if (obj.hasOwnProperty(key)) { // 处理逻辑 } }