C++优选算法十 哈希表

一、std::unordered_map

std::unordered_map 是一个关联容器,它存储键值对,并且使用哈希表来组织数据。因此,它提供了快速的查找、插入和删除操作,平均时间复杂度为 O(1)。

1.主要特性

  1. 无序性:元素在容器中的位置并不按照插入顺序或键的顺序排列,而是根据哈希值分布。
  2. 快速查找:平均情况下,查找、插入和删除操作的时间复杂度为 O(1)。
  3. 键唯一性 :每个键在 std::unordered_map 中是唯一的。

2.基本操作

  • 插入元素 :使用 insert 方法或 operator[]
  • 查找元素 :使用 find 方法或 operator[](如果键不存在,operator[] 会插入一个默认构造的值)。
  • 删除元素 :使用 erase 方法。
  • 遍历元素:可以使用迭代器遍历所有元素。

二、示例题目

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

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 1:

复制代码
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

复制代码
输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

复制代码
输入:nums = [3,3], target = 6
输出:[0,1]

解法(哈希表)
算法思路:

  • 如果我们可以事先将「数组内的元素」和下标」绑定在一起存入「哈希表」中,然后直接在哈希表中查找每一个元素的 target- nums[i],就能快速的找到「目标和的下标」。
  • 这里有一个小技巧,我们可以不用将充素全部放入到哈希表之后,再来二次遍历(因为要处理元素相同的情况)。而是在将元素放入到哈希表中的「同时」,直接来检查表中是否已经存在当前元素所对应的目标元素(即 target-nums[i])。如果它存在,那我们已经找到了对应解,并立即将其返回。无需将元素全部放入哈希表中,提高效率。
  • 因为哈希表中查找无素的时间复杂度是 0(1),遍历一遍数组的时间复杂度为 O(N),因此可以将时间复杂度降到O(N)。

这是一个典型的「用空间交换时间」的方式。

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) 
    {
        unordered_map<int,int> hash;
        for(int i=0;i<nums.size();i++)
        {
            int x=target-nums[i];
            if(hash.count(x))
                return {hash[x],i};
            hash[nums[i]]=i;
        }
        return {-1,-1};
    }
};

2.判断是否互为字符重排. - 力扣(LeetCode)

给定两个由小写字母组成的字符串 s1s2,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。

示例 1:

复制代码
输入: s1= "abc", s2= "bca"
输出: true 

示例 2:

复制代码
输入: s1= "abc", s2= "bad"
输出: false

解法(哈希表)
算法思路:

1.当两个字符串的长度不相等的时候,是不可能构成互相重排的,直接返回 false;

2.如果两个字符串能够构成互相重排,那么每个字符串中「各个字符」出现的「次数」一定是相同的。因此,我们可以分别统计出这两个字符串中各个字符出现的次数,然后逐个比较是否相等即可。这样的话,我们就可以选择「哈希表」来统计字符串中字符出现的次数。

cpp 复制代码
class Solution {
public:
    bool CheckPermutation(string s1, string s2) 
    {
        if(s1.size()!=s2.size())
            return false;
        int hash[26];
        for(int i=0;i<s1.size();i++)//统计第一个字符串的信息
        {
            hash[s1[i]-'a']++;
        }
        for(int j=0;j<s2.size();j++)//扫描第二个字符串,看看是否能重排
        {
            hash[s2[j]-'a']--;
            if(hash[s2[j]-'a']<0)//小于0证明有第一个字符串不存在的字符
                return false;
        }
        return true;
    }
};

3.存在重复元素. - 力扣(LeetCode)

给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false

示例 1:

**输入:**nums = [1,2,3,1]

**输出:**true

解释:

元素 1 在下标 0 和 3 出现。

示例 2:

**输入:**nums = [1,2,3,4]

**输出:**false

解释:

所有元素都不同。

示例 3:

**输入:**nums = [1,1,1,3,3,4,3,2,4,2]

**输出:**true

解法(哈希表)
算法思路:

分析一下题目,出现「至少两次」的意思就是数组中存在着重复的元素,因此我们可以无需统计元素出现的数目。仅需在遍历数组的过程中,检查当前元素「是否在之前已经出现过」即可。因此我们可以利用哈希表,仅需存储数「组内的元素」。在遍历数组的时候,一边检查哈希表中是否已经出现过当前元素,一边将元素加入到 哈希表中。

