代码随想录算法训练营第六天 | 242.有效的字母异位词 、 349. 两个数组的交集 、202. 快乐数、1. 两数之和
目录
哈希表理论基础
哈希表
![image-20221121095432622](https://img-blog.csdnimg.cn/20210104234805168.png)
哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素
解决的问题:一般哈希表都是用来快速判断一个元素是否出现集合里
查询时间复杂度:O(1)
哈希函数
通过哈希函数,可以把其它数据格式的数据转化为不同的数值
![image-20221121095805984](https://img-blog.csdnimg.cn/2021010423484818.png)
哈希碰撞
如图所示,小李和小王都映射到了索引下标 1 的位置,这一现象叫做哈希碰撞
![哈希表3](https://img-blog.csdnimg.cn/2021010423494884.png)
哈希碰撞解决办法
拉链法
![](https://img-blog.csdnimg.cn/20210104235015226.png)
刚刚小李和小王在索引1的位置发生了冲突,发生冲突的元素都被存储在链表中。 这样我们就可以通过索引找到小李和小王了
其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间
线性探测法
前提:保证tableSize大于dataSize,需要依靠哈希表中的空位来解决碰撞问题
例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。
![](https://img-blog.csdnimg.cn/20210104235109950.png)
常见的三种哈希结构
- 数组
- 数据较少,且范围可控时使用
- set (集合)
- 数据较多,且不知道范围
- map(映射)
- 有key-value的情况使用
242. 有效的字母异位词
遇到的问题
- 字符对象没法用hashCode函数
- 哈希冲突如何解决(比如只有一个输入“a”,”b”怎么解决) ->不应该用字符串长度定义计数数组,而应该用26
本人初次代码
class Solution {
public boolean isAnagram(String s, String t) {
int lengthS = s.length();
int lengthT = t.length();
if(lengthS != lengthT){
return false;
}
if(s == t){
return true;
}
String[] strS = s.split("");
String[] strT = t.split("");
System.out.println(Arrays.toString(strS));
System.out.println(Arrays.toString(strT));
int[] countArrayS = new int[26];
int[] countArrayT = new int[26];
for(int i = 0;i < lengthS;i++){
int hashCodeS = strS[i].hashCode() % 26;
countArrayS[hashCodeS]++;
}
for(int j = 0;j < lengthT;j++){
int hashCodeT = strT[j].hashCode() % 26;
countArrayT[hashCodeT]++;
}
for(int index = 0;index < 26;index++){
if(countArrayS[index] != countArrayT[index]){
return false;
}
}
return true;
}
}
思路
-
数组其实就是一个简单哈希表,而且这道题目中字符串只有小写字符,那么就可以定义一个数组,来记录字符串s里字符出现的次数
-
定一个数组叫做record,记录字符串s里字符出现的次数,大小为26 就可以了,初始化为0,因为字符a到字符z的ASCII也是26个连续的数值
-
需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25
-
-
遍历字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了
-
那看一下如何检查字符串t中是否出现了这些字符,同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作
-
那么最后检查一下,record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false
![](https://tva1.sinaimg.cn/large/008eGmZEly1govxyg83bng30ds09ob29.gif)
时间复杂度为O(n),空间上因为定义是的一个常量大小的辅助数组,所以空间复杂度为O(1)
代码随想录代码
/**
* 242. 有效的字母异位词 字典解法
* 时间复杂度O(m+n) 空间复杂度O(1)
*/
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']++;
}
for (int i = 0; i < t.length(); i++) {
record[t.charAt(i) - 'a']--;
}
for (int count: record) {
if (count != 0) {
return false;
}
}
return true;
}
}
可进步的地方
- 其实只用设置一个计数数组,遍历s字符串的时候加,遍历t字符串的时候减,最后看数组各个元素是否都等于0即可判断
- 不需要用hash函数,用ascii码即可
349. 两个数组的交集
本人代码
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
HashSet<Integer> setNums1 = new HashSet<>();
HashSet<Integer> setSame = new HashSet<>();
for (int i = 0; i < nums1.length; i++) {
if (setNums1.contains(nums1[i])){
continue;
}
setNums1.add(nums1[i]);
}
for (int i = 0; i < nums2.length; i++) {
if (!setNums1.contains(nums2[i])){
continue;
}
setSame.add(nums2[i]);
}
int[] result = new int[setSame.size()];
int index = 0;
for (Integer sameEle : setSame) {
result[index++] = sameEle;
}
return result;
}
}
自己ac嘿嘿
思路
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220707173513.png)
代码随想录代码
import java.util.HashSet;
import java.util.Set;
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
return new int[0];
}
Set<Integer> set1 = new HashSet<>();
Set<Integer> resSet = new HashSet<>();
//遍历数组1
for (int i : nums1) {
set1.add(i);
}
//遍历数组2的过程中判断哈希表中是否存在该元素
for (int i : nums2) {
if (set1.contains(i)) {
resSet.add(i);
}
}
//将结果几何转为数组
return resSet.stream().mapToInt(x -> x).toArray();
}
}
可进步的地方
- set的add方法可以自然去重,无需先进行判断是否重复再加入
- 可以多用用Stream流,这样可以方便把Collection对象,转化为想要的对象,比如数组
202. 快乐数
思路
- 像此类数学题,可以先自己拿例子尝试,去看看怎样才不是快乐数,就会发现求和的过程中,sum会重复出现此时就不是快乐数
- 遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了
本人代码
class Solution {
public boolean isHappy(int n) {
Set<Integer> result = new HashSet<>();
int sum = 0;
while(sum != 1){
if(result.contains(n)){
return false;
}
result.add(sum);
sum = 0;
for(int temp = n;temp != 0;temp = temp / 10){
sum += (temp % 10) * (temp % 10);
}
n = sum;
}
return true;
}
}
代码随想录代码
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;
}
private int getNextNumber(int n) {
int res = 0;
while (n > 0) {
int temp = n % 10;
res += temp * temp;
n = n / 10;
}
return res;
}
}
1. 两数之和
思路
-
因为本地,我们不仅要知道元素有没有遍历过,还有知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适
-
map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下表,这样才能找到与当前元素相匹配的(也就是相加等于target)
那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标
所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下表}
-
在遍历数组的时候,只需要向map去查询是否有和目前遍历元素比配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220711202638.png)
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220711202708.png)
个人问题
- 用数组下标当作key,把数组的元素值作为value,导致遇到 2 / target 的元素混入各种正确元素位置中,不知如何解决
个人代码
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0;i < nums.length;i++){
if(map.containsKey(target - nums[i])){
return new int[]{map.get(target - nums[i]),i};
}
map.put(nums[i],i);
}
return null;
}
}
总结
为什么会想到用哈希表
当需要查询另一个集合里的元素时我们会想到用到哈希表
三种数据结构如何使用
- 数组
- 数据较少,且范围可控时使用
- set (集合)
- 数据较多,且不知道范围
- map(映射)
- 有key-value的情况使用