哈希的本质是用空间换时间,把查找从 O(n) 降到 O(1) 平均。
HashSet
存不存在 / 去重,用于判断某元素是否出现过、数组去重、集合交集
Set<Integer> set = new HashSet<>();
set.add(3); // 添加元素
set.add(3); // 重复元素不会加入
set.contains(3); // 判断是否存在
set.remove(3); // 删除元素
典型题:两数之和、数组去重、判断重复元素、两数组交集
HashMap
计数 / 映射,用于字符统计、频率统计、值到索引映射
Map<Integer, Integer> map = new HashMap<>();
for (int n : nums) {
// 如果 n 不存在,默认返回 0,然后 +1
map.put(n, map.getOrDefault(n, 0) + 1);
}
两个Integer代表泛型,指key 和 value 都是整数,map.put(n, map.getOrDefault(n, 0) + 1);这行代码是哈希计数的经典写法,意思是给n做出现次数统计。map.getOrDefault(n, 0)去map里取n的值,如果n不存在,就返回默认值0map是指数组)。因为我们要统计出现次数,每遇到一次就加1。map.put(n, ...)代表把更新后的次数放回map里,如果n原来不存在,会自动创建。
典型题:字母异位词、出现次数最多的元素、前缀和 + 哈希、两数之和
TreeMap / TreeSet
有序哈希,用于需要自动排序、范围查询
Set<Integer> set = new TreeSet<>();
Map<Integer, Integer> map = new TreeMap<>();
基本用法:
TreeSet<Integer> set = new TreeSet<>();
set.add(5);
set.add(3);
set.add(8);
set.add(3); // 重复元素不会加入
System.out.println(set); // [3, 5, 8] 自动升序
TreeMap<Integer, String> map = new TreeMap<>();map.put(3, "C");
map.put(1, "A");
map.put(2, "B");
map.put(2, "BB"); // key=2 更新 value
System.out.println(map); // {1=A, 2=BB, 3=C} 自动按 key 排序
LinkedHashMap / LinkedHashSet
保序哈希,用于保持插入顺序 + 哈希效率
Map<Integer, Integer> map = new LinkedHashMap<>();
Set<Integer> set = new LinkedHashSet<>();
基本用法:
Set<Integer> set = new LinkedHashSet<>();
set.add(3);
set.add(1);
set.add(5);
set.add(1); // 重复元素不会加入
System.out.println(set); // [3, 1, 5] 插入顺序保持
Map<Integer, String> map = new LinkedHashMap<>();map.put(3, "C");
map.put(1, "A");
map.put(2, "B");
map.put(2, "BB"); // key=2 更新 value
System.out.println(map); // {3=C, 1=A, 2=BB} 插入顺序保持
哈希在算法中的 10 大经典用法
判断元素是否存在
Set<Integer> set = new HashSet<>(); if (set.contains(x)) {}
去重
Set<Integer> set = new HashSet<>(); for (int n : nums) { set.add(n); // HashSet 自动去重 }
统计频率
Map<Integer, Integer> map = new HashMap<>(); for (int n : nums) map.put(n, map.getOrDefault(n, 0) + 1);
字符串计数(异位词)
int[] cnt = new int[26]; for (char c : s.toCharArray()) cnt[c - 'a']++;
两数之和(值 → 下标)
Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int need = target - nums[i]; if (map.containsKey(need)) return new int[]{map.get(need), i}; map.put(nums[i], i); }
前缀和 + 哈希(子数组和为 k)
Map<Integer, Integer> map = new HashMap<>(); map.put(0, 1); int sum = 0, res = 0; for (int n : nums) { sum += n; res += map.getOrDefault(sum - k, 0); map.put(sum, map.getOrDefault(sum, 0) + 1); }
两数组交集
Set<Integer> set = new HashSet<>(); for (int n : nums1) set.add(n); Set<Integer> res = new HashSet<>(); for (int n : nums2) if (set.contains(n)) res.add(n);
判断重复元素
Set<Integer> set = new HashSet<>(); for (int n : nums) if (!set.add(n)) return true;
分组(分类)
Map<String, List<String>> map = new HashMap<>(); for (String s : strs) { String key = sort(s); map.computeIfAbsent(key, k -> new ArrayList<>()).add(s); }
最近最少使用缓存(LRU)
class LRUCache extends LinkedHashMap<Integer, Integer> { protected boolean removeEldestEntry(Map.Entry eldest) { return size() > capacity; } }
数组哈希 vs Map 哈希
当 key 范围固定小(如 a-z):
int[] hash = new int[26];
hash[c - 'a']++;
当 key 范围大或不连续:
Map<Integer, Integer> map = new HashMap<>();