1、在使用map中要注意find(x)查询的是键,而不是值
2、要注意多使用迭代器来解决问题,而不是总是使用下标,要知道set、map常用的一些函数,便于简化计算。
3、当判断一个值是不是出现过,要注意使用哈希表(数组、map、set要注意使用场合)
哈希表就是一个数组,通过数组的下标索引访问数组中的元素
1、将一个庞大的值域映射到一个比较小的空间。
2、判断一个元素是否 出现在集合中(哈希表的重要作用)
所以使用哈希表时一般是对数据进行插入、查询,删除一般是通过另开一个数组,对冲突值打上标记,不去遍历它。
将大值域的数映射到小值域的函数,通过取模运算,确保每个值在哈希表中有对应的位置索引(但是当数据量>哈希函数的取模时,会导致哈希表中的一个索引位置有多个值,产生哈希碰撞)
哈希函数在进行取模时,尽量选择质数,离2的幂次尽量的远
解决哈希碰撞的方法有两种:
1、拉链法:将映射到同一个索引下的元素通过链表存储起来
//哈希表拉链法:
#include
#include
#include
using namespace std;
const int N = 100003;//取模选择质数
int h [N];//定义一个槽,在每个槽中定义一个链(存值、存下一个位置坐标,当前用到哪些位置)
int e[N],ne[N],idx;//e[N]表示当前节点的val,ne[N]表示当前节点的next指针,idx表示用到哪个唯一索引
//插入函数,在某个槽上进行头节点插入(单链表)
void insert(int x)
{
//定义一个哈希函数
int k = (x%N+N)%N;//有负值
//插入操作就是将值插入到h[k]上,链表头插
e[idx] = x;//将当前的值存入到链表中
ne[idx] = h[k]; //将当前节点的下一位置的指针指向h[k];
h[k] = idx++;
}
bool find (int x)
{
int k = (x%N+N)%N;//哈希函数,获取元素在哈希表中的具体槽位置
//遍历该槽上所有的节点
for(int i=h[k];i!=-1;i=ne[i])
{
if(e[i] == x)
return true;
else
return false;
}
}
2、开放寻址法:定义一个较大的数组,判断元素通过哈希函数后的数据在哈希表中的相应位置是不是存在,如果不存在直接插入,存在且不为当前元素,就往后 开始判断,直到数组最后,再从哈希表的0开始,直到找到一个空位置,将其插入。
/*
*哈希表的开放寻址法:也是在小值域上产生冲突时使用
*使用了一维数组,但是一维数组的长度是题目要求的2~3倍
*
*/
//假设题目要求的n为100000,所以定义N为200000,在N后边寻找一个质数赋给N
#include
#include
using namespace std;
const int N=2000003;
const int null =0x3f3f3f3f;//定义一个标志,表示数组是空,当数组是这个值的时,就是没有值所占用
int h[N];
//开放寻址法的关键函数find(),如果x在哈希表中已经存在,则返回存在的位置;如果不存在,则返回要存储的位置
int find(int x)
{
//定义在该值域下的对应哈希数值
int k = (x%N+N)%N;
while(h[k]!=null&&h[k]!=x)//有人且不等于x
{
k++;
if(k ==N) k=0;//如果到最后了,循环回去
}
return k;
}
1、数组(上边的两段程序就是模拟哈希表)
2、set(集合)
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
set | 红黑树 | 有序 | 不可重复 | 不可 | O(log n) | O(log n) |
multiset | 红黑树 | 有序 | 可重复 | 不可 | Olog(n) | O(log n) |
unordered_set | 哈希表 | 无序 | 不可重复 | 不可 | O(1) | O(1) |
如果在使用set集合解决哈希问题的时候:
1、优先使用unordered_set,因为它的 查询增删的效率是O(1),其他的都是O(log n);
2、如果要求有序,使用set;
3、如果有重复元素,使用multiset;
3、map(映射)
映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
map | 红黑树 | key有序 | key不可重复 | key不可 | O(log n) | O(log n) |
multimap | 红黑树 | key有序 | key可重复 | key不可 | Olog(n) | O(log n) |
unordered_map | 哈希表 | key无序 | key不可重复 | key不可 | O(1) | O(1) |
map中key是通过红黑树或者哈希表创建的,所以对key有规定,但是对value没有
如果在使用map解决哈希问题的时候:
1、优先使用unordered_map,因为它的查询增删效率是O(1),其他的都是O(log n);
2、如果需要key是有序的,那么就用map;
3、如果要求key不仅有序还要有重复数据的话,那么就用multimap。
#include
set是有序集合,但是输入可以是无序的,自动排列成有序存储
set的本质:动态的维护一个有序集合
set的声明: set <数据类型> 变量名称
set常用函数:与vector容器类似可以使用size、empty、clear
set迭代器:
set和multiset的迭代器称为“双向迭代器”,不像vector迭代器一样支持“随机访问”,支持星号“*”解引用,支持“++”“--”两种操作。
迭代器的定义方式: set
begin()/end()迭代器:
返回集合的首尾迭代器,时间复杂度为O(1)
s.begin()是指向集合中最小的元素的迭代器
s.end()是指向集合中最大的元素的下一个位置的迭代器
insert
s.insert(x)把一个元素x插入到集合s中,时间复杂度为O(log n)
在集合中如果已经存在x元素,则不会重复插入该元素,对集合状态无影响
find
s.find(x)在集合s中查找等于x的元素,并返回指向该元素的迭代器,若不存在,则返回s.end(),时间复杂度为O(log n)
lower_bound/upper_bound(unordered_set没有这个操作)
也是查找函数,与find类似,但查找条件不同,时间复杂度为O(log n)
s.lower_bound(x)查找大于等于x的元素中最小的一个,并返回指向的迭代器
s.upper_bound(x)查找大于x的元素中最小的一个,并返回指向的迭代器。
erase
设it是一个迭代器,s.erase(it)表示从s中删除迭代器it指向的元素,时间复杂度为O(log n)
设x是一个元素,s.erase(x)表示从s中删除迭代器等于x的元素,时间复杂度为O(k+log n)
count
s.count(x)表示返回集合s中等于x的元素个数,时间复杂度为O(k+log n),k表示x的个数
#include头文件中包含了map容器,map容器是一个键值对key-value的映射,其内部实现是一棵以key为关键码的红黑树。
map的作用:动态的维护有序数列
map的键值对:map的key和value可以是任意类型,其中key必须定义小于号运算符,因为在比较的时候比较的是key的值。
map的声明:map
set常用函数:
size/empty/clear/begin/end均与set类似
insert/erase
与set类似,但是其参数是pair
find
h.find(x)在变量名为h的map中查找key为x的二元组。
[]操作符
h[key]返回key映射的value的引用,时间复杂度为O(logn)
[]操作符是map最吸引人的地方,可以很方便的通过h[key]来得到key对应的value,和可以对h[key]进行赋值操作,改变key对应的value
题目链接:LeetCode242、有效的字母异位词
使用一个数组表示哈希表,哈希函数是将对应的小写字母转换成数组的26个下标
哈希表记录字母出现的次数
在另一个字符中,减去哈希表对应位置的次数,判断最后哈希表是不是全部为0
class Solution {
public:
bool isAnagram(string s, string t) {
//使用一个数组存储对应的26字母出现的次数,判断另一个字符是否相同
//类似于使用数组表示哈希表,哈希函数是将对应的小写字母转换成对应的数字下标,哈希表中存储的是出现的次数
int num[26];
for(auto a:s)
{
num[a-'a']++;
}
for(auto i : t)
{
num[i-'a']--;
}
for(int i=0;i
题目链接:两个数组的交集
将一个数组输入到无序集合中,对另一个数组遍历,判断无序集合中是否存在当前元素,如果存在,就输入到结果中
新知识:在创建unordered_set时,
1、可以使用vector数组的迭代器将其初始化,
2、也可以在创建之后,通过insert插入元素
//使用vector数组的迭代器初始化
unordered_set nums(nums1.begin(),nums1.end());
//使用insert插入
unordered_set nums
for(auto x :nums1)
{
nums.insert(x);
}
新知识:在将集合转换成vector时,可以通过迭代器直接转换
//res是unordered_set类型
vector(res.begin(),res.end())
class Solution {
public:
vector intersection(vector& nums1, vector& nums2) {
//判断交集,可以使用unordered_set进行判断,将一个数组输入进集合,对另一个数组进行find,
//find发现了,就记录,find没发现就继续
//第一步创建2个unordered_set,一个记录nus1中的元素,一个记录结果
unordered_set nums(nums1.begin(),nums1.end());
unordered_set res;
//开始判断数组2中的元素是不是在无序集合中
for(auto x :nums2)
{
if(nums.find(x)!=nums.end())
{
//添加到一个列表
res.insert(x);
}
}
return vector(res.begin(),res.end());
}
};
将数组1输入到哈希表,将该位置的哈希表设置为1,表示存在值,
遍历判断数组2的元素所在哈希表是否有值。
class Solution {
public:
vector intersection(vector& nums1, vector& nums2) {
int hash[1003];
//将数组1输入到哈希表中,等于1表示此处有值
for(auto i:nums1)
{
hash[i] =1;
}
//判断数组2与数组1的合集,定义一个无序集合存储结果
unordered_set res;
for(auto i:nums2)
{
if(hash[i]==1)
res.insert(i);
}
return vector(res.begin(),res.end());
}
};
题目链接:LeetCode202、快乐数
使用无序集合记录出现的数,当出现一样的,表示进入循环,不是快乐数
class Solution {
public:
int sum(int &n)
{
int res_sum=0;
while(n!=0)
{
res_sum +=(n%10)*(n%10);
n/=10;
}
return res_sum;
}
bool isHappy(int n) {
//定义一个小函数,计算每位数字平方和结果
//因为可能出现无限循环,定义一个无序集合unordered_set记录当前出现过的数据
unordered_set num ;
while(n!=1)
{
if(num.find(n)!=num.end())return false;
else
{
num.insert(n);
}
n = sum(n);
}
return true;
}
};
题目链接:LeetCode1、两数之和
因为要返回的索引,所以可以使用map来记录元素与索引
遍历数组,使数组中的元素寻找与map中相对应的target-nums[i]是否存在,如果存在就是找到了,如果不存在,就将当前的元素加入到map,继续下一个元素。
class Solution {
public:
vector twoSum(vector& nums, int target) {
//使用一个无序映射unordered_map,记录已经遍历过的数组中的数
//通过遍历数组的当前元素,插叙map中target-nums[i]的结果是否存在
//因为在map中first代表key,使用find(x),查询的是key是否存在,所以需要将元素值放到first,下标索引放到second
unordered_map map;
vectorres(2);
//遍历数组,将遍历过的元素存入map
for(int i=0;i
通过对数组进行排序,使用左右双指针找到对应的值,在原数组中找到两个值的下标。(思路,没实现)