哈希表能解决什么问题呢,一般哈希表都是用来快速判断一个元素是否出现集合里。
要枚举的话时间复杂度是O(n),但如果使用哈希表的话, 只需要O(1)就可以做到。
但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找
常见的三种哈希结构
当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。
- 数组
- set (集合)
- map(映射)
C++中,set 和 map 分别提供以下三种数据结构,其底层实现以及优劣如下表所示
集合(set) | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
映射(map) | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::map | 红黑树 | key有序 | key不可重复 | key不可修改 | O(logn) | O(logn) |
std::multimap | 红黑树 | key有序 | key可重复 | key不可修改 | O(log n) | O(log n) |
std::unordered_map | 哈希表 | key无序 | key不可重复 | key不可修改 | O(1) | O(1) |
先判断两个字符串的长度是否相等,如果不相等,直接返回false
否则,两个字符串一起操作,同一个哈希表,一个++,一个减减
最后使用迭代器看看哈希表里面的元素是否全为0,如果存在不为0的,返回false;
否则,返回true。
class Solution {
public:
bool isAnagram(string s, string t) {
int m=s.size();
int n=t.size();
if(m!=n) return false;
unordered_map<char,int> map;
for(int i=0;i<m;i++){//前面判断了如果两者长度不一样,肯定会return false。这里的m肯定等于n,两个字符串一起操作即可
map[s[i]]++;
map[t[i]]--;
}
//哈希表操作:迭代器
for(unordered_map<char,int>::iterator it=map.begin();it!=map.end();it++){
if(it->second!=0) return false;
}
return true;
}
};
方法一:哈希表+迭代器
观察发现,当前者字符串的长度大于后者字符串长度时,肯定不可能构成题目要求的。
0~m-1长度,利用哈希表前者++,后者减减
m~n-1长度,利用哈希表,计算后者字符串的值,也即是减减操作
迭代器判断,如果发现有大于0的,表明前者字符串还差字符,构不成,直接return false
否则,返回true
for(unordered_map<char,int>::iterator it=map.begin();it!=map.end();it++){
if(it->second>0) return false;
}
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int m=ransomNote.size();
int n=magazine.size();
if(m>n) return false;
unordered_map<char,int> map;
for(int i=0;i<m;i++){
map[ransomNote[i]]++;
map[magazine[i]]--;
}
for(int j=m;j<n;j++){
map[magazine[j]]--;
}
for(unordered_map<char,int>::iterator it=map.begin();it!=map.end();it++){
if(it->second>0) return false;
}
return true;
}
};
方法二:用数组做哈希
定义一个数组叫做record用来上记录字符串里字符出现的次数,初始化伪0
在遍历后者字符串的时候,只需要将 magazine[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。
接着在遍历前者字符串的时候,只需要将 ransomNote[i] - ‘a’ 所在的元素做-1 操作即可,接着判断其是否小于0(表明后者字符串magazine中没有前者字符串中的字符),直接返回false
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int record[26]={0};//记录两个字符串中字符的相对值,初始化为0
for(int i=0;i<magazine.size();i++){
record[magazine[i]-'a']+=1;
}
for(int j=0;j<ransomNote.size();j++){
record[ransomNote[j]-'a']-=1;
if(record[ransomNote[j]-'a']<0) return false;
}
return true;
}
};
哈希表+迭代器
定义哈希表,第一个元素存放的是经过排序的单词,第二个元素存放的是字母异位词组(原来的词)
遍历字符串数组,依次取单词暂存为temp,排序每个单词,然后将排序好的单词和原来的单词作为一组放一起(也即是map[temp].push_back(strs[i]);)。
迭代器遍历哈希表 for(auto it=map.begin();it!=map.end();it++),auto比unordered_map<string,vector<string>>::iterator省时间。依次将it->second push—back进res
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
vector<vector<string>>res;
int n=strs.size();
if(n==0||n==1) return {strs};
unordered_map<string,vector<string>> map;//map中第一个存放的是经过排序的单词,第二个存放的是字母异位词组(原来的词)
for(int i=0;i<n;i++){
string temp=strs[i];//依次取单词
sort(temp.begin(),temp.end());//将依次取出来的单词排序
map[temp].push_back(strs[i]);//map.first是排序好的单词,map.second是原单词
}
//使用迭代器
for(auto it=map.begin();it!=map.end();it++){
res.push_back(it->second);
}
return res;
}
};
由于题目都限制了数组的大小,用数组做哈希
vector<int> record_s(26,0),record_p(26,0);
不需要记住字符的ASCII,只要求出一个相对数值就可以了。也就是 record_s[s[i]-'a']++;
先比较前m个,判断两个vector是否相等。接着窗口移动(for(;i<s.size();i++){//窗口移动)
将排出窗口的元素对应的数组-1,新进窗口的元素对应数组+1,判断两个vector是否相等(相等则把左边界放进res中)
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> res;
if(s.size()<p.size()) return res;
vector<int> record_s(26,0),record_p(26,0);
int i=0,m=p.size();
for(;i<m;i++){//比较前M个
record_s[s[i]-'a']++;
record_p[p[i]-'a']++;
}
if(record_p==record_s) res.push_back(0);//vector可以直接比较大小
for(;i<s.size();i++){//窗口移动
record_s[s[i-m]-'a']--;//将移出窗口的元素所对应的数组减1
record_s[s[i]-'a']++;
if(record_p==record_s) res.push_back(i-m+1);
}
return res;
}
};