1. 算法训练营第六天题目解析
今天要啃下四道经典题目:字母异位词判断、数组交集、快乐数验证以及两数之和。这些题目覆盖了哈希表应用的三种典型场景——频率统计、集合操作和索引记录,正是算法入门阶段需要突破的关键关卡。我在刷题过程中发现,很多同学卡在"知道该用哈希表但写不出完整实现"的阶段,这次我们就用Python和Java两种语言,从暴力解法到最优解逐层拆解。
1.1 题目核心考点拆解
先看四道题的本质要求:
- 242题:判断两个字符串的字符组成是否完全相同(顺序无关)
- 349题:找出两个数组中都存在的唯一元素
- 202题:验证数字经过特定运算能否收敛到1
- 1题:在数组中快速找到和为目标值的两个元素
这四道题看似类型各异,实则都在考察哈希结构在不同场景下的灵活应用。比如242题需要统计字符频率,349题需要快速查询元素存在性,而1题则需要记录数值与索引的映射关系。
2. 242.有效的字母异位词
2.1 暴力解法与优化思路
最直观的解法是对两个字符串排序后比较:
python复制def isAnagram(s: str, t: str) -> bool:
return sorted(s) == sorted(t)
时间复杂度O(nlogn),主要消耗在排序操作。面试时如果先写这个解法,一定要说明可以优化为O(n)的方案。
2.2 哈希表频率统计法
更高效的解法是使用数组模拟哈希表(已知字符范围时):
python复制def isAnagram(s: str, t: str) -> bool:
if len(s) != len(t):
return False
count = [0] * 26
for ch in s:
count[ord(ch) - ord('a')] += 1
for ch in t:
count[ord(ch) - ord('a')] -= 1
if count[ord(ch) - ord('a')] < 0:
return False
return True
关键点:先比较长度可提前终止;数组比字典更节省空间;遇到负数立即返回避免完整遍历
Java版本需要注意字符编码处理:
java复制public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) return false;
int[] table = new int[26];
for (char c : s.toCharArray()) {
table[c - 'a']++;
}
for (char c : t.toCharArray()) {
table[c - 'a']--;
if (table[c - 'a'] < 0) return false;
}
return true;
}
3. 349.两个数组的交集
3.1 集合操作的标准解法
这道题完美展示了集合(Set)的使用场景:
python复制def intersection(nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
但要注意三个细节:
- 结果需要去重
- 不考虑输出顺序
- 时间复杂度O(m+n),空间复杂度O(m+n)
3.2 处理大数据集的优化方案
当某个数组非常大时,可以优化空间占用:
python复制def intersection(nums1, nums2):
# 假设nums1比nums2大很多
small_set = set(nums2)
return [x for x in nums1 if x in small_set]
这样空间复杂度降为O(min(m,n))。Java中需要注意HashSet的初始化方式:
java复制public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> set1 = new HashSet<>();
Set<Integer> resultSet = new HashSet<>();
for (int num : nums1) set1.add(num);
for (int num : nums2) if (set1.contains(num)) resultSet.add(num);
return resultSet.stream().mapToInt(i->i).toArray();
}
4. 202.快乐数
4.1 数学规律与终止条件
快乐数的验证过程存在两个关键特征:
- 收敛到1的数是快乐数
- 不快乐的数会进入4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4的循环
因此可以用哈希表记录出现过的数字来检测循环:
python复制def isHappy(n: int) -> bool:
seen = set()
while n != 1 and n not in seen:
seen.add(n)
n = sum(int(d)**2 for d in str(n))
return n == 1
4.2 空间复杂度优化:快慢指针法
类比链表环检测,可以用数学方法替代哈希表:
python复制def isHappy(n: int) -> bool:
def get_next(number):
total = 0
while number > 0:
digit = number % 10
total += digit * digit
number = number // 10
return total
slow = n
fast = get_next(n)
while fast != 1 and slow != fast:
slow = get_next(slow)
fast = get_next(get_next(fast))
return fast == 1
这种方法将空间复杂度从O(logn)降到O(1),但实现难度稍高。
5. 1.两数之和
5.1 暴力解法的局限性
双重循环的O(n²)解法在面试中只能作为起点:
python复制def twoSum(nums: List[int], target: int) -> List[int]:
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
return []
5.2 哈希表的一次遍历优化
使用字典存储"数值:索引"的映射关系:
python复制def twoSum(nums: List[int], target: int) -> List[int]:
num_map = {}
for i, num in enumerate(nums):
complement = target - num
if complement in num_map:
return [num_map[complement], i]
num_map[num] = i
return []
注意点:先查询后插入可以避免重复使用同一元素;Java版本要注意Integer的缓存机制
Java实现需要注意HashMap的泛型参数:
java复制public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] {map.get(complement), i};
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No solution");
}
6. 哈希表应用场景深度总结
6.1 三种典型使用模式
-
频率统计:使用字典/数组记录元素出现次数(242题)
- 数组适用于有限字符集(ASCII/字母)
- 字典适用于任意可哈希类型
-
存在性检查:使用集合快速判断元素是否存在(349题)
- 去重场景首选Set
- 大数据量时注意空间优化
-
索引映射:存储值与位置的对应关系(1题)
- 适用于需要回溯索引的场景
- 注意处理重复值的情况
6.2 不同语言的实现差异
| 操作 | Python | Java |
|---|---|---|
| 哈希表声明 | dict = {} | HashMap<Integer, ...> |
| 集合声明 | set() | HashSet |
| 字符转ASCII | ord('a') | (int)'a' |
| 遍历 | for k,v in dict | for (Map.Entry ...) |
7. 常见错误与调试技巧
7.1 边界条件处理
- 242题:未先比较字符串长度直接统计
- 349题:未考虑空数组输入情况
- 202题:未处理n=0的特殊情况
- 1题:未检查无解的情况(应抛出异常)
7.2 性能优化检查点
-
避免不必要的哈希表操作:
python复制# 错误示范:重复计算 if x in dict: dict[x] += 1 else: dict[x] = 1 # 正确写法: dict[x] = dict.get(x, 0) + 1 -
选择合适的数据结构:
- 当键是连续整数时,数组比字典更高效
- 只需要判断存在性时,Set比Dict更合适
-
注意自动装箱问题(Java):
java复制// 低效写法 map.containsKey(Integer.valueOf(key)); // 优化后 map.containsKey(key); // 利用自动装箱
8. 扩展训练建议
掌握这四道题后,可以尝试以下变种题目巩固哈希表技能:
- 383.赎金信(242题进阶)
- 350.两个数组的交集II(349题变种,需考虑出现次数)
- 454.四数相加II(两数之和的扩展)
- 205.同构字符串(需要双向映射检查)
在实际工程中,哈希表常用于缓存实现、唯一性校验等场景。比如用户注册时的用户名查重,就可以直接套用349题的集合解法。我在处理电商系统的商品特征匹配时,就大量运用了这种思路,将O(n²)的匹配过程优化到线性时间复杂度。