哈希表

概念

哈希表是根据关键码的值而直接进行访问的数据结构。实际上其实数组也是一张哈希表,哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素

一般哈希表都是用来快速判断一个元素是否出现集合里

哈希法是牺牲了空间换取了时间,因为要使用额外的数组,set或者是map来存放数据,才能实现快速的查找


哈希函数

哈希函数,就是将元素直接映射为哈希表上的索引,然后就可以通过查询索引下标进行快速访问

哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值。


拉链法

当出现哈希碰撞的时候,发生冲突的元素都被存储在链表中

拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。

线性探测法

使用线性探测法,一定要保证哈希表大小大于数据大小。 因为需要依靠哈希表中的空位来解决碰撞问题

如果出现冲突的位置,那么就向下找一个空位放置。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放冲突的数据了


结合数组

有效的字母异位词

定义一个数组记录字符串s里字符出现的次数。然后将把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25

首先遍历字符串s,将 s[i] - 'a' 所在的元素做++ ,这样就将字符串s中字符出现的次数,统计出来了

然后再遍历字串t,对t中出现的字符映射数组索引上的数值再做–

最后,检查数组上有无元素不为零,如果又,就说明s和t中字符出现的次数肯定不一样

时间复杂度为O(n),空间上因为定义了常量大小的辅助数组,空间复杂度为O(1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean isAnagram(String s, String t) {
int[] nums = new int[26];
for (char c : s.toCharArray()) {
nums[c-'a']++;
}
for (char c : t.toCharArray()) {
nums[c-'a']--;
}
for (int num : nums) {
if (num != 0) {
return false;
}
}
return true;
}

赎金信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean canConstruct(String ransomNote, String magazine) {
int[] nums = new int[26];
for (char c : magazine.toCharArray()) {
nums[c - 'a']++;
}
for (char c : ransomNote.toCharArray()) {
nums[c - 'a']--;
}
for (int num : nums) {
if (num < 0){
return false;
}
}
return true;
}

结合Map

字母异位词分组

思路:

1、将不同的字符串转换为字符数组后进行排序

2、如果排序后结果相同,说明字母异位词相同

3、可以将排序后的结果作为map的key来进行存储,然后将字母异位词作为map的value进行存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
char[] array = str.toCharArray();
//进行排序
Arrays.sort(array);
//设置key
String key = new String(array);
//使用key在map集合中获取对应的value,如果没有获取到,则创建一个list集合并返回
List<String> list = map.getOrDefault(key, new ArrayList<>());
//更新list和map
list.add(str);
map.put(key,list);
}
return new ArrayList<List<String>>(map.values());
}

两数之和

1、map用来存放我们访问过的元素,在遍历数组的时候,需要记录之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)

2、map中的存储结构为 {key:数据元素,value:数组元素对应的下表}

3、在遍历数组的时候,只需要向map去查询是否有和目前遍历元素比配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if (nums == null || nums.length == 0) {
return res;
}
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.get(target - nums[i]) == null){
map.put(nums[i],i);
}else {
res[1] = i;
res[0] = map.get(target - nums[i]);
}
}
return res;
}

四数相加 II

目标:找到A[i] + B[j] + C[k] + D[l] = 0即可

将数组1和数组2每一个相加后的值作为map集合的key,相加的值的个数作为map集合的value,如果该值出现多次,则让value继续++

遍历数组3和数组4,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。count即为目标值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
HashMap<Integer, Integer> map = new HashMap<>();
int res = 0;
for (int num1 : nums1) {
for (int num2 : nums2) {
int temp = num1 + num2;
if (map.containsKey(temp)) {
map.put(temp,map.get(temp)+1);
}else {
map.put(temp,1);
}
}
}
for (int num3 : nums3) {
for (int num4 : nums4) {
int temp = num3 + num4;
if (map.containsKey(0-temp)) {
res += map.get(0-temp);
}
}
}
return res;
}

存在重复元素 II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public boolean containsNearbyDuplicate(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(nums[i])) {
if (Math.abs(i - map.get(nums[i])) <= k) {
return true;
}
}
map.put(nums[i],i);
}
return false;
}
}

单词规律

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public boolean wordPattern(String pattern, String s) {
HashMap<Character, String> map = new HashMap<>();
//分割字符串s
String[] strs = s.trim().split(" ");
if (pattern.length() != strs.length){
return false;
}
//遍历pattern
for (int i = 0; i < pattern.length(); i++) {
//如果map集合中不存在和pattern相同的key:说明有可能map集合中还未添加该元素 或者 map集合中不存在这个元素
if (!map.containsKey(pattern.charAt(i))) {
//在map集合中不存在和pattern相同的key的情况下,判断map集合的value是否和字符串s相同
//如果相同,则说明pattern中的不同元素,映射了字符串s中的同一个元素
if (map.containsValue(strs[i])) {
return false;
}
map.put(pattern.charAt(i),strs[i]);
}else {
//如果map集合中存在和pattern相同的key,判断是否和字符串中的元素相同,如果不相同,则出现了错误的映射关系
if (!map.get(pattern.charAt(i)).equals(strs[i])) {
return false;
}
}
}
return true;
}

