哈希表理论基础
哈希表
那么哈希表能解决什么问题呢,一般哈希表都是用来快速判断一个元素是否出现集合里。
将学生姓名映射到哈希表上就涉及到了hash function ,也就是哈希函数。
哈希函数
如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢?
此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,这样我们就保证了学生姓名一定可以映射到哈希表上了。
如果学生的数量大于哈希表的大小怎么办,此时就算哈希函数计算的再均匀,也避免不了会有几位学生的名字同时映射到哈希表 同一个索引下标的位置。
接下来哈希碰撞登场
哈希碰撞
一般哈希碰撞有两种解决方法, 拉链法和线性探测法。
拉链法
线性探测法
使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。
例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。如图所示:
常见的三种哈希结构
当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。
- 数组
- set (集合)
- map(映射)
总结
总结一下,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法!
有效的字母异位词
242. 有效的字母异位词 - 力扣(LeetCode)
/**
* 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是字母异位词
}
}
赎金信
这道题目和上一道题目很像,相当于求 字符串a 和 字符串b 是否可以相互组成 ,而这道题目是求 字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。
本题判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成,但是这里需要注意两点。
-
第一点“为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思” 这里说明杂志里面的字母不可重复使用。
-
第二点 “你可以假设两个字符串均只含有小写字母。” 说明只有小写字母,这一点很重要
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
// shortcut
if (ransomNote.length() > magazine.length()) {
return false;
}
// 定义一个哈希映射数组
int[] record = new int[26];
// 遍历
for(char c : magazine.toCharArray()){
record[c - 'a'] += 1;
}
for(char c : ransomNote.toCharArray()){
record[c - 'a'] -= 1;
}
// 如果数组中存在负数,说明ransomNote字符串中存在magazine中没有的字符
for(int i : record){
if(i < 0){
return false;
}
}
return true;
}
}
字母异位词分组
49. 字母异位词分组 - 力扣(LeetCode)
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> m = new HashMap<>();
for (String str : strs) {
char[] s = str.toCharArray();
Arrays.sort(s);
// s 相同的字符串分到同一组
m.computeIfAbsent(new String(s), k -> new ArrayList<>()).add(str);
}
return new ArrayList<>(m.values());
}
}
-
m.computeIfAbsent(new String(s), k -> new ArrayList<>()).add(str);
: 这是computeIfAbsent
方法的使用,它的作用是:new String(s)
: 将排序后的字符数组s
重新转换回字符串。computeIfAbsent
: 这个方法检查Map
中是否存在以new String(s)
为键的映射。如果不存在,它会执行提供的函数(即lambda表达式),并用该函数的结果作为值来创建新的键值对。k -> new ArrayList<>()
: 这是一个lambda表达式,定义了当键不存在时应该如何创建新的值。在这个例子中,它创建了一个新的ArrayList
。.add(str)
: 将当前的字符串str
添加到对应键的列表中。
找到字符串中所有字母异位词
解题思路:双指针
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> ans = new ArrayList<>();
// 初始化一个数组来统计字符串 p 中每个字符的出现次数
int[] cnt = new int[26];
for(int i = 0; i < p.length(); i++){
cnt[p.charAt(i) - 'a']++;
}
// l 和 r 分别表示滑动窗口的左右边界
int l = 0;
for(int r = 0; r < s.length(); r++){
// 更新当前窗口中字符的计数数组
cnt[s.charAt(r) - 'a']--;
// 从左侧收缩窗口,直到当前字符的计数在限定范围内
while(cnt[s.charAt(r) - 'a'] < 0){
cnt[s.charAt(l) - 'a']++;
l++;
}
// 检查当前窗口大小是否等于字符串 p 的大小
if(r - l + 1 == p.length()){
ans.add(l);
}
}
return ans;
}
}
另一个大佬把双指针相关的题目和思路总结了一遍,明明好好过一下
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)