探秘基础算法中的哈希相关结构

探索基础算法里的哈希相关构造

目录

  • 一,哈希表概览
  • 二,算法原理与代码实现
    • 1.两数之和
    • 349.两数组交集
    • 面试题01.02.判断字符重排
    • 217.存在重复元素
    • 219.存在重复元素II
    • 692.前k高频单词
    • 45.字母异位词分组
  • 三,算法总结

一,哈希表概览

哈希思想是算法中极为关键的思想,体现的是一种映射关系,而哈希表就是依据哈希思想构建的用于存储数据的容器。哈希表的功用在于能快速查找某个元素,其时间复杂度为O(1),不过遍历整个哈希表的时间复杂度是O(n)。

运用哈希通常有两种途径:
(1) STL中的unordered系列容器。
(2) 用数组模拟简易哈希表。这类情况一般适用于处理字符串中的“字符”或者当数据范围很小时使用。

接下来会通过若干题目深入领会哈希表的运用


二,算法原理与代码实现

1.两数之和

在这里插入图片描述
在这里插入图片描述

算法原理:

(1) 要是能预先把数组里的元素和对应的下标关联起来存入哈希表,之后直接在哈希表中查找每个元素对应的target - nums[i],就能迅速找到目标和的下标
(2) 这里有个小窍门,不用把所有元素都放进哈希表后再进行二次遍历(因为要处理元素相同的情况)。而是在往哈希表放元素的同时,直接检查表中是否已经存在当前元素所对应的目标元素(即target - nums[i])。要是存在,那就已经找到对应解,立刻返回。无需把所有元素都放进哈希表,以此提高效率
(3) 由于哈希表中查找元素的时间复杂度是O(1),遍历一遍数组的时间复杂度是O(N),所以能把时间复杂度降到O(N)

代码实现:

class Solution 
{
public:
    vector<int> twoSum(vector<int>& nums, int target) 
    {
        unordered_map<int, int> hash; // 存储元素和对应的下标
        for(int i = 0; i < nums.size(); i++)
        {
            // 查看在该元素之前是否存在一个数等于target - nums[i]
            int x = target - nums[i];
            if(hash.count(x)) return {hash[x], i};
            hash[nums[i]] = i; // 不存在就插入
        }
        return {-1, -1};
    }
};

349.两数组交集

在这里插入图片描述
在这里插入图片描述

算法原理:

解法1:利用哈希表
定义两个哈希表hash1和hash2,把两个数组分别放入哈希表进行去重,然后遍历hash1中的每个元素,在hash2中查找是否存在,若存在就是交集。把交集存入数组即可

代码实现:

class Solution 
{
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) 
    {
        // 去重
        unordered_set<int> s1(nums1.begin(), nums1.end());
        unordered_set<int> s2(nums2.begin(), nums2.end());

        // 遍历s1,在s2中查找是否存在,存在则为交集
        vector<int> ret;
        for(auto x : s1)
            if(s2.find(x) != s2.end())
                ret.push_back(x);

        return ret;
    }
};

解法2:使用set排序+去重。
这种解法不仅能找出交集,还能找到差集。
把两个数组都放进set容器实现排序+去重的效果,接着依次比较两个set里的元素,谁小谁就移动指针,相等时就同时移动指针,此时相等的元素就是交集,较小的元素就是差集,直到其中一个set遍历完,另一个set剩下的元素就是差集

代码实现:

class Solution
{
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
    {
        // 排序+去重
        set<int> s1(nums1.begin(), nums1.end());
        set<int> s2(nums2.begin(), nums2.end());

        // 依次比较,谁小谁移动指针,相等则为交集,然后同时移动指针
        auto it1 = s1.begin(), it2 = s2.begin();
        vector<int> ret;
        while (it1 != s1.end() && it2 != s2.end())
        {
            if (*it1 < *it2) ++it1;
            else if (*it1 > *it2) ++it2;
            else ret.push_back(*it1), ++it1, ++it2;
        }
        return ret;
    }
};

面试题01.02.判断字符重排

在这里插入图片描述
在这里插入图片描述

算法原理:

(1) 当两个字符串长度不一样时,肯定不能构成互相重排,直接返回false
(2) 要是两个字符串能构成互相重排,那么每个字符串中各个字符出现的次数必然相同。所以,可以用哈希表分别统计两个字符串中各个字符出现的次数,然后逐个比较是否相等

代码实现:

