算法训练营|Day06哈希表

2023/12/4 首次接触哈希表

哈希表基础知识

哈希表(Hash table,也称散列表):哈希表是根据关键码的值而直接进行访问的数据结构

数组就是一张哈希表,关键码:数组下标,通过下标访问数组元素

哈希表解决什么问题:

一般哈希表用来快速判断一个元素是否出现在集合里

举例理解:比如要查询一个名字是否在这所学校里,要枚举的话,时间复杂度为O(n),hash table只需O(1)。需要把学生信息存在哈希表中,查询时通过索引直接可以查询。将学生姓名映射到哈希表上就会涉及到hash function哈希函数。

哈希函数:通过hashCode把名字转化为数值

哈希函数会涉及哈希碰撞(两种解决办法:拉链法;线性探测法)

常见哈希结构

  1. 数组
  2. 集合(set)
  3. 映射(map)key value的数据结构
集合底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
std::set红黑树有序O(log n)O(log n)
std::multiset红黑树有序O(logn)O(logn)
std::unordered_set哈希表无序O(1)O(1)

红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,只能删除和增加

映射底层实现是否有序数值是否可以重复实现能否更改数值查询效率增删效率
std::map红黑树key有序key不可重复key不可修改O(logn)O(logn)
std::multimap红黑树key有序key可重复key不可修改O(logn)O(logn)
std::unordered_map哈希表key无序key不可重复key不可修改O(1)O(1)

当我们使用集合set来解决哈希问题时,优先使用unoedered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,就用set; 有序且要求数据重复则使用multiset

总结:

当我们遇到要快速判断一个元素是否出现在集合里的时候,就要考虑哈希法。

if(set.find(element) != set.end())

但哈希法也是牺牲了空间换取时间,因为我们要使用额外的数组,set或者map来存放数据,才能实现快速的查找

242. 有效的字母异位词 - 力扣(LeetCode)

该代码可以借鉴的地方是当以后需要统计字符出现的次数时,可以用到

record[]++

  • 遇到的另一个问题数组时c++原生数据结构,不可以record.size(),求数组长度需要sizeof(record)/sizeof(record[0])
  • 之前逻辑不通的地方,是函数中一旦执行return就会结束函数的运行,后面的代码不会执行
class Solution{
public:
    bool isAnagram(string s, string t)
    {
        int record[26]={0}; //  初始化数组record
        // 遍历字符串s
        for (int i = 0; i < s.size(); i++)
        {
            // 此段非常巧妙,将字母之间的ASCII递增值,差值转换为哈希函数
            record[s[i]-'a']++; // 对数组索引为(s[i]-'a')的元素值,进行递增操作
        }
        for (int j = 0; j < t.size(); j++)
        {
            record[t[j]-'a']--;
        }
        // 再次遍历record数组
        for(int i = 0; i < record.size() ; i++)
        {
            if (record[i] != 0)
            {
                return false;
            }
        }
        // 如果record数组中所有元素全部为0,则说明s和t是字母异位词
        return true;
    }
}

349. 两个数组的交集 - 力扣(LeetCode)

set : set, multiset; unorderset

学会使用哈希数据结构:unordered_set, 无序不重复

数组来做哈希表的限制:数值比较小,不可以跨度过大

学习点:

(1)如何构建无序集合

unordered_set<int> nums_set(nums1.begin(),nums1.end());

(2)循环无需索引,采用范围法遍历

(3)哈希表的核心 if(nums_set.find(num)!= nums_set.end()) 

   (4) 如何插入一个元素set.insert(nums)

