【ONE·基础算法 || 哈希表】

在这里插入图片描述

总言

  主要内容:编程题举例,熟悉理解如何运用哈希表。
  
  


  
  

1、哈希表

  什么时候需要用到哈希表? (这里仅作举例)
  1、统计频率: 哈希表可以快速统计一个数组中各个元素出现的频率。例如,给定一个整型数组,你可以使用哈希表来记录每个元素出现的次数。
  2、快速查找:哈希表的主要优势在于其查找操作的平均时间复杂度为O(1),这意味着无论哈希表中有多少元素,查找一个特定元素所需的时间都是常数级别的。因此,哈希表非常适合用于快速检验某个元素是否出现过,或者快速获取某个键对应的值。
  3、唯一性判断: 哈希表也可以用于判断一个集合中是否包含某个元素,或者用于实现去重操作。通过将元素作为键插入哈希表,如果插入成功则表示该元素之前不存在于集合中;如果插入失败(即哈希表中已存在该键),则表示该元素已经存在于集合中。
  
  
  如何使用哈希表?
  1、可以使用具有哈希结构的相关STL容器
  2、可以用数组模拟简易的哈希表(如字符串中的字符、数据范围很小无负数时等等场景中)
  
  
  
  
  

2、两数之和(easy)

  题源:链接

在这里插入图片描述
  
  

2.1、题解

  1)、思路分析
  1、暴力解法: 固定一个数,遍历找其前数,查看历史元素中,有没有能与之求和等于目标值的元素。(当然,这里也可以遍历查找当前元素之后的元素,是否有满足和为targe的。在暴力解法中这两种写法无区别,但转换到下述哈希表中,能看到不同之处。)
在这里插入图片描述

  2、借助哈希表:「数组内的元素」和「下标」 绑定在⼀起存⼊「哈希表」中,然后直接在哈希表中查找每⼀个元素的 target - nums[i] ,就能快速的找到「⽬标和的下标」。
在这里插入图片描述

  
  
  2)、题解
  借助哈希表的写法:
  1、注意,这里题目说明了每种输入只会对应一个答案,说明不存在无解的情况。
  2、哈希表中,查找元素的时间复杂度是 O ( 1 ) O(1) O(1) ,遍历⼀遍数组的时间复杂度为 O ( N ) O(N) O(N),相对于暴力解法的 O ( N 2 ) O(N^2) O(N2)可以将时间复杂度降到 O ( N ) O(N) O(N)典型的以空间交换时间

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int n = nums.size();
        unordered_map<int,int> hash;
        for(int i = 0; i < n; ++i)
        {
            int aim = target - nums[i];
            auto ret = hash.find(aim);//或使用hash.count()统计key值数进行判断
            if(ret != hash.end())
                return{i, ret->second};
            hash[nums[i]] = i;
        }
        return {-1,-1};
    }
};

  
  暴力解法:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int n = nums.size();
        for(int i = 0; i < n; ++i)
        {
            for(int j = i+1; j < n; ++j)
            {
                if(nums[i]+nums[j] == target)
                    return {i,j};
            }
        }
        return {-1,-1};
    }
};

  
  
  
  
  
  
  

3、判断是否互为字符重排(easy)

  题源:链接

在这里插入图片描述

  
  

3.1、题解

  1)、思路分析
  暴力解法仍旧是固定值后遍历比较,这里不再赘述。

  分析哈希表的解法:
  1、首先,要知道无论哪种解法,若两个字符串的长度不相等,二者不可能互为重排,因此可直接返回 false;
  2、若两个字符串能够构成互相重排,那么每个字符串中「各个字符」出现的「次数」⼀定是相同的。 因此,我们可以选择「哈希表」来统计字符串中字符出现的次数(由于这里字符串只是26个英文字母,因此直接使用数组作为哈希表即可,无需使用容器)。

  这里有两方法:①对字符串s1、s2分别使用哈希表统计,再对比两哈希表中字符出现次数。②对其中一字符串使用哈希表统计,另一字符串遍历进行对比。
  
  
  2)、题解
  使用两个哈希表来解决的情况:

class Solution {
public:
    bool CheckPermutation(string s1, string s2) {
        if(s1.size()!=s2.size()) return false;//两字符串长度不相等,非重排字符串
        // 使用两个哈希表来解决的情况:
        int hash1[26] = {0};
        int hash2[26] = {0};
        for(auto ch: s1)// 遍历存入字符
            hash1[ch -'a']++;
        for(auto ch: s2)
            hash2[ch -'a']++;

        for(int i = 0; i < 26; ++i)//判断
        {
            if(hash1[i] != hash2[i]) return false;
        }
        return true;
    }
};

  
  使用一个哈希表的情况:

