【哈希表理论基础 】
1、哈希表的作用
提高查找速度,普通遍历查找的时间复杂度为O(n),而哈希表实现O(1)
2、构建和使用哈希表的方法
- 构建:将键(key)通过哈希函数转换成对应的存储位置(索引),然后将键值对存储在该位置。
- 使用:key通过哈希函数计算得到索引,索引可以快速定位存储位置,从而实现高效的数据存取操作。
3、哈希碰撞
- 定义:哈希碰撞指的是不同的键经过哈希函数计算后得到相同的哈希值,导致它们应该存储在哈希表中的相同位置。
- 影响:哈希冲突会对哈希表的性能造成影响,需要额外的处理来解决这种冲突。
- 常见的处理方法包括:链地址法(将哈希表的每个位置设置为一个链表当发生哈希碰撞时,将新的键值对插入到对应位置的链表中)、线性探测法(当发生哈希碰撞时,通过一定的规则寻找下一个可用的空位置来存储数据)。
4、Java中常见的哈希结构
- 数组(array)
- 集合(Collection,包含List和Set)
- 映射(Map)
【242. 有效的字母异位词 】
方法一 利用HashMap
思路:key为单个字母,value为字母在字符串中出现的次数,分别对s和t建立两个HashMap,如果HashMap的内容完全一致,则返回true,否则返回false
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) return false;
Map<Character, Integer> sAlphaNum = new HashMap<>();
for (int i = 0;i < s.length(); i++){
char c = s.charAt(i);
if (sAlphaNum.containsKey(c)){
sAlphaNum.put(c, sAlphaNum.get(c) + 1);
}
else{
sAlphaNum.put(c, 1);
}
}
System.out.println(sAlphaNum);
Map<Character, Integer> tAlphaNum = new HashMap<>();
for (int i = 0;i < t.length(); i++){
char c = t.charAt(i);
if (tAlphaNum.containsKey(c)){
tAlphaNum.put(c, tAlphaNum.get(c) + 1);
}
else{
tAlphaNum.put(c, 1);
}
}
System.out.println(tAlphaNum);
if (sAlphaNum.keySet().size() != tAlphaNum.keySet().size()) return false;
for (Character c: sAlphaNum.keySet()){
if (tAlphaNum.containsKey(c) && tAlphaNum.get(c).equals(sAlphaNum.get(c))){
continue;
}
else{
return false;
}
}
return true;
}
}
时间复杂度: O(n)
空间复杂度: O(n)
方法二 构建哈希表(更推荐)
思路:
1、定义一个长度为26的int型数组nums,可以利用字母的ASCII编码获取字母与数组下标的关系,构建哈希表。
2、分别遍历两个字符串,遍历第一个字符串的时候,就在出现的字母对应数组位置+1,遍历第二个字符串的时候,就在出现的字母对应数组位置-1。
3、遍历nums,如果存在不为0的值,则返回false,nums全为0则返回true。
class Solution {
public boolean isAnagram(String s, String t) {
int[] nums = new int[26];
for (int i = 0; i < s.length(); i++){
char c = s.charAt(i);
nums[c - 'a']++;
}
for (int i = 0; i < t.length(); i++){
char c = t.charAt(i);
nums[c - 'a']--;
}
for(int i = 0; i < nums.length; i++){
if (nums[i] != 0){
return false;
}
}
return true;
}
}
时间复杂度: O(n)
空间复杂度: O(1) (因为数组长度固定为26,与输入字符串的长度无关)
【349. 两个数组的交集 】
方法一 利用HashSet
思路:
1、获取nums1数组的无重元素集合
2、遍历nums2数组,如果nums1数组的无重元素集合包含遍历到的元素,则添加进结果集合
3、将结果集合转换成结果数组并返回
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
// 排除特殊情况
if (nums1 == null || nums2 == null || nums1.length == 0 || nums2.length == 0) return new int[0];
// 获取nums1数组的无重元素集合
HashSet<Integer> nums1Set = new HashSet<>();
for (int i = 0; i < nums1.length; i++){
nums1Set.add(nums1[i]);
}
// 遍历nums2数组,如果nums1数组的无重元素集合包含遍历到的元素,则添加进结果集合
HashSet<Integer> resSet = new HashSet<>();
for (int i = 0; i < nums2.length; i++){
if (nums1Set.contains(nums2[i])){
resSet.add(nums2[i]);
}
}
// 将结果集合转换成结果数组
int[] res = new int[resSet.size()];
int i = 0;
for (int r: resSet){
res[i++] = r;
}
return res;
}
}
时间复杂度: O(n+m)
空间复杂度: O(n)
方法二 构建哈希表(利用题目的数值范围,更巧妙)
思路:根据题目给定的数值范围 0 <= nums1[i], nums2[i] <= 1000 构建哈希表
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
// 排除特殊情况
if (nums1 == null || nums2 == null || nums1.length == 0 || nums2.length == 0) return new int[0];
// 创建哈希表
int[] hash1 = new int[1002]; // 1001种可能的取值,索引从0开始,1001+1=1002
int[] hash2 = new int[1002];
ArrayList<Integer> resList = new ArrayList<>();
// 分别遍历两个表,遍历到的数字在对应位置+1
for (int num1 : nums1){
hash1[num1]++;
}
for (int num2 : nums2){
hash2[num2]++;
}
// 如果某个数字对应两个表的位置都不为0,则该数字就是交集中的元素
for (int i = 0; i < hash1.length; i++){
if (hash1[i] > 0 && hash2[i] > 0){
resList.add(i);
}
}
// 将集合转成数组
int i = 0;
int[] res = new int[resList.size()];
for (int r: resList){
res[i++] = r;
}
return res;
}
}
时间复杂度: O(n+m)
空间复杂度: O(n)
【202. 快乐数】
思路:
1、如果和计算为1,则这个数为快乐数;
2、如果和已经出现过,证明已经开始循环,则这个数不是快乐数;
关键就是判断当前的和是否之前出现过,可以利用HashSet判断。
class Solution {
public boolean isHappy(int n) {
// 用来存储每次的和
Set<Integer> sumSet = new HashSet<>();
// 循环的截止条件:
// 1、如果出现n=1,则这个数为快乐数;
// 2、如果sumSet已经包含了n,证明已经开始循环,则这个数不是快乐数;
while (n != 1 && !sumSet.contains(n)){
sumSet.add(n);
n = nextNum(n);
}
return n == 1;
}
// 输入一个整数,求组成它的各个数字的平方和并返回
private int nextNum(int n){
int s = 0;
while (n != 0){
int last = n % 10;
s += last * last;
n /= 10;
}
return s;
}
}
时间复杂度: O(logn)
空间复杂度: O(logn)
【1. 两数之和 】
思路:
1、利用哈希表,记录列表中出现过的值及其下标,如果重复出现则不记录。
2、查找差值temp = target - nums[i]是否在前面遍历过的元素中出现,以获取返回值。
注意:
1、必须在将值及其坐标存入map之前,对该值进行差值存在判断,避免构成target的两个数的值相等(如:6=3+3)这种情况导致的返回值错误。
2、如果nums = [2, 4, 7, 8],target = 9,在遍历2时,map中的key没有7(或前面没出现过7),则继续遍历,直到遍历7,map中的key有2,才返回。
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
int[] res = new int[2];
for (int i = 0; i < nums.length; i++){
// 计算差值并查找,如果前面出现过,则获取结果退出循环
int temp = target - nums[i];
if (map.containsKey(temp)){
res[0] = i;
res[1] = map.get(temp);
break;
}
// 如果前面没有出现过这个值,则将key=nums[i]和value=i存入map
if (!map.containsKey(nums[i])){
map.put(nums[i], i);
}
}
return res;
}
}
时间复杂度: O(n)
空间复杂度: O(n)