Day5 哈希表:242.有效的字母异位词 349.两个数组的相交 202.快乐数 1.两数之和

97 阅读4分钟

242.有效的字母异位词

题目链接:242.有效的字母异位词

难度指数:😀🙂

暴力,2层for循环,时间复杂度是O(n^2), (不介绍)


数组就是一个简单的哈希表。

eg:字符串1:abbc 字符串2:bbac

定义一个数组 hash[26] 能放下26个字母,hash[ ] 用来统计每个字母出现的频率(次数)。

用这个数组先统计第一个字符串里每个字母出现的频率;当遍历第二个字符串时,每个字母出现的频率,在 hash[ ] 里面做对应的减法,如果最后 hash[ ] 里面所有的元素都是0,说明这两个字符串就是有效字母异位词。

说一嘴:若最终结果是 hash[ ] 里面有元素是-1,-3,……,说明两个字符串中该字母出现的频率不相同,即不是有效的字母异位词。

当我们想使用哈希法来解决问题的时候,我们一般会从以下三种数据结构进行考虑:

  • 数组
  • set (集合)
  • map(映射)

Q:什么时候用数组,什么时候用set,什么时候用map

A:针对本题,哈希值比较小(0~25),而且范围可控(26个字母)的情况下,就采用数组

(如果哈希值很大(hash[ ]下标很多),就用set

将字母a到下标0做了一个映射,不需要记住字母a的ASCII码是多少

第一个字符串s

 int hash[26];
 ​
 for (int i = 0; i < s.size(); i++) {
     hash[s[i] - 'a']++;  //将字符串s中所有的字母出现的频率都统计在hash[]里面
 }

第二个字符串t

 for (int i = 0; i < t.size(); i++) {
     hash[t[i] - 'a']--;
 }

最后判断这2个字符串是有效字母异位词。

最后对哈希数组进行判断

AC代码: (核心代码模式)

 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 i = 0; i < t.size(); i++) {
             hash[t[i] - 'a']--;
         }
         for (int i = 0; i < 26; i++) {
             if (hash[i] != 0) {
                 return false;
             }
         }
         return true;
     }
 };

遇到一道题,感觉想用哈希法的时候,先看看能不能用数组,(能用数组尽量用,会比较快)

一般情况下,如果哈希值比较大,那么用数组就不太合适。

如果用set的话,每次在set里面放key,还要做一次哈希运算,做一个映射,很浪费时间。

349.两个数组的交集

题目链接:349.两个数组的交集

⚠️返回的元素是有去重的。

当数值很大,例如是上亿的话,我们想做哈希映射,那么用数组就不合适了。(数组下标放不下那么大的数,同时浪费了很多存储空间。)

🦄哈希表最擅长解决:给你一个元素,判断在这个集合里是否出现过。

只要遇到这种类似的场景,你脑中的第一个想法就是这道题可能会用到哈希表。

具体是用数组、set还是map需要具体分析。

如果数值很大,用数组就不合适了,推荐用set;

如果数值不是很大,但是数值很分散(eg:下标1, 5, 100000),就3个数,用数组下标来做映射的话,要开100000这么大的数组,很浪费空间。

思路:

num1 里面的所有数值放到哈希表。

遍历 num2 ,查找num2里面的元素是否在哈希表里面出现过;若有出现过,放到 result 集合,最后对 result 集合去重。

05.01.png

set解决:

我们使用 unordered_set ,因为它做映射的时候效率是最高的;做取值操作的时候效率也是最高的。

(插一嘴:因为 setmultiset 底层是树,我们在取值的时候,它还有一个查找的过程。)

在初始化的时候,直接就把 num1 放进哈希表里面,

接下来就要拿 num2 进行一个查询操作。

遍历 num2 的过程:

 for (int i = 0; i < num2.size(); i++) {  //遍历num2的过程       for (int num : num2)
     //判断在这个哈希表里面有没有出现过
     if (num_set.find(num) != num_set.end()) {  //找到了元素
         
         //把它放在result集合里
         result_set.insert(num);
     }
 }

就算你insert了100个2,最终也只能存下一个2。

因为用了 unordered_set,自动帮我们去重。

(插一嘴:使用 multiset 是可以重复的。)

AC代码: (核心代码模式)

 class Solution {
 public:
     vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
         unordered_set<int> result_set;  //存放最终结果,用set是为了去重
         unordered_set<int> nums_set(nums1.begin(), nums1.end());
 ​
         for (int num : nums2) {
             if (nums_set.find(num) != nums_set.end()) {
                 result_set.insert(num);
             }
         }
 ​
         return vector<int>(result_set.begin(), result_set.end());
     }
 };

力扣修改了数值,测试数据也改了:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

因此这道题是适合用数组的。

数组解决:

AC代码: (核心代码模式)

 ​

202.快乐数

题目链接:202.快乐数

AC代码: (核心代码模式)

 class Solution {
 public:
     // 取数值各个位上的单数之和
     int getSum(int n) {
         int sum = 0;
         while (n) {
             sum += (n % 10) * (n % 10);
             n /= 10;
         }
         return sum;
     }
     bool isHappy(int n) {
         unordered_set<int> set;
         while(1) {
             int sum = getSum(n);
             if (sum == 1) {
                 return true;
             }
             // 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
             if (set.find(sum) != set.end()) {
                 return false;
             } else {
                 set.insert(sum);
             }
             n = sum;
         }
     }
 };

1.两数之和

题目链接:1.两数之和

难度指数:😀😕

这道题是哈希表中比较经典的题目,用 map 来解决。

这道题如果不知道哈希表 真的不好想别的方法。

map 在本题的功能:存放我们遍历过的元素。

网友:value作为数组下标,value的下标作为数组下标对应的值,这里不知道输入数组范围,不好定义数组长度,因此不适合用数组来hash

本题要用 unordered_map,它用来存和读的效率是最高的。

样例:2,7,3,6

伪代码:

 unordered_map<int, int>map;
 ​
     //遍历这个数组
     for (int i = 0; i < nums.size(); i++) {
         //要查询的值
         s = target - nums[i];
 ​
         if (iter != map.end()) {  //如果我们想要查找的元素在map里出现过
             //此时就找到了2个元素相加等于target,返回它们对应的下标
             return {iter->value, i};  //其中一个下标,另一个下标i (当前遍历的元素)
         }
 ​
         //把遍历过的元素所对应的key和value存到map中
     }
 ​
 return ();  //遍历结束还没找到,return一个空集合

遍历到2,我们就要去map里面查询有没有遍历过7这个值,

AC代码: (核心代码模式)

 ​class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        std::unordered_map <int, int> map;
        for (int i = 0; i < nums.size(); i++) {
            //遍历当前元素,并在map中寻找是否有匹配的key
            auto iter = map.find(target - nums[i]);

            if (iter != map.end()) {
                return {iter->second, i};
            }
            //如果没有找到匹配对,就把遍历过的元素和下标加入到map中
            map.insert(pair<int, int>(nums[i], i));
        }

        return {};
    }
};

总结:

本题代码虽然简短,不过有4个注意点:

1️⃣为什么会想到使用哈希法?

2️⃣为什么要用 map? (同时,对于C++er,为什么要使用 unordered_map?)

3️⃣ map 究竟是用来做什么的?

4️⃣ map 里面的key是用来存什么?