哈希表(Hash table)

哈希表是根据关键码的值而直接进行访问的数据结构。代码随想录day7_字符串

哈希函数

代码随想录day7_#include_02

小李和小王都映射到了索引下标 1 的位置,这一现象叫做哈希碰撞代码随想录day7_字符串_03代码随想录day7_数组_04代码随想录day7_数组_05代码随想录day7_数组_06

std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。

当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。

那么再来看一下map ,在map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制的,因为key的存储方式使用红黑树实现的。

其他语言例如:java里的HashMap ,TreeMap 都是一样的原理。可以灵活贯通。

虽然std::set、std::multiset 的底层实现是红黑树,不是哈希表,std::set、std::multiset 使用红黑树来索引和存储,不过给我们的使用方式,还是哈希法的使用方式,即key和value。所以使用这些数据结构来解决映射问题的方法,我们依然称之为哈希法。 map也是一样的道理。

这里在说一下,一些C++的经典书籍上 例如STL源码剖析,说到了hash_set hash_map,这个与unordered_set,unordered_map又有什么关系呢?

实际上功能都是一样一样的, 但是unordered_set在C++11的时候被引入标准库了,而hash_set并没有,所以建议还是使用unordered_set比较好,这就好比一个是官方认证的,hash_set,hash_map 是C++11标准之前民间高手自发造的轮子。代码随想录day7_字符串_07

242.有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

示例 1: 输入: s = "anagram", t = "nagaram" 输出: true

示例 2: 输入: s = "rat", t = "car" 输出: false

说明: 你可以假设字符串只包含小写字母

代码随想录day7_字符串_08

定义一个数组叫做record用来上记录字符串s里字符出现的次数。

因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。

再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。 这样就将字符串s中字符出现的次数,统计出来了。

那看一下如何检查字符串t中是否出现了这些字符,同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。

那么最后检查一下,record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。

最后如果record数组所有元素都为零0,说明字符串s和t是字母异位词,return true。

时间复杂度为O(n),空间上因为定义是的一个常量大小的辅助数组,所以空间复杂度为O(1)。

#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Solution{
public:
    bool isAnagram(string s, string t){
        int record[26] = {0};
        for(int i = 0;i < s.size();i++){
            record[s[i] - 'a']++;
        }
        for(int i = 0;i < t.size() ;i++){
            record[t[i] - 'a']--;
        }
        for(int i = 0;i < 26;i++){
            if(record[i] != 0){
                return false;
            }
        }
        return true;
    }
};
int main(){
    Solution s;
    string a = "anagram";
    string b = "nagaram";
    if (s.isAnagram(a,b))
        cout << "true" << endl;
    else
        cout << "false" << endl;
    return 0;
}

349 : 两个数组的交集

题意:给定两个数组,编写一个函数来计算它们的交集。

代码随想录day7_数组_09

这道题目,主要要学会使用一种哈希数据结构:unordered_set,这个数据结构可以解决很多类似的问题。

注意题目特意说明:输出结果中的每个元素一定是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序

这道题用暴力的解法时间复杂度是O(n^2),那来看看使用哈希法进一步优化。

那么用数组来做哈希表也是不错的选择,

但是要注意,使用数组来做哈希的题目,是因为题目都限制了数值的大小。

而这道题目没有限制数值的大小,就无法使用数组来做哈希表了。

而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。

此时就要使用另一种结构体了,set ,关于set,C++ 给提供了如下三种可用的数据结构:

  • std::set
  • std::multiset
  • std::unordered_set

std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。代码随想录day7_#include_10

#include<iostream>
#include<string>
#include<vector>
#include <unordered_set>
using namespace std;

class Solution{
public:
    vector<int> findSubsection(vector<int>& nums1, vector<int>& nums2){
        unordered_set<int> result_set; //创建一个空的无序集合 result_set,用于存储找到的共同元素。
        unordered_set<int> nums_set(nums1.begin(), nums1.end());
        //将 nums1 中的元素放入另一个无序集合 nums_set 中,以便快速查找
        //遍历 nums2 中的每个元素,检查该元素是否在 nums_set 中,如果存在,则将其插入到 result_set 中。
        for (int num : nums2){
            if (nums_set.find(num) != nums_set.end()){ //遍历nums_set,找到num
                result_set.insert(num);
            }
        }
        //将 result_set 转换为 vector,并返回包含共同元素的 vector。
        return vector<int>(result_set.begin(), result_set.end());
    }
};
int main(){
    Solution s;
    vector<int> nums1 = {1, 2, 3};
    vector<int> nums2 = {1, 2, 3, 4, 5};
    vector<int> result = s.findSubsection(nums1, nums2);
    for (auto i : result){
        cout << i << " ";
    }
    return 0;
}
  • 时间复杂度: O(n + m) m 是最后要把 set转成vector
  • 空间复杂度: O(n)