class Solution 
{
public:
    bool CheckPermutation(string s1, string s2) 
    {
        // 长度不同,直接返回false
        if(s1.size() != s2.size()) return false;

        int hash1[26] = {0}, hash2[26] = {0};
        // 先统计s1和s2中每个字符出现的次数
        for(auto ch : s1) hash1[ch - 'a']++;
        for(auto ch : s2) hash2[ch - 'a']++;

        // 再比较两个哈希表中每个字符出现的次数是否相同
        for(char ch = 'a'; ch <= 'z'; ch++)
            if(hash1[ch - 'a'] != hash2[ch - 'a'])
                return false;

        return true;
    }
};

优化: 只用一个哈希表

先用一个哈希表统计s1中每个字符出现的个数,然后遍历s2时把s2中出现的字符在哈希表中减1。要是构成重排列,最后哈希表中的个数都为0,否则不构成

代码实现:

class Solution 
{
public:
    bool CheckPermutation(string s1, string s2) 
    {
        // 长度不同,直接返回false
        if(s1.size() != s2.size()) return false;

        int hash[26] = {0};
        // 统计s1中字符出现的次数
        for(auto ch : s1) hash[ch - 'a']++;

        // 遍历s2,出现的字符减1
        for(auto ch : s2) hash[ch - 'a']--;

        // 最后检查hash里是否都为0
        for(int i = 0; i < 26; i++)
            if(hash[i] != 0)
                return false;

        return true;
    }
};

217.存在重复元素

在这里插入图片描述
在这里插入图片描述

算法原理:
这道题和第一题做法基本类似

分析题目,出现至少两次意味着数组中有重复元素,所以无需统计元素出现的数目。只需在遍历数组时,检查当前元素之前是否出现过即可

因此可以用哈希表,只存储数组内的元素。遍历数组时,一边检查哈希表中是否已存在当前元素,一边把元素加入哈希表

代码实现:

class Solution 
{
public:
    bool containsDuplicate(vector<int>& nums) 
    {
        unordered_set<int> hash;
        for(auto x : nums) 
            if(hash.count(x)) return true;
            else hash.insert(x);

        return false;
    }
};

219.存在重复元素II

在这里插入图片描述
在这里插入图片描述

算法原理:

这道题和上一题大体相同,不过除了要求两个元素相等外,还得判断相等元素的下标差的绝对值是否小于等于k。所以本题的哈希表要把元素值和对应下标绑定

细节问题:

要是数组内有大量重复元素,当判断下标差的绝对值不符合要求时,插入相同元素的下标会覆盖原来的下标,这样结果还对吗,怎么处理?
结果依然正确,可以放心覆盖!

在这里插入图片描述
原因:
我们按下标从小到大的顺序遍历数组,当遇到两个相同元素并比较下标时,这两个下标肯定是距离最近的,因为:
(1) 如果当前判断符合条件就直接返回true,不用继续往后查找。
(2)
如果不符合条件,那么前一个下标肯定不可能和后续相同元素的下标匹配(因为下标在逐渐增大),所以可以大胆舍弃前一个存储的下标,换成新的下标,继续匹配

代码实现:

class Solution 
{
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) 
    {
        unordered_map<int, int> hash; // 绑定元素和下标
        for(int i = 0; i < nums.size(); i++)
        {
            if(hash.count(nums[i]))
            {
                if(abs(hash[nums[i]] - i) <= k)
                    return true;
            }
            hash[nums[i]] = i;
        }
        return false;
    }
};

692.前k高频单词

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法原理:

解法:使用map + 稳定排序函数
先用map统计每个单词出现的次数,map中是按单词的字典序排序的,所以还需要用排序函数对次数进行降序排列,最后提取前k个出现最多的字符串

魔鬼细节问题:

对次数排序时有以下几个注意点:
(1) 排序函数一定要用稳定排序! 算法库中的sort是不稳定的,而stable_sort是稳定的。
因为要是用不稳定的排序,出现次数相同的字符串顺序又会被打乱(map中已经按单词字典序排序),不符合题意
(2) 使用排序函数前要把map中的元素先导入vector中,再把vector的迭代器传给stable_sort
因为stable_sort只能传随机迭代器,而map的迭代器是双向迭代器,vector的迭代器是随机迭代器
(3) stable_sort的第三个参数:一个可调用的比较函数和函数对象

代码实现:

class Solution 
{
    struct kvcmp
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2)
        {
            return kv1.second > kv2.second;
        }
    };
