力扣热题100学习笔记 - 哈希
此博客用于记录刷题过程中的思路与代码,方便日后回顾。
每道题我会先自己尝试,再参考题解优化,欢迎交流指正。
目录
- 哈希篇
- [1. 两数之和](#1. 两数之和)
- [49. 字母异位词分组](#49. 字母异位词分组)
- [128. 最长连续序列](#128. 最长连续序列)
哈希篇
哈希表的核心思想:用空间换时间,将查找时间从 O(n) 降到 O(1)。
1. 两数之和
题目描述
给定一个整数数组 nums 和一个目标值 target,请找出数组中和为目标值的两个整数,返回它们的数组下标。
示例
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:nums[0] + nums[1] = 2 + 7 = 9
思路
- 核心问题:如何快速找到与当前数匹配的另一个数?暴力法需要 O(n²),用哈希表优化查找过程。
- 哈希表优化 :遍历数组时,用 HashMap 存储已经遍历过的元素(key 为元素值,value 为下标)。对于当前元素
nums[i],计算所需的补数complement = target - nums[i],如果补数已在哈希表中,说明找到了答案。 - 关键细节 :先判断补数是否存在,再将当前元素存入哈希表。这样可以避免同一个元素被使用两次(例如
target = 6, nums = [3, 3],第一个 3 存入前 map 为空,判断时找不到,存入;第二个 3 判断时 map 中有第一个 3,成功匹配)。
复杂度
- 时间:O(n),只需遍历一次数组,哈希表的查找和插入都是 O(1)
- 空间:O(n),最坏情况下需要存储 n 个元素
Java代码
java
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if (nums == null || nums.length == 0) {
return res;
}
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
res[0] = map.get(complement);
res[1] = i;
return res;
}
map.put(nums[i], i);
}
return res;
}
}
易错点/注意
- 返回下标顺序 :题目没有要求顺序,但通常返回
[较小下标, 较大下标],这里先找到的补数下标一定小于当前下标 - 同一元素不能用两次 :先判断再存入,避免当前元素和自己匹配(如
target=4, nums=[2]不会误判) - 边界处理:数组为空或长度为 0 时直接返回空数组
- 找到后及时返回:找到答案后立即返回,不需要继续遍历
49. 字母异位词分组
题目描述
给定一个字符串数组,将字母异位词(由相同字母重排组成的词)组合在一起。
示例
输入:strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:[["bat"],["nat","tan"],["ate","eat","tea"]]
核心思路:用字符频次数组作为 key,将字母异位词归为一组
处理流程:
- 遍历每个字符串,统计 26 个字母的出现次数
- 将频次数组转为字符串作为 HashMap 的 key
- 相同 key 的字符串属于字母异位词,放入同一列表
返回结果:所有分组的列表
Java代码
java
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
HashMap<String, List<String>> map = new HashMap<>();
ArrayList<List<String>> res = new ArrayList<>();
for (String str : strs) {
// 生成指纹:统计每个字母出现次数
char[] num = new char[26];
char[] chars = str.toCharArray();
for (char c : chars) {
num[c - 'a']++;
}
String key = new String(num);
// 分组:将当前单词加入对应指纹的列表中
if (map.containsKey(key)) {
map.get(key).add(str);
} else {
ArrayList<String> list = new ArrayList<>();
list.add(str);
map.put(key, list);
res.add(list); // 新分组加入结果集
}
}
return res;
}
}
易错点/注意
- 指纹生成方式 :用
char[]而非int[],这样可以直接new String(num)转换为字符串作为 key,简洁高效 - 结果同步 :
res中存储的是 map 中列表的引用,不需要额外维护,修改 map 中的列表会自动反映到 res - 空字符串处理 :空字符串会生成全为 0 的
char数组,同样可以正确分组 - 返回类型 :返回
ArrayList<List<String>>符合List<List<String>>的类型要求
128. 最长连续序列
题目描述
给定一个未排序的整数数组,找出数字连续的最长序列的长度(要求 O(n) 时间复杂度)。
示例
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长连续序列是 [1,2,3,4],长度为 4。
思路
- 用 HashSet 存储所有数,实现 O(1) 的查找。
- 遍历每个数,只有当它是某个连续序列的起点 (即
num - 1不存在)时,才开始向后计数。 - 计数过程中不断查找
num + 1是否存在,直到断掉,更新最大长度。
复杂度
- 时间:O(n)
- 空间:O(n)
Java代码
java
class Solution {
public int longestConsecutive(int[] nums) {
// 边界条件检查
if (nums == null || nums.length == 0) {
return 0;
}
// 创建 HashSet,用于存储所有数字
// Set<Integer> 表示这是一个存储 Integer 类型的集合
// new HashSet<>() 创建了一个 HashSet 对象
Set<Integer> numSet = new HashSet<>();
// 将数组中的所有数字添加到 HashSet 中
for (int num : nums) {
numSet.add(num); // add() 方法添加元素
}
int maxLength = 0; // 记录最长序列长度
// 遍历 HashSet 中的每个数字
for (int num : numSet) {
// 关键逻辑:只有当 num-1 不存在时,num 才是序列的起点
// contains() 方法检查元素是否存在,返回 true 或 false
if (!numSet.contains(num - 1)) {
int currentNum = num;
int currentLength = 1; // 当前序列长度,至少包含自己
// 从起点开始,不断查找下一个连续的数字
// while 循环会一直执行,直到找不到下一个数字
while (numSet.contains(currentNum + 1)) {
currentNum++; // 移动到下一个数字
currentLength++; // 序列长度加 1
}
// 更新最大长度
// Math.max(a, b) 返回 a 和 b 中较大的那个
maxLength = Math.max(maxLength, currentLength);
}
}
return maxLength;
}
}
易错点/注意
- 为什么要判断起点?避免重复计数,每个序列只从最小数开始统计。
- 注意空数组的边界情况。
小结
| 题目 | 核心技巧 | 时间复杂度 | 空间复杂度 |
|---|---|---|---|
| 两数之和 | 哈希表存差值的下标 | O(n) | O(n) |
| 字母异位词分组 | 排序后作为 key | O(n×k log k) | O(n×k) |
| 最长连续序列 | HashSet + 找起点 | O(n) | O(n) |
刷题心得
- 哈希表类题目通常先想「能不能用 Map/Set 减少一次循环」。
- 遇到「连续」「子数组」类问题,多考虑 Set 或前缀和结合哈希。
- 基础薄弱时,先理解暴力解法,再逐步优化,不要一开始就追求最优解。
持续更新中,代码待补充。每道题我会在理解透彻后把代码填进来,并补充更多细节和坑点。