day06
哈希表基本知识
-
哈希表:哈希表是根据关键码的值而直接进行访问的数据结构。关键码其实就是搜索哈希表的索引,而哈希表能够通过访问关键码直接访问表中元素。其本质为数组
-
哈希表通过哈希函数将关键码映射到哈希表中。当hashcode得到的值大于表大小时,为保证哈希表能将其存储下来,我们会对hashcode的值取模,如图所示:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FOxyoTMP-1692197161189)(pic/hashmap1.png)]
但问题是这样的存储方式会导致有部分不同的关键码映射到哈希表的同一个下标的位置,由此引入哈希碰撞。
-
上述情况我们称之为哈希碰撞。该问题有两种解决方法:
- 拉链法
将发生冲突的元素用链表存储起来。这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vcMBtMIO-1692197161189)(pic/hashmap2.png)]
2. 线性探测法使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize,
要不然哈希表上就没有空置的位置来存放 冲突的数据了。 -
常见的哈希结构
- 数组
- set(集合)
- map(映射)
应用:一般用来快速判断一个元素是否出现集合里。查询的时间复杂度为O(1)
例题 leetcode 242.有效的字母异位词
链接:https://leetcode.cn/problems/valid-anagram/description/
思路:
方法1:排序
将排序后的字符串进行比较即可。
```
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
char[] str1 = s.toCharArray();
char[] str2 = t.toCharArray();
Arrays.sort(str1);
Arrays.sort(str2);
return Arrays.equals(str1, str2);
}
}
```
方法2:哈希表
可以维护一个长度为26的数组,先遍历记录字符串s,统计s中字符出现的频次,然后遍历字符串t,减去对应的频次,
如果出现table[i] != 0,则说明t中有s没有的字符,返回false即可。
class Solution {
public boolean isAnagram(String s, String t) {
int[] ans = new int[26];
for(int i = 0; i < s.length(); i++){
ans[s.charAt(i) - 'a']++;
}
for(int i = 0; i < t.length(); i++){
ans[t.charAt(i) - 'a']--;
}
for(int i = 0; i < 26; i++){
if(ans[i] != 0) return false;
}
return true;
}
}
例题 leetcode 349. 两个数组的交集
链接:https://leetcode.cn/problems/intersection-of-two-arrays/description/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0, lenB = 0;
while (curA != null) { // 求链表A的长度
lenA++;
curA = curA.next;
}
while (curB != null) { // 求链表B的长度
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
// 让curA为最长链表的头,lenA为其长度
if (lenB > lenA) {
//1. swap (lenA, lenB);
int tmpLen = lenA;
lenA = lenB;
lenB = tmpLen;
//2. swap (curA, curB);
ListNode tmpNode = curA;
curA = curB;
curB = tmpNode;
}
// 求长度差
int gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap-- > 0) {
curA = curA.next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA != null) {
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
例题 leetcode 202. 快乐数
链接:https://leetcode.cn/problems/happy-number/description/
思路:模拟快乐数求算过程,并用哈希表来检查是否进入循环。
class Solution {
private int getNext(int n) {
int totalSum = 0;
while (n > 0) {
int d = n % 10;
n = n / 10;
totalSum += d * d;
}
return totalSum;
}
public boolean isHappy(int n) {
Set<Integer> seen = new HashSet<>();
while (n != 1 && !seen.contains(n)) {
seen.add(n);
n = getNext(n);
}
return n == 1;
}
}
例题 leetcode 1. 两数之和
链接:https://leetcode.cn/problems/two-sum/description/
解法1:暴力法
思路:嵌套for循环,比对每次两个数组的值的和是否为target。
class Solution {
public int[] twoSum(int[] nums, int target) {
for(int i = 0; i < nums.length; i++){
for(int j = i+1; j < nums.length; j++){
if(nums[i] + nums[j] == target) return new int[] {i,j};
}
}
return new int[] {-1,-1};
}
}
解法2:哈希表
思路:创建一个哈希表,对于每一个x,先查询哈希表中是否存在target - x,
再将x插入到哈希表中,避免出现2x = target,但是整数数组中只有一个x出现的情况。
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; ++i) {
if (hashtable.containsKey(target - nums[i])) {
return new int[]{hashtable.get(target - nums[i]), i};
}
hashtable.put(nums[i], i);
}
return new int[0];
}
}