什么时候使用哈希法?当需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。
第1题 求两数之和
解题思路:不用双重循环暴力解法,用哈希表试一试。为什么选用哈希表?解题关键在于一个元素是否在集合中。为什么选用map?因为既要存储元素,又要存元素下标。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
//哈希表实现
//可以无序,所以用unordered_map
unordered_map<int, int> table;
for (int i = 0; i < nums.size(); i++)
{
auto iter = table.find(target - nums[i]);//例如当前num[i]=2,就要在列表里找是否有7在
if (iter != table.end())//若有,则输出,没有的话指针会走到表尾
{
return{ iter->second,i };//返回两个下标索引,因为存在唯一答案,直接输出就行
}
//没找到的话就把当前元素以及下标存入表中
table.insert(pair<int,int>(nums[i], i));//pair是将2个数据组合成一组数据
}
return {};
}
};
第4题 四数相加Ⅱ
解题思路:跟第一题解法相似,可以将四个数组两两结合。这里要存储的是求和为0出现的次数,所以使用unordered_map。
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
//key是数据求和的值,value是这个和出现的次数
unordered_map<int, int> table;
int count = 0;//用于计数
int n = nums1.size();
//将四个数组分为两两一组
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
table[(nums1[i] + nums2[j])]++;
for (int k = 0; k < n; k++)
for (int l = 0; l < n; l++)
if (table.find(0 - (nums3[k] + nums4[l])) != table.end())
count += table[0 - (nums3[k] + nums4[l])];
return count;
}
};
第15题 三数相加
解题思路:这道题哈希表做就会变得复杂化。此处算法参考的是leetCode的精选题解。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
//特殊情况直接判断结束
if (nums.size() < 3)
return {};
//将数组排序
sort(nums.begin(), nums.end());
int i = 0;
vector<vector<int>> res;
while (i < nums.size())
{
if (nums[i] > 0)
break;//当前第一个数字大于零,直接提前终止循环
int left = i + 1, right = nums.size() - 1;
while (left < right)
{
int x = nums[i];
int y = nums[left];
int z = nums[right];
if (x + y > 0 - z)//当前三个数字和大于0,左移right找一个小一点的数
right--;
else if (x + y < 0 - z)//当前三个数字和小于0,右移left找一个大一点的数
left++;
else
{
//三数相加和为0,存储当前数字
res.push_back({x,y,z});
//移动指针,注意要去掉重复元素
while (left < right && nums[left] == nums[left + 1])
left++;
while (left < right && nums[right] == nums[right - 1])
right--;
//去掉重复元素之后在移动左右指针
left++;
right--;
}
}
//同样的去重操作
while (i + 1 < nums.size() && nums[i] == nums[i + 1])
i++;
i++;
}
return res;
}
};
第242题 有效的字母异位词
解题思路:数组是形式最简单的哈希表,索引是key,存储内容是value。本题采用一维数组进行记录,因为题目中给的都是小写字母,所以定义一个长度为26的数组,通过遍历s记录每个字母出现的次数,然后再遍历t,t中出现的字母做减法记录,一正一负互相抵消,若s和t是异位词,则数组最终恢复全零。
class Solution {
public:
bool isAnagram(string s, string t) {
//简单版哈希表——数组
//定义一个数组,数组长度为26,用于记录每个字母出现的次数
int letter[26] = {0};
//记录s中字母出现的次数
for(int i = 0;i<s.size();i++)
{
letter[s[i]-'a']++;//索引0即代表字母a
}
//t中出现的字符做减法计数
for(int i = 0;i<t.size();i++)
{
letter[t[i]-'a']--;
}
//若是字母异位词,则数组最终都是0
for(int i = 0;i<26;i++)
{
if(letter[i] != 0)
return false;
}
return true;
}
};
第49题 字母异位词分组
解题思路:参考的是题解评论中的一种解决方法。主要的算法流程就是定义一个哈希表,这个哈希表的用处就是将异位词挂在同一个键值之下,这里面判断异位词的方法是将一个单词按照a~z的顺序重新生成新的键值,例如单词“eat”的键值就是“aet”,同样,单词“tea”的键值也是“aet”,它俩都会挂在key="aet"下面。最后,再将哈希表的value输出。
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
//定义一个哈希表map,负责将给定的字符串进行分类
map<string, vector<string>> table;
//定义一个容器负责存储输出结果
vector<vector<string>> res;
for (int i = 0; i < strs.size(); i++)
{
string key = strs[i];//key存储的是单个字符串
sort(key.begin(), key.end());//对单个字符串进行排序,例如“eat排序完为aet”
table[key].push_back(strs[i]);//将排序完相同的字母排序存入同一key值下
}
/*auto是用来自动推导表达式或变量的实际类型的
就如同下面的例子,table是一个很复杂的变量,
在程序庞大或者复杂的时候,
不知道table.begin()的返回结果是什么进而导致不确定ite前面的要输入类型应该是什么,
通过auto关键字就能直接推到ite的数据类型,在复杂程序中很好用*/
for (auto ite = table.begin(); ite != table.end(); ite++)
res.push_back(ite->second);//ite->first是键值,ite->second是键值下面挂的一串数组,所以直接就能写入到res中
return res;
}
};
第438题 找到字符串中所有字母异位词
解题思路:刚开始想用49题的思想去求解,但是由于测试用例过于庞大,程序执行超过时间限制,因此只能放弃。这里采用一下官方的解题思路。
vector<int> findAnagrams(string s, string p) {
int p_len = p.size();
int s_len = s.size();
if (p.size() > s.size())
return {};
vector<int> res;//存储结果的容器
vector<int> count_s(26);//用于计数的容器
vector<int> count_p(26);
for (int i = 0; i < p_len; i++)
{
//统计滑动窗口中每个字母出现的情况
count_s[s[i] - 'a']++;
count_p[p[i] - 'a']++;
//若conut内全是0,说明当前滑动窗口与p是异位词
}
if (count_s == count_p)
res.push_back(0);
//从这里开始移动窗口
for (int i = 0; i < s_len - p_len; i++)
{
//将窗口前面的字符删去计数
count_s[s[i] - 'a']--;
//将后一个字符加入窗口计数
count_s[s[i + p_len] - 'a']++;
//判断当前窗口是否是异位词
if (count_s == count_p)
res.push_back(i+1);
}
return res;
}
第383题 赎金信
解题思路:同样采用上述思想,将一维数组作为哈希表来使用。遍历ransomNote,记录每个字母出现得次数,然后再遍历magazine,若也出现了相同的字母,则数组中相应的字母记录减一(对应字母可以多余ransomNote,但是不能少),若ransomNote中的字母在magazine全部出现,数组最终所有元素为0。
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
//特判
int r_len = ransomNote.size();
int m_len = magazine.size();
if (r_len <= 0 || m_len <= 0)
return false;
if (r_len > m_len)
return false;
int letter[26] = { 0 };//定义一个数组存储ransomNote中字母出现得个数
for (int i = 0; i < r_len; i++)
letter[ransomNote[i] - 'a']++;
for (int j = 0; j < m_len; j++)
{
if (letter[magazine[j] - 'a'] > 0)
letter[magazine[j] - 'a']--;
}
//若letter全为零,说明magazine里包含组成ransomNote全部字母
int sum = accumulate(letter, letter + 26, 0);
if (sum)
return false;
else
return true;
}
};
第349题 两个数组的交集
解题思路:这是一道简单题,主要的算法思想就是遍历第一个数组,记录一下num1中有哪些数字出现过,再遍历第二个数组,看看num2中哪些数字在1中出现过,然后记录到结果res中,其中最关键的部分在于去除重复数字,因此考虑使用"unordered_set"容器。
简单介绍以下"unordered_set"容器:
- unordered_set 容器提供了和 unordered_map 相似的能力,但 unordered_set 可以用保存的元素作为它们自己的键;
- 不能存放重复元素;
- 不会对内部存储的数据进行排序。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
//遇见需要去重操作的,考虑set,unordered_set
unordered_set<int> res;
int hash[1005] = {0};//一维数组也是哈希表
for(int i = 0;i<nums1.size();i++)
hash[nums1[i]] = 1;//将出现过的数字标注1
for(int i = 0;i<nums2.size();i++)
{
//如果num2中的元素在num1中出现过,则将其存入res中
if(hash[nums2[i]] == 1)
res.insert(nums2[i]);
}
return vector<int>(res.begin(),res.end());//强制类型转换
}
};
第350题 两个数组的交集Ⅱ
解题思路:这里不需要对数据进行去重,所以普通容器即可,整体思想与上述题目差别不大。
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
//没有去重操作,可以直接用容器
vector<int> res;
int hash[1005] = { 0 };//一维数组也是哈希表
for (int i = 0; i < nums1.size(); i++)
hash[nums1[i]]++;//将出现过的数字计数
for (int i = 0; i < nums2.size(); i++)
{
//如果num2中的元素在num1中出现过,则将其存入res中
if (hash[nums2[i]])//因为要输出重复元素
{
res.push_back(nums2[i]);
hash[nums2[i]]--;//两个数组有重复元素,哈希表计数减一
}
}
return res;
}
};
第202题 快乐数
解题思路:判断一个数到底是不是快乐数,一是看最后sum是否为1 ,若sum最后达不到1,如何跳出循环呢?方法就是判断当前的sum是否是第二次出现,若是,说明不是快乐数,跳出循环即可。
class Solution {
public:
//先写一下对各位数求和函数
int Getsum(int n)
{
int s=0;
while(n)
{
s += (n%10)*(n%10);
n = n/10;
}
return s;
}
bool isHappy(int n) {
unordered_set<int> table;
while (1)
{
int sum = Getsum(n);//对当前数字求各位和
if (sum == 1)
return true;
else if (table.find(sum) != table.end())//当前的sum是第二次出现
return false;
else//既不是1也不是二次出现的数,存入表中
table.insert(sum);
n = sum;
}
}
};