哈希法使用场景,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
242.有效的字母异位词
题目链接/文章讲解/视频讲解: 代码随想录
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']++;
}
for(int j=0;j<t.size();j++)
{
hash[t[j]-'a']--;
}
for(int i=0;i<25;i++)
{
if(hash[i]!=0)
{return false;}
}
return true;
}
};
注意点:
hash[s[i]-'a']++;为哈希映射,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了。
- 时间复杂度:O(n + m),其中
n
和m
是两个字符串的长度。(遍历s,为每一个字符访问一遍,数组一次访问的时间复杂度为O(1),访问n次,为O(n),遍历字符串t同理) - 空间复杂度:O(1)。
- 哈希表
hash
:
哈希表hash[26]
是一个固定大小的数组,用来记录每个字母的出现次数。由于数组的大小是固定的 26(表示小写字母 'a' 到 'z'),所以这一部分的空间复杂度是 O(1)。
关于hash[26]空间复杂度为O(1)的问题:如果使用数据的大小与输入数据的规模有关,例如,你在处理一个长度为 n
的字符串,并且需要一个数组来记录每个字符的出现次数(如 hash[n]
),那么空间复杂度会是 O(n),因为数组的大小是动态变化的,取决于输入的大小。
但是,如果你使用的是一个大小固定的数组(数组的长度不会变化),如 hash[26]
,那么无论输入的数据量如何变化,这个数组的大小始终不变,因此空间复杂度为 O(1)。
349. 两个数组的交集
1.建议使用set,(两个数组其中的数差值较大,数量较多)
题目链接/文章讲解/视频讲解:代码随想录
哈希数据结构:unordered_set(已经进行数组去重操作),无序(效率更高)
set(自动排序,升序)
注意题目特意说明:输出结果中的每个元素一定是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int>result;//存放两数组交集的结果
unordered_set<int>nums01(nums1.begin(),nums1.end());
for(int nums:nums2)
{
if(nums01.find(nums)!=nums01.end())
{
result.insert(nums);
}
}
return vector<int>(result.begin(),result.end());
}
};
注意点:find(10) != s.end()
判断了 set
是否包含元素 10
。如果 find
返回的迭代器不是 end()
,说明 10
存在于 set
中。
std::set
是一个集合容器,自动按升序(默认)排列,且不允许重复元素。- 常见的操作包括插入、查找、删除、遍历、获取大小等。
set
中的元素都是唯一的,且内部自动排序,因此查找、插入、删除等操作的时间复杂度通常是 O(log n)。
2.直接采用数组
class Solution { public: vector<int> intersection(vector<int>& nums1, vector<int>& nums2) { unordered_set<int>result;//存放两数组交集的结果 int hash[1005]={0}; for(int nums:nums1)//遍历num1中所有的nums { hash[nums]=1; } for(int nums:nums2) { if(hash[nums]==1) {result.insert(nums);} } return vector<int>(result.begin(),result.end()); } };
比较:直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。
202. 快乐数 (使用set)
题目链接/文章讲解:代码随想录
题目中说了会 无限循环,那么也就是说求和的过程中,sum会重复出现,这对解题很重要!
可采用unordered_set,底层逻辑为哈希实现,不能重复的数组,可用来判断sum是否会重复出现,unordered_set用来存储已经出现的sum值。
class Solution {
public:
int getsum(int n)
{
int sum=0;
while(n)
{
sum+=(n%10)*(n%10);
n=n/10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int>s;//不能重复的数组,判断是否无限循环,因为循环一定重复
while(1)
{
int sum=getsum(n);
if(sum==1){return true;}
if(s.find(sum)!=s.end())
{
return false;
}
else {s.insert(sum);}
n=sum;//更新sum的值
}
}
};
1. 两数之和 (map解决)
题目链接/文章讲解/视频讲解:代码随想录
1.什么时候可以使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。本题呢,我就需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。
2.此时就要选择另一种数据结构:map ,map是一种key value的存储结构,可以用key保存数值,用value再保存数值所在的下标。
std::pair
在 std::map
中非常常用,因为 std::map
是一个键值对容器,每个元素都由一个 pair
组成,其中:
first
是键(Key
)。second
是值(Value
)。
3.这道题目中并不需要key有序,选择std::unordered_map 效率更高!
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int>m;//存储已经遍历的数组元素
for(int i=0;i<nums.size();i++)
{
int need=target-nums[i];
auto iter=m.find(need);//返回的是一个迭代器
if(iter!=m.end())
{
return {iter->second,i};
}
else
{
m.insert(pair<int,int>(nums[i],i));//若没有在已有的存储容器中map找到相应的值,则将数值插入遍历容器中
}
}
return{};
}
};
return {};
表示返回一个空的 std::vector<int>
。
return {};
不能存在于 for
循环的括号内,是因为 return
语句会立即结束函数的执行,导致循环不再继续执行。所以,若将 return {};
放入循环体内,它会在第一次执行时就退出函数,跳过后续的代码。
具体原因:
-
return
语句的作用:return
用来退出函数并返回一个值。无论return
语句在函数体中的哪个位置被执行,都会立即终止函数的执行,不会再执行任何后续代码。
-
for
循环和return
:- 如果
return {};
在for
循环的括号内(即循环体内),当第一次执行到return {};
时,整个函数会立即返回,不会继续执行后续的循环迭代。 - 这意味着如果你在循环中写
return {};
,在任何一次循环中,函数就会提前结束,导致无法完成对数组中所有元素的遍历。 return {};
只能放在函数末尾:return {};
应该放在for
循环外面,当遍历完整个数组后,仍未找到匹配的元素时,函数才会返回空的std::vector<int>
。
- 如果
二.采用双指针法解决(确保数组已经排序)
排序后,索引信息会被打乱,为了保持原数组的索引信息,你可以在排序时保持元素的原始索引。你可以使用 pair<int, int>
来存储元素和它的索引。排序时根据元素的值排序,但仍然保留元素的原始位置。
sort(indexed_nums.begin(), indexed_nums.end());
语句会对 indexed_nums
中的元素进行排序,默认是按 pair
中的第一个元素(即数组中的值)排序。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
// 创建一个存储元素及其索引的数组
vector<pair<int, int>> indexed_nums;
for (int i = 0; i < nums.size(); i++) {
indexed_nums.push_back({nums[i], i});
}
// 按照元素值排序
sort(indexed_nums.begin(), indexed_nums.end());
int index1 = 0;
int index2 = indexed_nums.size() - 1;
while (index1 < index2) {
int sum = indexed_nums[index1].first + indexed_nums[index2].first;
if (sum == target) {
// 返回原始索引
return {indexed_nums[index1].second, indexed_nums[index2].second};
} else if (sum < target) {
index1++;
} else {
index2--;
}
}
return {}; // 如果没有找到,返回空
}
};