Day6 HashTable part1
定义部分来自<<代码随想录>>
Hash Table
Definition: 哈希表是根据关键码的值直接进行访问的数据结构,一般哈希表都是用来快速判断一个元素是否出现在集合里
eg.用名字查询是否在学校 -> Time Complexity: O(1)
如何将学生姓名映射到哈希表上 -> Hash Function
Hash Function
Definition: 通过hashCode把名字转化为数值, 一般hashcode是通过特定编码方式, 可以将其他数据格式转化为不同的数值
eg. 把学生名字映射为哈希表上的索引数字
如果hashCode得到的数值大于tableSize, 为了保证映射出来的索引数值都落在哈希表上 -> 对数值取模
如果学生数量大于tableSize -> 哈希碰撞
哈希碰撞
Definition: 映射到了同一索引下标
解决办法:
拉链法
发生冲突的元素都被存储在链表中
(数据规模是dataSize, 哈希表的大小为tableSize)
Notice: 选择适当的哈希表的大小 (数组空值 -> 浪费大量内存, 链表太长 -> 多余时间查找)
线性探测法
保证tableSize大于dataSize, 用哈希表中的空位来解决碰撞问题
常见的三种哈希结构
HashMap
HashMap是一种常用的哈希表实现,它使用键值对(Key-Value)的方式存储数据。它基于哈希函数将键映射到存储桶(buckets)上,这样可以快速地插入、删除和查找元素。HashMap允许空键和空值,但是不保证元素的顺序。
Time complexity: 平均查找、插入和删除操作 O(1)
import java.util.HashMap;
HashMap<Integer, String> hashMap = new HashMap<>();
hashMap.put(1, "One");
hashMap.put(2, "Two");
hashMap.put(3, "Three");
String value = hashMap.get(2); // 获取键为2的值,结果为 "Two"
Type | HashMap | TreeMap |
---|---|---|
底层数据结构 | HashTable O(1) | Red-Black Tree (一种自平衡二叉搜索树) O(logn) |
元素顺序 | 无序 位置由哈希值决定 | 有序性 元素按照键的自然顺序或指定的比较器顺序进行排序 |
键的要求 | key 必须是可哈希的 (有 hashCode() 和 equals() 方法) 允许键为 null | key 必须是可比较的 (实现 Comparable 接口或在构造时提供了比较器) 不允许 null 作为键 |
性能 | 高效的插入、删除和查找操作 但不需要保持有序的情况 | 需要有序遍历 (按照键的顺序) 以及范围查询的情况 |
HashSet
HashSet是基于HashMap实现的一种无重复元素的集合。它不允许重复的元素,并且不保证元素的顺序。HashSet是一种快速查找元素的数据结构,适用于需要存储唯一元素的情况。
Time complexity: 元素的存储和查找操作的时间复杂度是 O(1) 的平均情况
底层: HashSet 是通过构造一个 HashMap 来实现的,只不过在 HashSet 中,我们只关心键值(key)部分而忽略了值(value)部分。HashSet 中的元素实际上是作为 HashMap 中的 key 存储的。当我们调用 HashSet 的 add 方法时,实际上是将元素作为 HashMap 的 key 插入到 HashMap 中,而 value 部分使用一个常量对象。
import java.util.HashSet;
HashSet<String> hashSet = new HashSet<>();
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Orange");
boolean containsBanana = hashSet.contains("Banana"); // 检查集合中是否包含 "Banana",结果为 true
Type | HashSet | TreeSet |
同 hashMap and TreeMap | ||
Hashtable
Hashtable是一个早期的哈希表实现,它类似于HashMap,但是具有一些不同之处。Hashtable是线程安全的,但性能相对较差,而且不允许空键和空值。在多线程环境中,可以使用Hashtable来保证线程安全的操作。
import java.util.Hashtable;
Hashtable<String, Integer> hashtable = new Hashtable<>();
hashtable.put("One", 1);
hashtable.put("Two", 2);
hashtable.put("Three", 3);
int value = hashtable.get("Two"); // 获取键为 "Two" 的值,结果为 2
总结: 当我们要快速判断一个元素出现在集合里的时候, 就要考虑哈希法, 但其实是牺牲空间换取时间 -> 额外的set, map来存放数据
Leetcode242 Valid Anagram
题目链接: 有效的字母异位词
Method 1
两次 sort(s.begin(), s.end())
ascending order.
Method 2
两个字符串长度必须相同, 每个字符的数量也相同, 顺序可以不同. 可以用哈希表解决. 统计 s 字符串时, 各字符+1, 统计 t 字符串时, 各自符-1
Time Complexity: O(m+n)
Space Complexity: O(1)
class Solution {
public boolean isAnagram(String s, String t) {
HashMap<Character, Integer> result = new HashMap<>();
for (char x: s.toCharArray()){
result.put(x, result.getOrDefault(x,0) + 1);
}
for (char y: t.toCharArray()){
result.put(y, result.getOrDefault(y, 0) - 1);
}
for (int num: result.values()){
if (num != 0){
return false;
}
}
return true;
}
}
Method 3
提前定义数组大小, 确定相对位置
Notice: s.charAt(i)
得到的是String的第一位字符, s.charAt(i) - 'a'
表示和 a
的相对index
class Solution {
public boolean isAnagram(String s, String t) {
int[] record = new int[26];
for (int i = 0; i < s.length(); i++){
record[s.charAt(i) - 'a'] += 1;
}
for (int i = 0; i < t.length(); i++){
record[t.charAt(i) - 'a'] -= 1;
}
for (int num: record){
if (num != 0) return false;
}
return true;
}
}
Leetcode349 Intersection of Two Arrays
题目链接: Intersection
[Example]:
Input: nums1 = [1,2,2,1], nums2 = [2,2]
Output: [2]
一开始想仿照上题Method3, 但是发现无法确定数组的具体大小. 需要新建一个新的HashSet, 循环其中一个数组, 把里面的值加入set中
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
if (nums1.length == 0 || nums2.length == 0) return new int[0];
Set<Integer> set = new HashSet<>();
Set<Integer> result = new HashSet<>();
for (int num: nums1){
set.add(num);
}
for (int num: nums2){
if (set.contains(num)){
result.add(num);
}
}
return result.stream().mapToInt(x -> x).toArray();
}
}
Leetcode202 Happy Number
[Example]:
Input: n = 19
Output: true
Explanation:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
这道题的本质是看会不会出现重复和, 如果有和重复出现了, 说明会一直循环, 即永远得不到1. 创建一个set记录和
此外, digit sum的求法: digit / 10 digit % 10
class Solution {
public boolean isHappy(int n) {
Set<Integer> set = new HashSet<>();
int temp = sum(n);
while (temp != 1){
if (set.contains(temp)){
return false;
}
else{
set.add(temp);
temp = sum(temp);
}
}
return true;
}
public int sum(int n){
int sum = 0;
while (n > 0){
sum += (n % 10) * (n % 10);
n = n / 10;
}
return sum;
}
}
Leetcode1 Two sum
题目链接: 两数之和
[Example]:
Input: nums = [2,7,11,15], target = 9
Output: [0,1]
Explanation: Because nums[0] + nums[1] == 9, we return [0, 1].
Method1
最先想到的解法, 两个for loop循环直到得到需要的Target
Time Complexity: O(n^2)
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
for (int i = 0; i < nums.length; i++){
for (int j = i + 1; j < nums.length; j++){
if (nums[i] + nums[j] == target){
result = new int[]{i,j};
}
}
}
return result;
}
}
Method2
因为涉及到判断某元素是否出现在集合里, 考虑用HashSet, 又因为需要知道元素index, 需要用HashMap
查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法
Time Complexity: O(n)
Space Complexity: O(n)
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 temp = target - nums[i];
if(map.containsKey(temp)){
res[1] = i;
res[0] = map.get(temp);
break;
}
map.put(nums[i], i);
}
return res;
}
}