哈希表总结
哈希表总结
1、 基础知识
哈希表又称为散列表
使用哈希表解决问题的时候,要用到的数据结构为:数组,Set,Map,数组很简单,主要说一下Set和Map
C++里面,Set和Map分别提供三种数据结构(图来自代码随想录)
- Map(映射)
map作为关联容器的一种,储存的都是pair对象(pair<const K, T> ),储存的时候用pair类模板创建的键值对,需要注意的是:使用 map 容器存储的各个键值对,键的值既不能重复也不能被修改。 - Set(集合)
set 容器存储的各个键值对,其键和值完全相同,也就意味着它们的类型相同,因此 set 容器类模板的定义中,仅有第 1 个参数用于设定存储数据的类型
可以看到,unordered的map和set查找效率和增删效率是很优的,优先使用。如果需要集合是有序的,那就可以用set和map,要求重复数据的话,multi是可以用的。
如果我们要快速判断一个元素是否在集合里出现过的时候,就需要考虑哈希法,具体的选择思路后续的题目总结中会介绍。
2、 相关算法题及解决思路
题目描述:
设定一个二十六位的数组就可以解决:
class Solution {
public:
bool isAnagram(string s, string t) {
bool result;
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']--;
}
/* cout << (sizeof(record)/sizeof(record[0])) << endl;
cout << 26 << endl; */
for (int i = 0; i < (sizeof(record)/sizeof(record[0])); i++) {
result = true;
if(record[i] != 0)
{
result = false;
break;
}
}
return result;
}
};
- 两个数组的交集
题目描述:
可以使用unordered_set,将其中一个数组插入,(符合不重复,无需排序),然后在unordered_set中寻找
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
int hash[1005] = {0};
unordered_set <int> res;
for (int num : nums1) {
hash[num] = 1;
}
for (int num : nums2) {
if (hash[num] == 1) {
res.emplace(num);
}
}
return vector<int>(res.begin() , res.end());
}
};
题目描述:
这个题最重要的就是无限循环,无限循环就意味着相加结果会出现重复的情况,可以使用unordered_set储存相加结果,若出现重复,返回false,相加等于1,返回true。
class Solution {
public:
int getsum(int num) {
int sum = 0;
while (num != 0) {
sum += (num % 10) * (num % 10);
num /= 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> sum_res_his;
int sum_res = getsum(n);
int c = 0;
int a = 0;
while (sum_res != 1) {
if (sum_res_his.find(sum_res) != sum_res_his.end()) {
//cout << c << endl;
//cout << a << endl;
return false;
}
else {
sum_res_his.insert(sum_res);
//c++;
}
sum_res = getsum(sum_res);
//a++;
}
return true;
}
};
题目描述:
每种输入只会对应一个答案,就意味着数组当中只有这两个数可以完成条件,且不能重复使用。
把这个数组放到map中,key为数值,对应的值为下标,遍历map容器,寻找相加为target的,并返回对应即可。可以边放入map,边寻找。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::unordered_map <int, int> his;
for (int i = 0; i < nums.size(); i++) {
auto iter = his.find(target - nums[i]);
if (iter != his.end()) {
return vector<int>{iter->second, i};
}//寻找部分
his.insert(pair<int ,int>(nums[i], i));//放入部分
}
return {};
}
};
和解决两书相加的思路一致,先组合前两个数组,相加的和作为key放入map当中,对应的值为相加为此值的组数,再在map中寻找能和后两个数组相加为0的,然后统计前两个数组形成的map里面的次数即可(因为若满足条件,map存储的必然是所出现的次数)
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map <int, int> sum_resu;
for (auto i1 : nums1) {
for (auto i2 : nums2) {
sum_resu[i1 + i2]++;
}
}
int count = 0;
for (auto i3 : nums3) {
for (auto i4 :nums4) {
if (sum_resu.find(0 - (i3 + i4)) != sum_resu.end()) {
count += sum_resu[(0 - (i3 + i4))];
}
}
}
return count;
}
};
这个题让人头皮发麻,最主要的要求是不重复,在数组内找出来不重复的三元组让其相加等于0。如果用哈希法写,去重很困难,有很多的问题都会想不到,博主头铁写了一下,到现在都没搞清楚,可能实力还不够,我们直接来说双指针法。双指针法在链表中也用到过,非常好理解,并且思路可以适用于四数之和,五数之和......
双指针法同样,也面临的问题就是,如何去重,具体的思路就是:指定第一个数a,然后在a的下一个数作为b,数组的最后一个数作为c,如果三数之和大于0,c向前一个数(有序的好处),如果小于0,b向前一步,并且,在a,b,c选择挪动的时候需要考虑怎么去重,去重方式很重要。
代码如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++) {
if (nums[0] > 0) {
break;
}
if (i > 0 && nums[i] == nums[i-1]) {
continue;
}
int right = nums.size() - 1;
int left = i + 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
}
if (sum < 0) {
left++;
}
if (sum == 0) {
res.push_back(vector<int>{nums[i], nums[left], nums[right]});
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
left++;
right--;
}
/* while (left < right && nums[right] == nums[right + 1]) {
right--;
} */
}
}
return res;
}
};
需要注意的点有:
1、排序(有助于我们快速判断,并且有助于指针的操作)
2、我们需要注意,题目要求的是三元组不能重复,但是在三元组内部,是可以重复的
3、我们需要寻找的是a+b+c=0,输出[a,b,c],a如何去重?nums[i] == nums[i-1] 作为判断条件,还是nums[i] == nums[i+1]作为判断条件?如右图第一种情况,若nums[i] == nums[i+1]作为判断条件,如下图①,就会出现直接跳过了b = -2这种情况,而nums[i] == nums[i-1]就不会这样。
4、同样的问题,带到b,c去重,b,c去重是先判断还是先找三元组?下图中②所示,如果先去重,会忽略b = 1的情况,导致跳过了a = -2,b = 1,c = 1这种情况,所以先组成三元组,然后在b,c向前的方向判断b,c是否重复即可
四数之和同样的道理,如下图
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(),nums.end());
for (int i = 0; i < nums.size(); i++) {
if (nums[i] > target && nums[i] > 0) break;
if (i > 0 && nums[i] == nums[i-1]) continue; //i去重
for (int j = i + 1; j < nums.size(); j++) {
if (nums[i] + nums[j] > target && nums[i] + nums[j] >= 0) break;
if (j > i + 1 && nums[j-1] == nums[j]) continue;//j去重
int left = j + 1;
int right = nums.size() - 1;
while (left < right) {
int sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
if (sum > target) {
right--;
}
else if (sum < target) {
left++;
}
else if (sum == target) {
res.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
left++;
right--;
}
}
}
}
return res;
}
};
题目描述:
就像有效字母有效的字母异位词一样,但是要求每个字符在ransomnote中只出现一次,哈希法最简单不过了,但是到底是用数组呢还是map?map的寻找建立比较费时,最好用数组就可以,再说字母总共26位,是有限的,我为了熟悉map的应用,也写了map的版本。
map版本
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
map<char, int> store_ran;
// ransomNote放入map当中,key为字母,对应的值为次数
for (int i = 0; i < ransomNote.length(); i++) {
if (store_ran.find(ransomNote[i]) != store_ran.end()) {
store_ran.at(ransomNote[i])++;
}
else {
store_ran.emplace(make_pair(ransomNote[i],1));
}
}
for (int i = 0; i < magazine.length(); i++) {
if (store_ran.find(magazine[i]) != store_ran.end()) {
store_ran.at(magazine[i])--;
}
}//如果map中各个key对应的值为0或者小于0,则证明就是赎金信
for (auto iter = store_ran.begin(); iter != store_ran.end(); iter++) {
if(iter->second > 0) {
return false;
}
}//遍历map,一旦出现key值对应的为大于0状态,返回false
return true;
}
};
数组版本
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int hash_arry[26] = {0};
if (ransomNote.length() > magazine.length()) {
return false;
}
for (int i = 0; i < magazine.length(); i++) {
hash_arry[magazine[i] - 'a']++;
}
for (int i = 0; i < ransomNote.length(); i++) {
hash_arry[ransomNote[i] - 'a']--;
if (hash_arry[ransomNote[i] - 'a'] < 0) {
return false;
}
}
return true;
}
};
参考:代码随想录