哈希表是根据关键码的值而直接进行访问的数据结构。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希表
,函数f(key)为哈希函数
。
哈希表通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。
它牺牲了空间换取了时间,因为要使用额外的数组、set或map来存放数据,才能实现快速的查找。
当我们要快速判断一个元素是否出现在集合里时,就要考虑哈希法。
常见的三种哈希数据结构:数组、set、map。
在C++中,set和map又分别被提供了三种数据结构。
std::unordered_set
底层实现为哈希表,std::set
和std::multiset
的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
当我们要用集合来解决问题时,优先用unordered_set
,因为它的查询和增删效率最优,如果需要集合是有序的则用set
,如果要求不仅有序还要有重复数据的话,就用multiset
。
map 是一个key-value
的数据结构,map中对key有限制,对value没有限制,因为key是使用红黑树存储的。
std::unordered_map
底层实现为哈希表,std::map
和std::multimap
的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的。
映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
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) |
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
class Solution {
public:
bool isAnagram(string s, string t) {
if(s.size() != t.size()) {
return false;
}
int record[26] = {0};
for(int i = 0; i < s.size(); i++) {
record[s[i] - 'a']++;
}
for(int i = 0; i < t.size(); i++) {
record[t[i] - 'a']--;
}
for(int i = 0; i < 26; i++) {
if(record[i] != 0) {
return false;
}
}
return true;
}
};
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
if(ransomNote.size() > magazine.size()) {
return false;
}
int record[26] = {0};
for(int i = 0; i < magazine.size(); i++) {
record[magazine[i] - 'a']++;
}
for(int i = 0; i < ransomNote.size(); i++) {
record[ransomNote[i] - 'a']--;
if(record[ransomNote[i] - 'a'] < 0) {
return false;
}
}
return true;
}
};
给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> resultSet;
unordered_set<int> tmpSet(nums1.begin(), nums1.end());
for(int num : nums2) {
if(tmpSet.find(num) != tmpSet.end()) {
resultSet.insert(num);
}
}
return vector<int>(resultSet.begin(), resultSet.end());
}
};
给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int, int> tmpMap;
vector<int> result;
for(int num : nums1) {
tmpMap[num]++;
}
for(int num : nums2) {
if(tmpMap.count(num)) {
result.push_back(num);
tmpMap[num]--;
if(tmpMap[num] == 0) {
tmpMap.erase(num);
}
}
}
return result;
}
};
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
class Solution {
public:
int getSum(int n) {
int result = 0;
while(n) {
result += (n % 10) * (n % 10);
n /= 10;
}
return result;
}
bool isHappy(int n) {
unordered_set<int> sums;
while(1) {
int sum = getSum(n);
if(sum == 1) {
return true;
}
if(sums.find(sum) != sums.end()) {
return false;
}
sums.insert(sum);
n = sum;
}
}
};
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> map; // 存放我们已经访问过的元素
for(int i = 0; i < nums.size(); i++) {
auto it = map.find(target - nums[i]);
if(it != map.end()) {
return {it->second, i};
}
map.insert(pair<int, int>(nums[i], i));
}
return {};
}
};
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
// TODO