class Solution
{
public:
    // 成员函数 intersection 用于计算数组交集
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
    {
        // 存放结果的无序集合,使用set是为了去重
        unordered_set<int> result_set;
        // 将nums1转换为无序集合 nums_set,方便快速查找
        // 这行代码是将nums1中的元素去重,构建一个无序集合nums_set
        unordered_set<int> nums_set(nums1.begin(), nums1.end());
        // 遍历nums2中的每一个元素
        for (int num:nums2)  
        {
            // 如果当前元素在nums_set中出现过,则加入结果集
            if (nums_set.find(num) != nums_set.end()) //nums_set.find(num)用于检查当前迭代的元素num是否在无序集合nums_set中,若存在则返回该元素,若不存在则指向集合末尾,代码中!=nums_set.end()则表明,返回值不指向末尾
            {
                result_set.insert(num);// 在result_set中插入num,即交集元素
            }
        }
        // 将结果集转换为向量并返回
        return vector<int>(result_set.begin(), result_set.end());
        // 这行代码使用 result_set.begin() 和 result_set.end() 定义了一个迭代器范围,然后通过 vector<int> 构造函数将这个范围内的元素复制到一个新的整数向量中。最终,这个新的向量被作为函数的返回值。这样做的目的是将存储在 result_set 中的交集元素以向量的形式返回,方便函数的调用者使用和处理。
    }
};

数组解法

class Solution
{
public:
    // 成员函数 intersection 用于计算两个整数向量的交集
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
    {
        // 存放结果的无序集合,使用set是为了给结果集去重
        underordered_set<int> result_set;
        //数组hash 用于记录nums1中每个元素的出现情况,默认数值为0
        int hash[1005] = {0};
        //遍历nums1中的每一个元素,将其在hash中标记为1
        for (int num:nums1)
        {
            hash[num] = 1;
        }
        // 遍历nums2中的每个元素
        for (int num:nums2)
        {
            if (hash[num] == 1)
            {
                result_set.insert(num);
            }
        }
        // 将结果集合转换为向量并返回
        return vector<int>(restult_set.begin(),result_set.end());
    }
};

202. 快乐数 - 力扣(LeetCode)

此题核心点:求和&判断

求和:记忆(1)如何取个位数n%10 ; (2) 如何去掉最低位数值 n/=10;

判断:记忆(1)如何写无限循环while(1);  (2)如何判断数在set中 if(set.find(sum) != set.end()); 

(3) set中插入元素 set.insert();

class Solution
{
public:
 // 取数值各个位上的单数之和
    int getSum(int n)
    {
        int sum = 0;
        // 循环迭代,取每个位上的数字的平方和
        while(n)
        {
            sum += (n % 10) *(n % 10); // n%10是为了取得n的最低位数字
            n /= 10; // 将n更新为去掉最低位的数值
        }
        // 返回计算得到的平方和
        return sum;
    }
    bool isHappy(int n)
    {
        unoredered_set<int> set;
        // 无限循环,直到满足退出条件
        while(1)
        {
            //获取当前数的各个位上的单位之和
            int sum = getSum(n);
            if (sum == 1)
            {
                return true;
            }
            //
            if (set.find(sum) != set.end())
            {
                return false; // 如果在set中发现重复sum,则说明陷入死循环
            }
            else
            {
                set.insert(sum); // 更新set
            }
            // 更新当前数为平方和
            n = sum;
        }
    }
};

1. 两数之和 - 力扣(LeetCode)

 什么时候使用哈希法:当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就第一时间想到哈希法。

本题,我们不仅要知道元素有没有遍历,还要知道这个元素对应的下标,需要key value结构来寻访,key存元素,value存下标

class Solution
{
public:
    vector<int> twoSum(vector<int>& nums, int target)
    {
        std::unordered_map <int,int> map; // 创建一个无序映射map,用于存储键值对,
        // 键,值都是 int 类型
        // 遍历数组
        for(int i = 0; i < nums.size(); i++)
        {
           auto iter = map.find(target - nums[i]); // 在无序map中查找target-nums[i]的元素
            // auto 关键字用于自动推断迭代器的类型
            // iter 指向该元素的位置,否则指向映射的末尾map.end()
            if (iter != map.end())
            {
                return {iter->second,i}; // 创建一个包含两个元素的整数数组
            }
            //如果没找到匹配对,就把访问过的元素和下标加入到map中
            map.insert(pair<int,int>(nums[i],i)); // pair 表示一个键值对的对象
        }
    }
}

以上题目的关键点都在

set.find(num) != set.end()  某个元素是否在集合中,即哈希表的关键核心

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值