1 哈希Hash
哈希算法就是一种空间换时间的思想,利用红黑树(一种类似AVL树的平衡树)、哈希表等数据结构,实现增、取的快速实现。
哈希相关的数据结构unordered_set、unordered_map、map都适用于快速判断一个元素是否在集合中的问题。
2 有效的字母异位词
LeetCode:有效的字母异位词
做了二叉树和回溯之后,再用数组这种线性结构解决这种问题,真有一种难以严明的轻快感。
class Solution {
public:
bool isAnagram(string s, string t) {
int count[26]={0};
if(s.size()!=t.size())
return false;
for(int i=0;i<t.size();i++)
{
++count[s[i]-'a'];
--count[t[i]-'a'];
}
for(int i=0;i<26;i++)
{
if(count[i]!=0)
return false;
}
return true;
}
};
3 两个数组的交集
LeetCode:两个数组的交集
利用字典nums_set去重+快速比较,利用result_set的去重,天然满足集合不重复条件。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
//不使用vector是为了进行天然的去重
unordered_set<int> result_set;
unordered_set<int> num_set(nums1.begin(),nums1.end());
for(int num : nums2)
{
//该语句用于判断是否在集合内
if(num_set.find(num)!=num_set.end())
{
result_set.insert(num);
}
}
return vector<int>(result_set.begin(),result_set.end());
}
};
4 快乐数
LeetCode:快乐数
利用Hash的Set保存已有的结果,结果不在集合内就加入集合,在意味着无限循环,直接break跳出循环。
class Solution {
public:
bool isHappy(int n) {
int sum=0;
int res;
unordered_set<int> sum_set;
sum_set.insert(n);
while(1)
{
sum=0;
//计算各个位置的平方和
while(n)
{
res=n % 10;
sum+=res*res;
n/=10;
}
n=sum;
//快乐数
if(sum==1)
return true;
//出现已出现过的数字意味着无限循环
if(sum_set.find(sum)!=sum_set.end())
return false;
//不确定就进行下一轮循环
else
sum_set.insert(sum);
}
}
};
5 两数之和
LeetCode:两数之和
唯一的槽点在于输出的是指针而不是值,导致排序+双指针的方法没法直接用,于是直接用set{之前遍历元素}+当前元素进行比较。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target)
{
//一次遍历的办法
//利用哈希保存自己之前已经遍历过的元素,并且进行比较
//因为目前的元素还没有进入map,因此保留了两个相同元素相加的可能,如2+2=4
//另外因为只存在一个有效答案,我们可以认定,在已经遍历过的结果中,不存在满足有效答案的重复元素
unordered_map<int,int> vk;
for(int i=0;i<nums.size();++i)
{
auto iter=vk.find(target-nums[i]);
if(iter!=vk.end())
{
return {iter->second,i};
}
else
{
vk[nums[i]]=i;
}
}
return {};
}
};
6 四数相加II
LeetCode:四数相加II
相比三数/四数相加,没有去重要求的四数相加II反而更加简单。
这一提体现了哈希空间换时间的思想,利用Hash的O(1)取,将原来的O(n4)转化为了2个O(n2)的循环,
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int> umap;
//记录a+b的情况
for(int a:nums1)
{
for(int b:nums2)
{
++umap[a+b];
}
}
int count=0;
//c+d=-a-b的情况下,计数
for(int c:nums3)
{
for(int d:nums4)
{
if(umap.find(-(c+d))!=umap.end())
count+=umap[-(c+d)];
}
}
return count;
}
};
7 赎金信
LeetCode:赎金信
和2没有本质区别。
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int count[26]={0};
for(int i=0;i<ransomNote.size();i++)
{
--count[ransomNote[i]-'a'];
}
for(int i=0;i<magazine.size();i++)
{
++count[magazine[i]-'a'];
}
for(int i=0;i<26;++i)
{
if(count[i]<0)
return false;
}
return true;
}
};
8 三数之和
LeetCode:三数之和
虽然放在了Hash来讲,但事实上双指针反而更好使的一道题目,利用Hash需要多多思考去重情况的一道题,给出了2种Hash和1种双指针答案。
Hash1·循环进行两数之和
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(),nums.end());
unordered_set<int> set;
//三数之和可以看成是一个循环内的二数之和
//两数之和的目标是-nums[i]
//按照这个思路进行即可
for(int i=0;i<nums.size()-2;++i)
{
//剪枝
if(nums[i]>0)
break;
//a去重
if(i>0 && nums[i]==nums[i-1])
continue;
set.clear();
for(int j=i+1;j<nums.size();j++)
{
//允许一次重复,因为这一轮循环需要求b和c,b/c重复是允许的
//但是如果有三个数,就需要去重了
if(j>i+2 && nums[j-1]==nums[j] && nums[j-2]==nums[j])
continue;
int complement=-(nums[i]+nums[j]);
//这一轮从i+1走到目前有某个b与目前c=nums[j]满足条件
if(set.find(complement)!=set.end())
{
result.push_back({nums[i],nums[j],complement});
//用过一次就不能再用了,避免重复
set.erase(complement);
}
//加入set,自动去重
else
{
set.insert(nums[j]);
}
}
}
return result;
}
};
Hash2·利用后向覆盖+向后遍历确定有序性,避免重复
利用哈希保存靠后的序号,遍历从前往后找,于是就意味着三数的排列必然为a<b<c,方便进行剪枝与确定数值。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
//利用双指针
int i,left,right;
vector<vector<int>> result;
sort(nums.begin(),nums.end());
for(i=0;i<nums.size()-2;++i)
{
//剪枝
if(nums[i]>0)break;
//去重
if(i>0 && nums[i]==nums[i-1])continue;
//双指针
left=i+1;
right=nums.size()-1;
while(left<right)
{
//去重
if(right<nums.size()-1 && nums[right]==nums[right+1])
{
--right;
continue;
}
if(left>i+1 && nums[left]==nums[left-1])
{
++left;
continue;
}
int sum=nums[i]+nums[left]+nums[right];
//sum>0,需要减小,i是固定的,left只能增大,所以只有++right了
if(sum>0)--right;
//同理,--left
else if(sum<0)++left;
//满足条件,继续搜索
else if(sum==0)
{
result.push_back({nums[i],nums[left],nums[right]});
--right;
++left;
}
}
}
return result;
}
};
双指针(增大只能++left,减小只能–right)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
//利用双指针
int i,left,right;
vector<vector<int>> result;
sort(nums.begin(),nums.end());
for(i=0;i<nums.size()-2;++i)
{
//剪枝
if(nums[i]>0)break;
//去重
if(i>0 && nums[i]==nums[i-1])continue;
//双指针
left=i+1;
right=nums.size()-1;
while(left<right)
{
//去重
if(right<nums.size()-1 && nums[right]==nums[right+1])
{
--right;
continue;
}
if(left>i+1 && nums[left]==nums[left-1])
{
++left;
continue;
}
int sum=nums[i]+nums[left]+nums[right];
//sum>0,需要减小,i是固定的,left只能增大,所以只有++right了
if(sum>0)--right;
//同理,--left
else if(sum<0)++left;
//满足条件,继续搜索
else if(sum==0)
{
result.push_back({nums[i],nums[left],nums[right]});
--right;
++left;
}
}
}
return result;
}
};
9 四数之和
LeetCode:四数之和
解决了三数之和,就可以用一样的套路(双指针)解决四数之和了,多加一层循环罢了。
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
long Target=target;
sort(nums.begin(),nums.end());
if(nums.back()<Target && nums.back()<=0)return result;
if(nums.front()>Target && nums.front()>=0)return result;
int i,j,left,right;
int n=nums.size();
//双指针
for(i =0 ;i< n-3; ++i)
{
if(i>0 && nums[i]==nums[i-1])continue;
for(j=i+1; j < n-2; ++j)
{
if(j>i+1 && nums[j]==nums[j-1])continue;
left=j+1;
right=n-1;
while(left<right)
{
if(left>j+1 && nums[left]==nums[left-1])
{
++left;
continue;
}
if(right<n-1 && nums[right]==nums[right+1])
{
--right;
continue;
}
long sum=long(nums[i])+long(nums[j])+long(nums[left])+long(nums[right]);
if(sum>Target)--right;
if(sum<Target)++left;
if(sum==Target)
{
result.push_back({nums[i],nums[j],nums[left],nums[right]});
--right;
++left;
}
}
}
}
return result;
}
};
10 总结
回归哈希,虽然暂时还没有去深究C++底层容器的红黑树啊、哈希编码的原理,但好用就是好用,而且题目难度也降低了很多。
另外就是感觉一直没睡够,到了下午做题都没精神了,忘得也快。
——2023.2.25