哈希表理论基础
关于哈希表的一些基础概念,如哈希函数和哈希冲突,可以参考博客:你是怎么看懂HashMap的
关于哈希表的具体实现方式,可以参考:深入理解哈希表
有效的字母异位词
哈希表的实现方式有三种,分别是数组、Set和Map,数组适用于数据量不大的情况,在数据量很大时,需要考虑使用Set,如果要用到数据的两个属性,可以考虑Map中的key_value形式。
这道题使用数组来实现哈希表,创建哈希表来存储已经遍历过的元素。
如果使用暴力解法,使用两层for循环,那么时间复杂度是 O(n^2)。
注意代码中for each的写法,for(int count : record)这里的循环判断条件是 "enhanced for loop",也称为 "for-each loop"。这是 Java 5 引入的一个新特性,主要用于遍历数组或集合。record 是一个整数数组。这个循环将遍历 record 数组中的每一个元素,将元素值赋给 count,然后执行循环体中的语句。
/**
* 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']++; // 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
}
for (int i = 0; i < t.length(); i++) {
record[t.charAt(i) - 'a']--;
}
for (int count: record) {
if (count != 0) { // record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return false;
}
}
return true; // record数组所有元素都为零0,说明字符串s和t是字母异位词
}
}
两个数组的交集
题目链接 求两个数组的交集,并且要求元素是唯一的,可以使用Set构建哈希表,Set有自动去重的功能,先遍历数组1并存入哈希表,在遍历数组2,判断是否有映射在哈希表的元素,最后创建res数组存储交集元素。
-
在Java中,数组的大小是固定的,一旦创建就不能改变。因此,你不能直接向Java数组中添加元素。想要对数组进行扩容,只能①创建一个新的更大的数组;②使用ArrayList;
-
这里的
length没加括号()是因为,数组取的是属性,不用括号,而字符串取的是方法,要加括号 -
注意多态的使用,父类引用指向子类对象,HashSet是Set的子类
Set <Integer> set1 = new HashSet<>(); -
在Java中,Set和HashSet都是集合类型,但它们在使用和实现上有一些区别。
Set是Java集合框架中的一个接口,它继承自Collection接口。Set接口的特点是不允许集合中有重复的元素,即任何两个元素e1和e2都不能满足e1.equals(e2)。Set接口没有提供任何额外的方法,它只使用了Collection接口的方法。Set接口有多个实现类,包括HashSet、LinkedHashSet和TreeSet等。 HashSet 是Set接口的一个实现类。它使用哈希表(实际上是一个HashMap实例)来存储元素。HashSet允许存储null元素。HashSet的特点是最能够提供常数时间复杂度的基本操作(add、remove和contains),因此在性能上通常优于其他Set实现类。 当你写Set<Integer> resSet = new HashSet<>();时,你实际上是在创建一个HashSet实例,并将其引用赋值给一个Set类型的变量。这是一种常见的编程实践,称为“程序面向接口编程”。这样做的好处是,你可以在不改变代码其他部分的情况下,改变resSet的实际实现类型。例如,你可以将其改为Set<Integer> resSet = new TreeSet<>();,而不需要修改使用resSet的代码。 这种做法提高了代码的灵活性,使得代码更容易适应需求的变化。同时,这也是一种隐藏实现细节的方式,使得代码更容易理解和维护。
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);
}
}
//方法1:将结果集合转为数组
return resSet.stream().mapToInt(x -> x).toArray();
//方法2:另外申请一个数组存放setRes中的元素,最后返回数组
int[] arr = new int[resSet.size()];
int j = 0;
for(int i : resSet){
arr[j++] = i;
}
return arr;
}
}
快乐数
题中的无限循环是解题的关键点所在,这是我们跳出循环的判断条件。这里n的数值取值范围很大,所以考虑使用Set构建哈希表,利用哈希表存储已经求得过的结果,在下一次求出结果时遍历,若得到已经存在哈希表中的数值,则是开始无限循环,需要填=跳出循环体。
class Solution {
public boolean isHappy(int n) {
//n取值很大,使用set
//创建哈希表记录求过的结果,以跳出无限循环
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 tmp = n % 10;
res += tmp * tmp;
n /= 10;
}
return res;
}
}
两数之和
这道题我们既要获取所需元素的大小,又要返回所需元素的下标,所以考虑使用Map的key_value来存储,key来存储元素数值,value存储元素下标,一定要搞清楚key和value分别代表什么。
创建Map哈希表:Map<Integer, Integer> map = new HashMap<>();
向Map插入数据:map.put(nums[i], i);
class Solution {
public int[] twoSum(int[] nums, int target) {
/*这道题有四个需要注意的地方
1:为什么用哈希表:这里设置的数据量很大,且要判断元素是否出现在遍历过的元素里面,
所以先考虑哈希映射;
2:为什么使用map:在寻找题解的时候,我们不仅要知道元素的数值,还要知道元素的下标,
所以使用map的key-value的形式存储;
3:map的作用是什么:map在这里是用来存放已经遍历过的元素,以判断是否满足两数之和为target;
4:key和value分别代表的是什么:这里我们要判断元素的加和是否为target,所以把元素设置为key,
value则是元素所在的小标;
*/
//创建一个数组来存储符合条件的两个元素的下标
//创建map哈希表来存储遍历过的元素
int[] res = new int[2];
if(nums == null || nums.length == 0)
return res;
Map<Integer, Integer> map = new HashMap<>();
//遍历数组,并把不确定的元素存入哈希表map中
for(int i = 0; i < nums.length; i++){
int tmp = target - nums[i];
if(map.containsKey(tmp)){
//res存储符合的两个元素的下标
res[0] = map.get(tmp);
res[1] = i;
//找到答案后要跳出循环
break;
}
map.put(nums[i], i);
}
//返回res
return res;
}
}