这篇文章上次修改于 483 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
算法学习笔记(5): 异或哈希
简介
异或哈希主要利用了异或操作的特殊性,再从特殊性出发,解决阻挡特殊性的问题。
异或操作的特殊性
异或操作是对数字的二进制表示按位相加并对2取余,它的运算十分高效,利于计算机的运算。异或运算符合交换律等等。同时有个特性A ^ A = 0
.
推论可得当一个数进行偶数次异或运算后,其值为0,当一个数进行奇数次操作后,其结果为自身
问题1:
一个数组中除了一个元素外其他元素都出现了两次(偶数次),找出这个元素。
题解:
我们就可以采用异或操作的特殊性,一旦异或两次就会变成0,故剩余的数就是所有值异或的结果。同样很好理解,类似于一个bitmap,每个数都占据一个位的空间,如果出现一次就变成1,再出现一次就变成0。最后的运算结果值剩下的位就是只出现1次的数了。
但是反思一下,对于不同的数,我都要将它们映射到不同的位上,而且一旦有n个数,我的bitmap相当于使用了2^n的空间,那如果我们不采用这种方式,直接使用这个值呢
修改尝试
对于序列,如果直接使用这个值,就会产生不同的数直接互相干扰,例如数列 3 5 7,他的运算结果是0,因为3和5异或恰好就是7。为了避免这种互相干扰带来的冲突,我们选择哈希随机数。
哈希随机数
此时就需要请出我们的哈希产生作用,我们希望将原本的序列中的数字映射到一个不互相干扰的数字上,对此我们通常采用mt19937_64 rnd(time(0))
,产生一个随机数,再将普通数字映射到上面。
代入该随机数的目的就是为了减少冲突。
mt19937_64 是 c++ 自带的梅森旋转(Mersenne Twister)伪随机数,可以随机生成 64 位整数。同时它输出的数字也是64位无符号整数。
此时代入它,让我们解决更加复杂的题目。
问题2:
给一个长度为 n 的数组 A,找到所有的连续子序列 S ,其长度为L,满足:S包含A中前L个的所有数字。求这样的区间个数。
题解:
就是在询问对于区间[L, R] 和 区间[1, R - L + 1], 元素是否相同,对此可以理解为
判断一个序列是否是另一个序列的排列组合问题
以下便是题解代码
#include<bits/stdc++.h>
using namespace std;
mt19937_64 random(time(0)); // 创建随机数引擎
using hash_t = uint64_t; // 随机数和运算后的数都是64无符号整数
int solve(int n)
{
vector<hash_t> preXorValue(n + 1);
vector<hash_t> hashValue(n + 1);
vector<bool> hasSet(n + 1);
for (int i = 1; i <= n; i++)
{
int temp;
cin >> temp;
if(not hasSet[temp]){
hashValue[temp] = random();
hasSet[temp] = true;
}
preXorValue[i] = preXorValue[i - 1] ^ hashValue[temp]; // 计算前缀和
}
int result = 0;
for (int l = 1; l <= n; l++)
for (int r = l; r <= n; r++)
if ((preXorValue[r] ^ preXorValue[l - 1]) == preXorValue[r - l + 1]) // 注意异或运算的优先级,加括号
result++;
return result;
}
问题3:
判断一个序列的乘积是否是完全平方数
题解:
对序列的数进行因数分解,然后改序列所有因数出现的个数都是偶数个,即为平方数