代码随想录算法训练营到第六天,题单正好是四道哈希表经典题:242. 有效的字母异位词、349. 两个数组的交集、202. 快乐数、1. 两数之和。看题目数量不算多,两简单两中等,但刷完我发现这一天的内容密度其实很高。前几天的数组、链表题目都是靠双指针或遍历就能想明白的,从这天开始,刷题思维要第一次正式切换到"用额外空间换时间"的赛道上来,而且这一切换就回不去了。
如果你也在跟训练营、或者刚接触LeetCode,这四道题可以当做一个完整的哈希表入门单元来刷,因为它们的难度和用法刚好是一个递进关系:242告诉你什么时候该用数组当哈希表,349告诉你集合set怎么去重,202告诉你哈希判重能解决"循环检测"这类抽象问题,1. 两数之和则直接把HashMap推到你面前,让你从暴力循环思维正式转向哈希思维。这篇就把四道题的完整思路、代码实现和我在实际写的过程中踩过的坑都拆开讲一遍。
1. 第六天四道题的主线:从数组哈希到map的进阶路径
1.1 为什么是这四道题排在一起
先看这四道题放在同一天的内在逻辑。242是哈希表最朴素的形态——用一个固定大小的数组当计数器;349开始引入集合,因为需要去重;202表面上跟哈希表八竿子打不着,但本质是"检测一个序列是否出现重复",这也是set最擅长的事情;1. 两数之和则上升到map,因为它不光要判断"存不存在",还要把下标存下来。
所以你会看到,四道题正好对应了哈希结构的三种形态:
- 数组:适用于key取值范围明确且较小的场景
- HashSet:只需要判断"是否存在",不需要存额外信息
- HashMap:既要判断存在,还要取出对应的value
当你把这四种形态的适用边界都想清楚了,后面做哈希表专题的中等题,比如454题四数相加、383题赎金信,思路就会顺很多。我在训练营学到的最有用的一点就是:不要一上来就HashMap,先想清楚你要存的数据到底是什么,再决定用哪种结构。
1.2 什么题适合用哈希表:先看懂"查找"需求
哈希表解决的核心问题只有一个:把一个查找操作的时间复杂度从O(n)降到O(1)。注意,是均摊O(1),这点后面两数之和里还会再提。
判断一道题适不适用哈希表,我的经验是看它有没有"查找某个元素是否在某个集合中"的需求。242要查两个字符串的字符分布是否一致,349要查nums1里的元素是否在nums2中出现,202要查当前的平方和是否已经出现过,1要查target减去当前元素后剩下的数是否已经遍历过。四道题本质上全都是在做查找。
初学者最容易犯的错误是:看到一个数组题,第一反应是排序或者双指针,完全想不起来哈希表。其实判断标准很简单,如果你的算法里需要反复查询"某个元素在不在某个结构里",而且这个结构不会频繁增删,那哈希表就是第一选择。相反,如果要频繁遍历且对顺序有要求,就考虑别的办法。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. LeetCode 242 有效的字母异位词:数组哈希的模板题
2.1 暴力解法的局限
先看一眼题目:给定两个字符串s和t,判断t是否是s的字母异位词。所谓异位词,就是字母组成完全一样、只是排列顺序不同,比如"anagram"和"nagaram"就是一对。
我看到这道题第一反应是排序,把两个字符串都排一下序再比较是否相等,这当然可以做,时间复杂度是O(nlogn)。但这里有个隐含信息:题目限定字符串中只有小写字母,也就是字符集只有26个。当字符集被明确限定时,直接用数组统计词频是最优解,这也是很多题解最终采用的方案。
如果不用哈希结构,还可以两层循环挨个匹配,但那就是纯粹的O(n²)了,一旦字符串边长就会超时。这些解法不是不能实现,而是没有利用到题目给的特殊条件。
2.2 用26格数组统计词频的完整思路
核心思路:用数组record[26]记录每个字母出现的次数。遍历s,遇到哪个字母就给对应位置加1;再遍历t,遇到哪个字母就给对应位置减1。最后如果record数组里每个位置都正好是0,说明两个字符串的字符分布完全一致,返回true,否则返回false。
java复制class Solution {
public boolean isAnagram(String s, String t) {
int[] record = new int[26];
for (char c : s.toCharArray()) {
record[c - 'a']++;
}
for (char c : t.toCharArray()) {
record[c - 'a']--;
}
fo
