部分参考:代码随想录 (programmercarl.com)
常见的三种哈希结构
一般哈希表都是用来快速判断一个元素是否出现集合里。
例如要查询一个名字是否在这所学校里。
要枚举的话时间复杂度是O(n),但如果使用哈希表的话, 只需要O(1)就可以做到。
我们只需要初始化把这所学校里学生的名字都存在哈希表里,在查询的时候通过索引直接就可以知道这位同学在不在这所学校里了。
当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。
- 数组
- set (集合)
- map(映射)
std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。
当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
那么再来看一下map ,在map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。
虽然std::set、std::multiset 的底层实现是红黑树,不是哈希表,std::set、std::multiset 使用红黑树来索引和存储,不过给我们的使用方式,还是哈希法的使用方式,即key和value。所以使用这些数据结构来解决映射问题的方法,我们依然称之为哈希法。 map也是一样的道理。
这里在说一下,一些C++的经典书籍上 例如STL源码剖析,说到了hash_set hash_map,这个与unordered_set,unordered_map又有什么关系呢?
实际上功能都是一样一样的, 但是unordered_set在C++11的时候被引入标准库了,而hash_set并没有,所以建议还是使用unordered_set比较好,这就好比一个是官方认证的,hash_set,hash_map 是C++11标准之前民间高手自发造的轮子。
242.有效的字母异位词
class Solution {
public:
//进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
//那只能使用哈希表了unordered_map<char,int> mymap
bool isAnagram(string s, string t) {
int hasharray[26]={0};
for(int i=0;i<s.size();i++){
hasharray[s[i]-'a']++;
}
for(int i=0;i<t.size();i++){
hasharray[t[i]-'a']--;
}
for(int i=0;i<26;i++){
if(hasharray[i]!=0) return false;
}
return true;
}
};
进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
class Solution {
public:
//进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
//那只能使用哈希表了unordered_map<char,int> mymap
bool isAnagram(string s, string t) {
unordered_map<char,int> mymap;
for(int i = 0; i < s.size(); i++){
mymap[s[i]]++;
}
for(int i = 0; i < t.size(); i++){
mymap[t[i]]--;
}
for(int i = 0; i < s.size(); i++){
if(mymap[s[i]] != 0 )
return false;
}
for(int i = 0; i < t.size(); i++){
if(mymap[t[i]] != 0 )
return false;
}
return true;
}
};
349. 两个数组的交集
class Solution {
public:
//输出结果中的每个元素一定是唯一的,所以结果要用unordered_set转化为vector
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> uset;
unordered_set<int> tem;
for(int i = 0; i < nums1.size(); i++){
uset.insert(nums1[i]);
}
for(int i = 0; i < nums2.size(); i++){
if(uset.count(nums2[i])){
tem.insert(nums2[i]);
}
}
return vector<int> (tem.begin(),tem.end());
}
};
用数组替代哈希表来做
class Solution {
public:
//用数组来做
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int> hash(1005, 0);
vector<int> res;
for(int i = 0; i < nums1.size(); i++){
hash[nums1[i]]++;
}
for(int i = 0; i < nums2.size(); i++){
if(hash[nums2[i]] != 0){
res.push_back(nums2[i]);
hash[nums2[i]] = 0;
}
}
return res;
}
};
202. 快乐数
class Solution {
public:
//如何取数值各个位上的单数
//求和的过程中,sum会重复出现,这对解题很重要!
int getSum(int n){//获取各个位上的单数的操作
int sum = 0;
while(n){
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
bool isHappy(int n) {
int sum;
unordered_set<int> uset;//这个要放在循环外面
//还需要注意不能使用递归,否则栈溢出
while(1){
sum = getSum(n);
if(sum == 1) return true;
else{
if(uset.count(sum)) return false;
uset.insert(sum);
n = sum;
}
}
}
};
1. 两数之和
class Solution {
public:
//这题要获取下标,所以不能排序
vector<int> twoSum(vector<int>& nums, int target) {
//vector<int> res(2, 0);
unordered_map<int, int> umap;
for(int i = 0; i<nums.size(); i++){
if(umap.count(target - nums[i])){
return {i, umap[target - nums[i]]};
}
umap[nums[i]] = i;
//umap.insert(pair<int, int>(nums[i], i));
}
return {};
}
};
454.四数相加II
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> umap;
int res = 0;
for(int i = 0; i < nums1.size(); i++){
for(int j = 0; j < nums2.size(); j++){
umap[nums1[i] + nums2[j]]++;
}
}
for(int i = 0; i < nums3.size(); i++){
for(int j = 0; j < nums4.size(); j++){
if(umap.count(0 - nums3[i] - nums4[j])){
res += umap[0 - nums3[i] - nums4[j]];
}
}
}
return res;
}
};
383. 赎金信
// 时间复杂度: O(n)
// 空间复杂度:O(1)
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int record[26] = {0};
//add
if (ransomNote.size() > magazine.size()) {
return false;
}
for (int i = 0; i < magazine.length(); i++) {
// 通过recode数据记录 magazine里各个字符出现次数
record[magazine[i]-'a'] ++;
}
for (int j = 0; j < ransomNote.length(); j++) {
// 遍历ransomNote,在record里对应的字符个数做--操作
record[ransomNote[j]-'a']--;
// 如果小于零说明ransomNote里出现的字符,magazine没有
if(record[ransomNote[j]-'a'] < 0) {
return false;
}
}
return true;
}
};
904. 水果成篮
class Solution {
public:
//这题用set做不了,set是去重的,假如加入了多个3进去,删除时却只需要删除一次就将所有3全部删了
//案例:[3,3,3,1,2,1,1,2,3,3,4]
int totalFruit(vector<int>& fruits) {
int i = 0, j = 0;
int res = 0;
unordered_map<int,int> mymap;
for(;j < fruits.size(); j++){
mymap[fruits[j]]++;
while(mymap.size() > 2){//判断
mymap[fruits[i]]--;
if(mymap[fruits[i]]==0){
mymap.erase(fruits[i]);
}
i++;
}
res=max(res,j-i+1);
}
return res;
}
};
560. 和为 K 的子数组
class Solution {
public:
//连续子数组求和,想到前缀和
int subarraySum(vector<int>& nums, int k) {
int sum = 0;
int res = 0;
unordered_map<int, int> umap;
umap[0] = 1;
for(int i = 0; i < nums.size(); i++){
sum += nums[i];
if(umap.count(sum - k)){
res += umap[sum - k];
}
umap[sum]++;
cout<<res<<endl;
}
return res;
}
};
15. 三数之和
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()-2;i++){
if(nums[i]>0) return res;
if(i>0&&nums[i]==nums[i-1]) continue;//去重
int l=i+1;
int r=nums.size()-1;
while(l<r){
if(nums[i]+nums[l]+nums[r]>0){
r--;
}else if(nums[i]+nums[l]+nums[r]<0){
l++;
}else{
res.push_back(vector<int>{nums[i],nums[l],nums[r]});//注意这里是大括号,强制类型转化
while(l<nums.size()-2&&nums[l]==nums[l+1]) l++;//这两行前面的判断条件都是有必要的,否则会越界
while(r>0&&nums[r]==nums[r-1]) r--;
l++;
r--;
}
}
}
return res;
}
};
18. 四数之和
class Solution {
public:
/*
/*
/*获取当前最小值,如果最小值比目标值大,说明后面越来越大的值根本没戏*/
// int min1=nums[k]+nums[k+1]+nums[k+2]+nums[k+3];
// if(min1>target){
// break;
// }
// /*获取当前最大值,如果最大值比目标值小,说明后面越来越小的值根本没戏,忽略*/
// int max1=nums[k]+nums[length-1]+nums[length-2]+nums[length-3];
// if(max1<target){
// continue;
// }
/*作者:有为吴
链接:https://leetcode.cn/problems/4sum/solutions/44281/ji-bai-9994de-yong-hu-you-dai-ma-you-zhu-shi-by-yo/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
int l = 0,r = 0;
int n = nums.size();
for(int i = 0; i < n-3; i++){//这里为什么i < nums.size()-3就不行,改成n-3就可以过
//nums.size()是无符号整型,c++就是有这个坑,java这样写就可以过
if (nums[i] > target && nums[i] >= 0) {
break; // 这里使用break,统一通过最后的return返回
}
if(i > 0 && nums[i]==nums[i-1]) continue;//去重
for(int j = i + 1; j < n-2; j++){
if (nums[i] + nums[j] > target && nums[i] + nums[j] >= 0) {
break;
}
if(j > i+1 && nums[j]==nums[j-1]) continue;//去重
l = j + 1;
r = nums.size() - 1;
while(l < r){
if((long) nums[i] + nums[j] + nums[l] + nums[r] > target) r--;
else if((long) nums[i] + nums[j] + nums[l] + nums[r] < target) l++;
else{
res.push_back(vector<int>{nums[i] , nums[j] , nums[l] , nums[r]});
while(r > l && nums[l] == nums[l+1]) l++;
while(r > l && nums[r] == nums[r-1]) r--;
l++;
r--;
}
}
}
}
return res;
}
};