cpp 复制代码
class Solution {
public:
    bool containsDuplicate(vector<int>& nums) 
    {
        unordered_map<int,int> hash;

        for(int x:nums)
        {
            if(hash.count(x))
                return true;
            else
                hash[x]++;
        }
        return false;
    }
};

4.存在重复元素Ⅱ. - 力扣(LeetCode)

给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 ij ,满足 nums[i] == nums[j]abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false

示例 1:

复制代码
输入:nums = [1,2,3,1], k = 3
输出:true

示例 2:

复制代码
输入:nums = [1,0,1,1], k = 1
输出:true

示例 3:

复制代码
输入:nums = [1,2,3,1,2,3], k = 2
输出:false

解法(哈希表)
算法思路:

解决该问题需要我们快速定位到两个信息

两个相同的元素;

这两个相同元素的下标。

因此,我们可以使用「哈希表」,令数组内的元素做 key 值,该元素所对应的下标做 val 值,将「数组元素」和「下标」绑定在一起,存入到「哈希表」中。

思考题:

如果数组内存在大量的「重复元素」,而我们判断下标所对应的元素是否符合条件的时候,需要将不同下标的元素作比较,怎么处理这个情况呢?

答:这里运用了一个「小贪心」。

我们按照下标「从小到大」的顺序遍历数组,当遇到两个元素相同,并且比较它们的下标时,这两个下标一定是距离最近的,因为:

  • 如果当前判断符合条件直接返回 true,无需继续往后查找。.
  • 如果不符合条件,那么前一个下标一定不可能与后续相同元素的下标匹配(因为下标在逐渐变大),那么我们可以大胆舍去前一个存储的下标,转而将其换成新的下标,继续匹配。
cpp 复制代码
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 j=hash[nums[i]];
                if(abs(i-j)<=k)
                    return true;
                else
                    hash[nums[i]]=i;
            }
            else
            {
                hash[nums[i]]=i;
            }
        }
        return false;
    }
};

5.字母异位词分组**. - 力扣(LeetCode)**

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

示例 1:

复制代码
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]

输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

示例 2:

复制代码
输入: strs = [""]

输出: [[""]]

示例 3:

复制代码
输入: strs = ["a"]

输出: [["a"]]

解法(哈希表+排序)
算法思路:

互为字母异位词的单词有一个特点:将它们「排序 」之后,两个单词应该是「完全相同」的。所以,我们可以利用这个特性,将单词按照字典序排序,如果排序后的单词相同的话,就划分到同一组中。

这时我们就要处理两个问题:

  • 排序后的单词与原单词需要能互相映射;
  • 将排序后相同的单词,「划分到同一组」;

利用语言提供的「容器」的强大的功能就能实现这两点:

  • 将排序后的字符串(string)当做哈希表的 key 值;
  • 将字母异位词数组(string[])当成 val 值。

定义一个「哈希表」即可解决问题。

cpp 复制代码
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) 
    {
        unordered_map<string,vector<string>> hash;

        for(string x:strs)
        {
            string str=x;
            sort(x.begin(),x.end());
            
            hash[x].push_back(str);
        }
        vector<vector<string>> vv;
        for(auto x:hash)
        {
            vv.push_back(x.second);
        }
        return vv;
    }
};

从这道题我们可以拓展一下视野,不要将容器局限于基本类型,它也可以是一个容器嵌套另一个容器的复杂类型。

相关推荐
机器视觉知识推荐、就业指导1 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Swift社区2 小时前
LeetCode - #139 单词拆分
算法·leetcode·职场和发展
Kent_J_Truman2 小时前
greater<>() 、less<>()及运算符 < 重载在排序和堆中的使用
算法
IT 青年3 小时前
数据结构 (1)基本概念和术语
数据结构·算法
Yang.993 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王3 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_3 小时前
C++自己写类 和 运算符重载函数
c++
Dong雨3 小时前
力扣hot100-->栈/单调栈
算法·leetcode·职场和发展
六月的翅膀3 小时前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++