同构字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public boolean isIsomorphic(String s, String t) {
return isIsomorphicTwo(s, t) && isIsomorphicTwo(t, s);
}
public boolean isIsomorphicTwo(String s, String t) {
int n = s.length();
HashMap<Character, Character> map = new HashMap<>();
for (int i = 0; i < n; i++) {
char c1 = s.charAt(i);
char c2 = t.charAt(i);
if (map.containsKey(c1)) {
if (map.get(c1) != c2) {
return false;
}
} else {
map.put(c1, c2);
}
}
return true;
}

结合Set

两个数组的交集

解法一:使用set

1、先判断nums1和nums2有没有一个为空,有则说明无交集

2、创建两个HashSet,先把nums1中的所有数据加入到set1中,然后遍历nums2,如果set1中出现了与nums2中数据相同的,说明该数为交集,则加入到set2中

3、创建目标数组,将set2的数据放到目标数组并返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public int[] intersection(int[] nums1, int[] nums2) {
if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
return new int[0];
}
HashSet<Integer> set1 = new HashSet<>();
HashSet<Integer> set2 = new HashSet<>();
for (int num1 : nums1) {
set1.add(num1);
}
for (int num2 : nums2) {
if (set1.contains(num2)) {
set2.add(num2);
}
}
int[] target = new int[set2.size()];
int left = 0;
for (int num2 : set2) {
target[left++] = num2;
}
return target;
}

解法二:使用哈希表

1、创建一个临时数组,并用nums1每个元素的值num1作为临时数组索引,并让索引处的元素加一

注意:此处要判断元素是否为0,为了防止出现重复元素而++两次

2、将nums2每个元素的值num2作为临时数组索引,判断索引处对应的元素是否为0,如果不是则说明该数即为交集,添加到list中。

注意:此处需要将临时数组对应索引的元素置空,为了防止出现重复元素导致list集合出现重复元素

3、将list中的数据遍历到目标数组并返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int[] intersection(int[] nums1, int[] nums2) {
if (nums1 == null || nums2 == null || nums1.length == 0 || nums2.length == 0){
return new int[0];
}
int[] temp = new int[1001];
List<Integer> list = new ArrayList<>();
for(int i = 0;i < nums1.length ;i++){
if(temp[nums1[i]] == 0){
temp[nums1[i]]++;
}
}
for(int i = 0;i < nums2.length ;i++){
if(temp[nums2[i]] == 1){
list.add(nums2[i]);
temp[nums2[i]] = 0;
}
}
int[] target = new int[list.size()];
for(int i = 0;i < list.size() ;i++){
target[i] = list.get(i);
}
return target;
}

快乐数

解法一:快慢指针思想

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean isHappy(int n) {
int slow = create(n);
int fast = create(create(n));
while (slow != fast) {
slow = create(slow);
fast = create(create(fast));
}
return slow == 1 ? true : false;
}

private int create(int n) {
int sum = 0;
while (n > 0) {
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}

解法二:哈希集合检测

1、创建一个hashset,将数字添加进set集合中

2、添加时判断集合中是否有这个元素,并且该数字不为1,如果以上条件都满足,则说明还没有进行循环

3、如果以上条件不满足,说明已经进入了循环,此时判断该数是否为1,如果是则为快乐数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public boolean isHappy(int n) {
Set<Integer> set = new HashSet<>();
while (n != 1 && !set.contains(n)) {
set.add(n);
n = getNum(n);
}
return n == 1;
}

private int getNum(int n) {
int sum = 0;
while (n > 0) {
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}

771. 宝石与石头

image-20221113233239263

首先对字符串jewels进行遍历,将字符分别存到HashSet中,以便之后遍历stones的时候进行查找

遍历stones,并将每个字符与HashSet中的进行比对,如果字符存在,则说明存在一个宝石,让结果count++,遍历结束,返回count即为答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int numJewelsInStones(String jewels, String stones) {
int count = 0;
HashSet<Character> set = new HashSet<>();
for (int i = 0; i < jewels.length(); i++) {
char jewel = jewels.charAt(i);
set.add(jewel);
}
for (int i = 0; i < stones.length(); i++) {
char stone = stones.charAt(i);
if (set.contains(stone)) {
count++;
}
}
return count;
}
}

其他

两个数组的交集 II

解法一:哈希映射

……待补充


丢失的数字

方法一:哈希表

1
2
3
4
5
6
7
8
9
10
11
12
13
public int missingNumber(int[] nums) {
HashMap<Integer, Integer> map = new HashMap<>();
int res = 0;
for (int i = 0; i < nums.length; i++) {
map.put(nums[i],i);
}
for (int i = 0; i <= nums.length; i++) {
if (!map.containsKey(i)){
res = i;
}
}
return res;
}

方法二:排序

1
2
3
4
5
6
7
8
9
public int missingNumber(int[] nums) {
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
if (nums[i] != i) {
return i;
}
}
return nums.length;
}