代码随想录刷题day6丨哈希表理论基础 ,242.有效的字母异位词,349. 两个数组的交集,202. 快乐数, 1. 两数之和
1.哈希表理论基础
1.1概述
-
哈希表是根据关键码的值而直接进行访问的数据结构
-
直白来讲其实数组就是一张哈希表
-
哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素
-
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法
-
例如要查询一个名字是否在这所学校里。
要枚举的话时间复杂度是O(n),但如果使用哈希表的话, 只需要O(1)就可以做到。
我们只需要初始化把这所学校里学生的名字都存在哈希表里,在查询的时候通过索引直接就可以知道这位同学在不在这所学校里了。
将学生姓名映射到哈希表上就涉及到了hash function ,也就是哈希函数
-
-
1.2哈希函数
-
概述:哈希函数,把学生的姓名直接映射为哈希表上的索引,然后就可以通过查询索引下标快速知道这位同学是否在这所学校里了。
-
哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。
-
如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢?
此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,这样我们就保证了学生姓名一定可以映射到哈希表上了。
此时问题又来了,哈希表我们刚刚说过,就是一个数组。
如果学生的数量大于哈希表的大小怎么办,此时就算哈希函数计算的再均匀,也避免不了会有几位学生的名字同时映射到哈希表 同一个索引下标的位置。
接下来哈希碰撞登场
1.3哈希碰撞
-
概述:如图所示,小李和小王都映射到了索引下标 1 的位置,这一现象叫做哈希碰撞。
-
一般哈希碰撞有两种解决方法, 拉链法和线性探测法。
-
拉链法
刚刚小李和小王在索引1的位置发生了冲突,发生冲突的元素都被存储在链表中。 这样我们就可以通过索引找到小李和小王了
- (数据规模是dataSize, 哈希表的大小为tableSize)
- 其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。
-
线性探测法
使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
- 例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。如图所示:
-
1.4常用的三种哈希结构
- 数组
- set (集合)
- map(映射)
1.5总结
- 当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
- 哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
2.题目
2.1有效的字母异位词
-
文档讲解:https://programmercarl.com/0242.%E6%9C%89%E6%95%88%E7%9A%84%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.html
-
解题思路:暴力解法或者哈希法
-
暴力解法:先判断长度,如果长度不一样就输出false,一样的话我们将两个字符串变为两个char数组,再通过sort()函数按照ASCII码进行排序,然后用equal()函数比较两个数组是否一样。
-
哈希法:因为小写字母只有26个,优先考虑用数组
-
定一个大小为26的数组 就可以了,初始化为0,因为字符a到字符z的ASCII也是26个连续的数值。
-
解题步骤:
- 定义一个数组叫做record用来上记录字符串s里字符出现的次数
- 需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。
- 在遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了。
- 为了检查字符串t中是否出现了这些字符,对字符串t进行遍历操作,在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。
- 最后检查一下,**record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。**如果record数组所有元素都为零0,说明字符串s和t是字母异位词,return true。
-
-
-
代码:
-
暴力解法
//时间复杂度:O(nlogn) //空间复杂度:O(logn) class Solution { public boolean isAnagram(String s, String t) { if(s.length() != t.length()){ return false; } char[] arr1 = s.toCharArray(); char[] arr2 = t.toCharArray(); Arrays.sort(arr1); Arrays.sort(arr2); return Arrays.equals(arr1, arr2); } }
-
哈希法
//时间复杂度为O(n) //空间复杂度为O(1) class Solution { public boolean isAnagram(String s, String t) { int[] record = new int[26]; for (int i = 0; i <= s.length() - 1; i++) { record[s.charAt(i) - 'a']++; 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了 } for (int i = 0; i <= t.length() - 1; i++) { record[t.charAt(i) - 'a']--; } for (int i = 0; i < 26; i++) { if (record[i] != 0) { return false; record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。 } } return true;//record数组所有元素都为零0,说明字符串s和t是字母异位词 } }
-
-
总结:当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
2.2两个数组的交集
-
题目链接:349. 两个数组的交集 - 力扣(LeetCode)
-
文档讲解:https://programmercarl.com/0349.%E4%B8%A4%E4%B8%AA%E6%95%B0%E7%BB%84%E7%9A%84%E4%BA%A4%E9%9B%86.html
-
解题思路:哈希法(数值大用set,数值小用数组)
-
首先把 nums1放入哈希表
-
遍历 nums2,去哈希表里面找相同的元素
-
定义新的数组arr 去存储我们找到的相同的元素
-
-
代码:
-
用 set
//使用HashSet class Solution { public int[] intersection(int[] nums1, int[] nums2) { if(nums1 == null || nums2 == null){ return new int[0]; } Set<Integer> nums1Set = new HashSet<>(); Set<Integer> resultSet = new HashSet<>(); //遍历nums1 for(int i = 0;i <= nums1.length -1;i++){ nums1Set.add(nums1[i]); } //遍历nums2的过程中判断哈希表nums1Set中是否存在该元素 for(int i = 0;i <= nums2.length -1;i++){ if(nums1Set.contains(nums2[i])){ resultSet.add(nums2[i]); } } //另外申请一个数组arr存放resultSet中的元素,最后返回数组arr int[] arr = new int[resultSet.size()]; int index = 0; for(int result : resultSet){ arr[index++] = result; } return arr; } }
-
用数组
//使用hash数组 class Solution { public int[] intersection(int[] nums1, int[] nums2) { int hash1[] = new int[1001]; int hash2[] = new int[1001]; for(int i = 0;i <= nums1.length -1;i++){ hash1[nums1[i]]++; } for(int i = 0;i <= nums2.length -1;i++){ hash2[nums2[i]]++; } List<Integer> resultList = new ArrayList<>(); for(int i = 0;i<1001;i++){ if(hash1[i] > 0 && hash2[i] > 0){ resultList.add(i); } } int index = 0; int arr[] = new int[resultList.size()]; for(int result : resultList){ arr[index++] = result; } return arr; } }
-
-
总结:
- 注意题目特意说明:输出结果中的每个元素一定是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序
- 这道题用暴力的解法时间复杂度是O(n^2)
- 使用数组来做哈希的题目,是因为题目都限制了数值的大小。
- 如果题目没有限制数值的大小,就无法使用数组来做哈希表了
- 如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费
- 遇到哈希问题我直接都用set行不行?
- 不行。直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。
- 注意:set 不能用 下标遍历,因为set是无序的
2.3快乐数
-
-
文档讲解:https://programmercarl.com/0202.%E5%BF%AB%E4%B9%90%E6%95%B0.html
-
解题思路:哈希法(set)
- 题目中说了会 无限循环,那么也就是说求和的过程中,sum会重复出现,这对解题很重要!
- 当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。
- 所以这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。
-
代码:
//HashSet class Solution { public boolean isHappy(int n) { Set<Integer> record = new HashSet<>(); while (n != 1 && !record.contains(n)) { record.add(n); n = getNextNumber(n); } return n == 1; } // 定义一个方法求下一个数res private int getNextNumber(int n) { int res = 0; while (n > 0) { int temp = n % 10; res += temp * temp; n = n / 10; } return res; } }
-
总结:
- 难点就是求和的过程,如果对取数值各个位上的单数操作不熟悉的话,做这道题也会比较艰难。
2.4两数之和
-
-
文档讲解:https://programmercarl.com/0001.%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.html
-
解题思路:暴力解法和哈希法(map)
-
本题我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
-
-
代码:
-
暴力解法
class Solution { public int[] twoSum(int[] nums, int target) { int[] array1 = new int[2]; int i = 0; int j = 0; for(i = 0;i<nums.length-1;i++){ for(j = i+1;j<nums.length;j++){ if(nums[i]+nums[j]==target){ array1[0]=i; array1[1]=j; return array1; } } } return new int[]{-1, -1}; } }
-
map方法
class Solution { public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>(); int[] result = new int[2]; // 存储结果的数组 if (nums == null || nums.length == 0) { return new int[] {}; } for (int i = 0; i < nums.length; i++) { int s = target - nums[i]; // 计算目标补数 if (map.containsKey(s)) { // 如果map中包含补数 result[0] = i; result[1] = map.get(s); return result; } map.put(nums[i], i); // 如果未找到配对项,将当前数字和索引存入map中以待后续使用 } return new int[] {}; // 如果没有找到配对项,返回一个空数组作为结果 } }
-
-
总结:
- 该题如果使用数组和set来做哈希法的局限。
- 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
- set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。
- 该题map的作用?
- 存放我们遍历过的元素
- 该题如果使用数组和set来做哈希法的局限。