public:
    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        // 统计每个字符串出现的次数
        map<string, int> mapCnt;
        for(auto& s : words) mapCnt[s]++;

        vector<pair<string, int>> v(mapCnt.begin(), mapCnt.end());
        // 用稳定排序对字符串出现的次数降序排列
        stable_sort(v.begin(), v.end(), kvcmp());

        // 提取前k个出现次数最多的单词
        vector<string> ret;
        for(int i = 0; i < k; i++)
            ret.push_back(v[i].first);

        return ret;
    }
};

45.字母异位词分组

在这里插入图片描述
在这里插入图片描述

算法原理:

这道题要解决两个问题:
**(1) 判断两个字符串是否是字母异位词。

可以用上面题目的方法用哈希表统计字符出现的个数来判断。这里用更简洁的方法,排序,不过时间复杂度更高。把每个字符串按字典序排序,若是字母异位词,排序后字符串相等,否则不相等。
(2) 如何把字母异位词分组。
可以这样定义哈希表,把key定义为string,把value定义为字符串数组vector< string >
把每个字符串排序后在哈希表中查找,如果存在,就把它放到对应的字符串数组中** 。

在这里插入图片描述

代码实现:

```cpp
class Solution
{
public:
vector> groupAnagrams(vector& strs)
{
unordered_map> hash;

    // 把所有字母异位词分组
    for(auto& s : strs)
    {
        string tmp = s;
        sort(tmp.begin(), tmp.end());
        hash[tmp].push_back(s);
    }

    // 提取结果
    vector<vector<string>> ret;
    for(auto& [x, y] : hash)
        ret.push_back(y);

    return

文章整理自互联网,只做测试使用。发布者:Lomu,转转请注明出处:https://www.it1024doc.com/12765.html

(0)
LomuLomu
上一篇 6小时前
下一篇 4小时前

相关推荐

  • Spring Boot与WebSocket融合全攻略:从入门到高阶应用

    一、WebSocket基础概念与核心原理 1.1 WebSocket协议的本质内涵 WebSocket是一种在单一TCP连接上开展全双工通信的协议,它攻克了HTTP协议在实时通信方面的局限。不同于HTTP那种请求 – 响应的模式,WebSocket允许服务器主动向客户端推送数据,实现了真正意义上的双向交互。 传统HTTP通信的弊病所在: 每一次请求都得重新搭…

    未分类 2025 年 6 月 18 日
    18100
  • MySQL 面试题

    MySQL 中有哪几种锁? 全局锁、行级锁、自增锁、记录锁、外键锁、间隙锁、表级锁、元数据锁、意向锁、临键锁 MySQL 中有哪些不同的表格? 基础表、临时表、系统表、信息表、性能模式表、分区表、外键表、触发器使用的表、存储过程和函数使用的表 简述在 MySQL 数据库中 MyISAM 和 InnoDB 的区别? 事务支持 InnoDB:支持事务处理,具有提…

    未分类 2025 年 1 月 13 日
    27700
  • 2024 DataGrip最新激活码,DataGrip永久免费激活码2025-01-11 更新

    DataGrip 2024最新激活码 以下是最新的DataGrip激活码,更新时间:2025-01-11 🔑 激活码使用说明 1️⃣ 复制下方激活码 2️⃣ 打开 DataGrip 软件 3️⃣ 在菜单栏中选择 Help -> Register 4️⃣ 选择 Activation Code 5️⃣ 粘贴激活码,点击 Activate ⚠️ 必看!必看! 🔥 …

    2025 年 1 月 11 日
    43100
  • DataGrip永久激活破解教程,亲测有效

    DataGrip永久激活破解教程,亲测有效 本教程适用于IDEA、PyCharm、DataGrip、Goland等,支持Jetbrains全家桶! 直接进入正题,先上最新版本破解成功的截图,如下图所示,可以看到已经成功破解到 2099 年辣,舒服! 接下来,下面将结合截图讲解, 来详细讲解如何激活DataGrip至 2099 年。 这个方法也支持旧版本! 不…

    DataGrip破解教程 2025 年 4 月 10 日
    62900
  • 2025年最新DataGrip永久破解教程(亲测有效,激活至2099年)🚀

    🔥 本教程适用于Jetbrains全家桶(IDEA、PyCharm、DataGrip、Golang等),一键破解超简单! 先给大家看看最新版本的破解成果✨,成功激活到2099年,简直不要太爽! 下面我就手把手教大家如何破解DataGrip,同样的方法也适用于旧版本哦~ 无论你是Windows、Mac还是Linux系统,都能完美破解! 💻 第一步:下载Data…

    DataGrip激活码 2025 年 6 月 20 日
    22300

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信