class Solution {
public:
    bool CheckPermutation(string s1, string s2) {
        if(s1.size() != s2.size()) return false;//两字符串长度不相等,非重排字符串
        //使用一个哈希表的情况:
        int hash[26] = {0};
        for(auto ch : s1)//存放字符
            hash[ch - 'a']++;
        //判断:扫描第⼆个字符串,看看是否能重排
        for(auto ch : s2)
        {
            if(--hash[ch - 'a'] < 0)//上述首行判断保证了二者字符长度相同,当减到负数时,s2中存在与s1不相等的字符
                return false;
        }
        return true;
    }
};

  
  
  
  
  
  
  

4、存在重复元素 I(easy)

  题源:链接

在这里插入图片描述

  
  

4.1、题解

  1)、题解
  1、暴力解法: 可以对数组排个序,然后判断相邻元素是否相等,由此即可判断元素是否重复。
  2、使用哈希表: 用unordered_map容器统计每一个元素出现的次数,然后查看map容器是否有元素出现的次数 >= 2。

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_map<int,int> hash;
        for(auto e : nums)
        {
            hash[e]++;//将当前元素放入哈希表中
            //顺带对当前元素是否出现至少两次以上进行判断(注意:这里要先放数,再进行判断)
            auto ret = hash.find(e);
            if(ret != hash.end())
            {
                if(ret->second >= 2) return true;
            }
        }
        return false;
    }
};

  3、哈希表更简化的写法: ⽆需准确统计除元素出现的数目,可以直接使用unordered_set容器,仅需在遍历数组的过程中,判断当前元素是否在之前已经出现过

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_set<int> hash;//可以直接使用K模型
        for(auto e : nums)
        {
            if(hash.count(e)) return true;//为真,意味着在当前元素之前,哈希表中已经存在该元素(遍历到当前位置时,元素数目为2)
            else hash.insert(e);//为假,哈希表中无该元素,将其存放进入,方便后续元素判断。
        }
        return false;
    }
};

  
  
  
  
  
  
  

5、存在重复元素 II(easy)

  题源:链接

在这里插入图片描述
  
  

5.1、题解

  1)、思路分析
  仍旧可以使用哈希表(容器unordered_map),让数组内的元素做 key 值,该元素所对应的下标做 val 值,将「数组元素」和「下标」匹配存入「哈希表」中。
  
  这里有一个小问题:如果在遍历过程中,遇到相同的元素(即存在多个重复元素的情况),但哈希表unordered_map中只记录了一个下标,该如何处理?
  题目没有要求举例出所有情况,这里我们只需要判断一组能够满足条件的情况即可。而题目要求找满足abs(i - j) <= k的两下标间距,这里当然是间距越小越容易达成满足的条件。而我们是从左到右的顺序遍历,完全可以舍弃先前的下标,保留最新下标位置。
在这里插入图片描述

  
  
  2)、题解
  使用unordered_map.find()的写法:

class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        unordered_map<int,int> hash;
        for(int i = 0; i < nums.size(); ++i)
        {
            //找哈希表中,是否存在与当前元素相同的数值
            auto ret = hash.find(nums[i]);
            if(ret != hash.end())
            {   //若存在,判断二者下标间距是否满足条件
                int gap = abs(ret->second - i);
                if(gap <= k) return true;
            }
            //将当前元素存放入哈希表中
            hash[nums[i]] = i;//这里可覆盖原先下标,因为我们要求的是abs(i - j) <= k,与后续元素比较是,当然是间距越小越符合要求。
        }
        return false;//找不到的情况
    }
};

  使用unordered_map.count()的写法:

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]))
            {   //若存在,判断二者下标间距是否满足条件
                int gap = abs(hash[nums[i]] - i);//mapped_type& operator[] ( const key_type& k )
                if(gap <= k) return true;
            }
            //将当前元素存放入哈希表中
            hash[nums[i]] = i;//这里可覆盖原先下标,因为我们要求的是abs(i - j) <= k,与后续元素比较是,当然是间距越小越符合要求。
        }
        return false;//找不到的情况
    }
};

  
  
  
  
  
  
  

6、字母异位词分组(medium)

  题源:链接

在这里插入图片描述
  
  

6.1、题解

  1)、思路分析
  互为字母异位词的单词有⼀个特点:将它们「排序」之后,两个单词是「完全相同」的。 我们可以利用这个特性,将单词按照字典序排序,如果排序后的单词相同的话,就划分到同⼀组中。
  那么,哈希表中key-value值该如何设置?
  ①、将排序后的字符串( string )当做哈希表的 key 值;
  ②、将字母异位词数组( string[] )当成 val 值。
  
  
  2)、题解

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {

        unordered_map<string, vector<string>> hash;
        for (int i = 0; i < strs.size(); ++i) {
            // 排序
            string str = strs[i];
            sort(str.begin(), str.end());
            hash[str].push_back(strs[i]); // 将原字符串存放入
        }
        // 从哈希表中提取字符串分组返回
        vector<vector<string>> ret;
        for (auto& [x, y] : hash) {
            ret.push_back(y);
        }
        return ret;
    }
};

  
  
  
  
  
  
  
  
  
  
  

Fin、共勉。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值