题目链接:有效字母异位词
视频讲解:hash表数组使用技巧
此题用hash表的数组的数据结构,数量不大用数组节省时间。
用一个大小为26的 h[] 数组记录其中一个字符串每个字母出现的次数,'字母' - 'a' 得到的是他们两个的ASCII码的差,表示该字母是0-26中的第几位,用它当做 h[] 数组的下标,然后以该字符串的大小遍历 h[],遍历到字母得到的h[]下标所对应的值+1,就记录了该字母出现的次数。 接着用h[] 在对另一个字符串遍历,把该字符串中的字母得到的下标所对应的值-1,通过一加一减后若h[]的值全部为零则表示两个字符串所含的字母及数量相同。
学习总结
1、数组的下标和所保存的元素是意义对应的关系,有时可以反过来考虑一下,让下标表示我们所想要的值,用其对应的元素来计数。
2、26个字母对应的ASCII码是连续的,用任意一个字母减去 'a' 可以得到该字母在0-26的位置,返回的是ASCII值。
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
bool isAnagram(string s, string t) {
int h[26] = {0};
for (int i = 0; i < s.size(); ++i)
{
h[s[i] - 'a']++;
}
for (int i = 0; i < t.size(); ++i)
{
h[t[i] - 'a']--;
}
for (int i = 0; i < 26; ++i)
{
if (h[i] != 0)
return false;
}
return true;
}
};
题目链接:两个数组的交集
视频讲解:学透哈希表,set使用有技巧!
遇到给你一个元素,需要判断在一个集合中是否出现过,就要想到用hash表来解决。这道题题目提示数组长度小于100,我们再创建一个数组解决。但如果长度没有限制则需采用set。在长度较短时用数组可以节省时间,set存储元素是会进行hash运算的,耗时。
此题输出的元素是不重复的,选择unordered_set存储输出的元素是再好不过了。
set
// 时间复杂度: O(n + m) m 是最后要把 set转成vector
// 空间复杂度: O(n)
class Solution {
public:
vector intersection(vector& nums1, vector& nums2) {
unordered_set tmp(nums1.begin(), nums1.end()); // 把nums1的元素复制到tmp,不重复
unordered_set s; // 存放相同的元素,用unordered_set避免去重
for (auto i = 0; i < nums2.size(); ++i)
{
if (tmp.find(nums2[i]) != tmp.end()) // 表示在tmp中找到了与nums2相等的值
{
s.insert(nums2[i]);
}
}
return vector(s.begin(), s.end());
}
};
数组
// 时间复杂度: O(m + n)
// 空间复杂度: O(n)
class Solution {
public:
vector intersection(vector& nums1, vector& nums2) {
int h[1005] = {0};
unordered_set s; // 存放相同的元素,用unordered_set避免去重
for (auto it : nums1) // 遍历nums1中的元素
{
h[it] = 1; // nums1中的元素表示数组下标,对应下标赋1
}
for (auto it : nums2)
{
if (h[it] == 1) // 在h中找到值为1的下标即为相同的元素
{
s.insert(it);
}
}
return vector(s.begin(), s.end());
}
};
题目链接:快乐数
用unordered_set可以判断是否有求和后相同的值,有相同的就会进入无限循环。
方法
1、写一个取数值各个位上的单数之和的函数
2、进入while循环,先判断求和后的值是否为1,为1直接返回true,否则判断之前保存到set里的sum值是否出现过,若出现过则会进入无限循环,返回false;没有出现过把sum插入set中进行下一次循环。
// 时间复杂度: O(logn)
// 空间复杂度: O(logn)
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 s;
while (1)
{
int sum =getSum(n);
if (sum == 1)
{
return true;
}
// 如果sum出现过,就说明进入了无线循环
if (s.find(sum) != s.end())
{
return false;
}
else
{
s.insert(sum);
}
n = sum;
}
}
};
题目链接:两数之和
视频讲解:梦开始的地方
此题我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适。
再来看一下使用数组和set来做哈希法的局限。
此时就要选择另一种数据结构:map ,map是一种key value的存储结构,可以用key保存数值,用value再保存数值所在的下标。
这题思路是我们先找到一个值让target减去它所得的值就是我们所需要的第二个值,所以在map中要用key表示数组中的数,因为好find,用value表示值对应的下标,容易输出。
所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。
在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。
要点
1、为什么要用hash表;
2、选择什么样的hash结构;
3、map用来存什么的;
4、map的key和value分别用来存什么的;
// 时间复杂度: O(n)
// 空间复杂度: O(n)
class Solution {
public:
vector twoSum(vector& nums, int target) {
unordered_map m;
for (int i = 0; i < nums.size(); ++i)
{
auto n = m.find(target - nums[i]);
if (n != m.end())
{
return {i, n->second};
}
m.insert(pair(nums[i], i));
}
return {};
}
};