1.哈希表理论基础
当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。
- 数组
- set (集合)
- map(映射)
这里数组就没啥可说的了,我们来看一下set。
在C++中,set 和 map 分别提供以下三种数据结构,其底层实现以及优劣如下表所示:
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动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) |
std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。
当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
那么再来看一下map ,在map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。
其他语言例如:java里的HashMap ,TreeMap 都是一样的原理。可以灵活贯通。
虽然std::set、std::multiset 的底层实现是红黑树,不是哈希表,std::set、std::multiset 使用红黑树来索引和存储,不过给我们的使用方式,还是哈希法的使用方式,即key和value。所以使用这些数据结构来解决映射问题的方法,我们依然称之为哈希法。 map也是一样的道理。
2.有效的字母异位词
这道题主要是学会如何使用数组进行作为哈希表。通过将第一个单词中每个字母出现的频率一一映射到数组中,再用该数组与另一个单词中的字母进行一一比较,就可以很轻易的得出这两个单词是否位异位词了时间复杂度为O(n)。当然啦,还有一种较为简单的方法,就是直接将两个单词进行排序,如果排序后两个单词一致,则说明两个单词互为异位词,这种方法的时间复杂度为O(n*logn)。
哈希法代码如下:
class Solution { //方法一:使用哈希表
public:
bool isAnagram(string s, string t) {
int hash[26] = {0};
for (int i = 0; i < s.size(); i++) {
hash[s[i] - 'a']++; //这一句就说明不需要知道‘a’的ASCII就可以将字母映射到数组中去,这相当于哈希函数
}
for (int i = 0; i < t.size(); i++) {
hash[t[i] - 'a']--;
}
for (int i = 0; i < 26; i++) {
if (hash[i] != 0) {
return false;
}
}
return true;
}
};
排序法代码如下:
class Solution { //方法二:排序
public:
bool isAnagram(string s, string t) {
if (s.length() != t.length()) {
return false;
}
sort(s.begin(), s.end());
sort(t.begin(), t.end());
if (s == t) {
return true;
} else {
return false;
}
}
};
可在本地IDE运行的完整代码如下:
#include <iostream>
#include <string>
using namespace std;
// class Solution { //方法一:使用哈希表
// public:
// bool isAnagram(string s, string t) {
// int hash[26] = {0};
// for (int i = 0; i < s.size(); i++) {
// hash[s[i] - 'a']++; //这一句就说明不需要知道‘a’的ASCII就可以将字母映射到数组中去,这相当于哈希函数
// }
// for (int i = 0; i < t.size(); i++) {
// hash[t[i] - 'a']--;
// }
// for (int i = 0; i < 26; i++) {
// if (hash[i] != 0) {
// return false;
// }
// }
// return true;
// }
// };
class Solution { //方法二:排序
public:
bool isAnagram(string s, string t) {
if (s.length() != t.length()) {
return false;
}
sort(s.begin(), s.end());
sort(t.begin(), t.end());
if (s == t) {
return true;
} else {
return false;
}
}
};
int main() {
string s = "anagram";
string t = "ngramaa";
Solution sol;
bool ans = sol.isAnagram(s, t);
cout << ans << endl;
return 0;
}
3.两个数组的交集
这个题目很简单,使用哈希法,用一个unordered_set就能轻松解决。这里不多赘述,直接放代码。
代码如下:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) { //搞复杂了,用unordered_set就能解决
unordered_set<int> hashSet;
unordered_set<int> result;
for (int i = 0; i < nums1.size(); i++) {
hashSet.insert(nums1[i]);
}
for (int i = 0; i < nums2.size(); i++) {
if (hashSet.count(nums2[i])) {
result.insert(nums2[i]);
}
}
vector<int> ans(result.begin(), result.end());
return ans;
}
};
可在本地IDE完整运行的代码如下:
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
using namespace std;
// class Solution {
// public:
// vector<int> intersection(vector<int>& nums1, vector<int>& nums2) { //搞复杂了,其实用unordered_set就能解决,没必要用unordered_map
// unordered_map<int, int> hashMap;
// int length1 = nums1.size();
// int length2 = nums2.size();
// unordered_map<int, int> ans;
// unordered_map<int, int>::iterator it;
// vector<int> result;
// for (int i = 0; i < length1; i++) {
// int key = nums1[i];
// hashMap[key] = nums1[i];
// }
// for (int i = 0; i < length2; i++) {
// if (hashMap.count(nums2[i]) > 0) {
// int key = nums2[i];
// ans[key] = nums2[i];
// }
// }
// for (it = ans.begin(); it != ans.end(); it++) {
// result.push_back(it->second);
// }
// return result;
// }
// };
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) { //搞复杂了,用unordered_set就能解决
unordered_set<int> hashSet;
unordered_set<int> result;
for (int i = 0; i < nums1.size(); i++) {
hashSet.insert(nums1[i]);
}
for (int i = 0; i < nums2.size(); i++) {
if (hashSet.count(nums2[i])) {
result.insert(nums2[i]);
}
}
vector<int> ans(result.begin(), result.end());
return ans;
}
};
int main() {
Solution sol;
vector<int> nums1;
nums1.push_back(4);
nums1.push_back(9);
nums1.push_back(5);
vector<int> nums2;
nums2.push_back(9);
nums2.push_back(4);
nums2.push_back(9);
nums2.push_back(8);
nums2.push_back(4);
vector<int> ans = sol.intersection(nums1, nums2);
for (int i = 0; i < ans.size(); i++) {
cout << ans[i] << " ";
}
cout << endl;
}
4.快乐数
解题思路:这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。
代码如下:
class Solution {
public:
int getSum (int n) {
int sum = 0;
while (n) {
int m = n % 10;
n /= 10;
sum = sum + m * m;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> set;
while (n != 1) {
int sum = getSum(n);
if (set.find(sum) != set.end()) {
return false;
} else {
set.insert(sum);
n = sum;
}
}
return true;
}
};
可在本地IDE运行的完整代码如下:
#include <iostream>
#include <unordered_set>
using namespace std;
class Solution {
public:
int getSum (int n) {
int sum = 0;
while (n) {
int m = n % 10;
n /= 10;
sum = sum + m * m;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> set;
while (n != 1) {
int sum = getSum(n);
if (set.find(sum) != set.end()) {
return false;
} else {
set.insert(sum);
n = sum;
}
}
return true;
}
};
int main() {
Solution sol;
int n = 19;
bool ans = sol.isHappy(n);
cout << ans << endl;
}
5.两数之和
这道题有两个解法:1.两个for循环,暴力求解 2.哈希法,使用unordered_map,先遍历一个数组中的元素给map到哈希表中,然后再遍历另一个数组,查看是否有符合题意的数。
暴力解法代码如下:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) { //方法一:暴力解法
vector<int> result;
for (int i = 0; i < nums.size() - 1; i++) {
for (int j = i + 1; j < nums.size(); j++) {
if (nums[i] + nums[j] == target) {
result.push_back(i);
result.push_back(j);
return result;
}
}
}
return result;
}
};
哈希法代码如下:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) { //方法二:哈希表
vector<int> result;
unordered_map<int, int> map;
unordered_map<int, int>::iterator it;
for (int i = 0; i < nums.size(); i++) {
it = map.find(target - nums[i]);
if (it != map.end()) {
result.push_back(i);
result.push_back(it->second);
return result;
} else {
map.insert(pair<int, int>(nums[i], i));
}
}
return result;
}
};
可在本地IDE完整运行的代码如下:
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
// class Solution {
// public:
// vector<int> twoSum(vector<int>& nums, int target) { //方法一:暴力解法
// vector<int> result;
// for (int i = 0; i < nums.size() - 1; i++) {
// for (int j = i + 1; j < nums.size(); j++) {
// if (nums[i] + nums[j] == target) {
// result.push_back(i);
// result.push_back(j);
// return result;
// }
// }
// }
// return result;
// }
// };
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) { //方法二:哈希表
vector<int> result;
unordered_map<int, int> map;
unordered_map<int, int>::iterator it;
for (int i = 0; i < nums.size(); i++) {
it = map.find(target - nums[i]);
if (it != map.end()) {
result.push_back(i);
result.push_back(it->second);
return result;
} else {
map.insert(pair<int, int>(nums[i], i));
}
}
return result;
}
};
int main() {
vector<int> nums;
nums.push_back(2);
nums.push_back(7);
nums.push_back(11);
nums.push_back(15);
int target = 9;
Solution sol;
vector<int> ans = sol.twoSum(nums, target);
for (int i = 0; i < ans.size(); i++) {
cout << ans[i] << " ";
}
cout << endl;
}
今毕。