文章目录
bit数组
//bit数组指的是相比于一般数组每一位上都是1bit,如何实现?
//利用int数组拆分实现
int arr[10];//10x4x8=320bit
int i = 144;//第i位
//如何拿到第i位的状态?
int numindex = i/32;
int moveindex = i%32;
int state = ((arr[numindex]>>moveindex) &1);
//如何将第i位改为1?
arr[numindex] = arr[numindex]|(1<<moveindex);
//如何把第i位改成0?
arr[numindex] = arr[numindex]&(!(1<<moveindex));
1、布隆过滤器
什么时候使用布隆过滤器?类似黑名单问题(只有增,查),允许一定的失误率。
单样本的数据大小只要哈希函数可以接受即可。
//假设样本数为n,失误率为p,则所需要的m个比特位,需要k个哈希函数
//满足:m = -(n*lnp)/(ln2*ln2);k = ln2*m/n (向上取整)
//因为实际的k向上取整,m尽量选大的(比如算出来是16g,但是面试官可以给你32g,你就可以选择32,使得真实失误率更低) p真 = (1-e.^(-n*k真/m真)).^k真
/*
确定好参数后,将每个数据依次输入到k个哈希函数,将每次得到的值都对m取余,将bit数组对应结果上的标志位置1(已经为1的就保持就行)。
查询时,将数据依次依次输入到k个哈希函数中,在对m取余,查看bit数组上对应位是否为1,若有一位为零,则不属于,若全为零,则属于。
*/
2、一致性哈希算法:
问题背景:假设现在我有三台服务器来存储大量的数据。一般做法,计算每次数据键的哈希值,并将哈希值对3取模,模为几,就放到几号服务器。但这个就有几个问题,首先是键的选取,越多越好,(假设键选取国家名,一般而言,中美最多,就势必使得其中两台机器的负载加重),其次,如果要增加或删除一台服务器,则需要全数据的迁移,故代价太大。
改进思路:假设哈希值从0-2.^32次方,把他考虑为一个顺时针首位相接的圆。对我们每台服务器的键求一个哈希值。按顺时针,将数据存放到大于等于数据对应哈希值的那台服务器上(可以二分查找确定服务器)。问题1:当你的服务器数量非常少时,服务器对应的哈希值肯定不能均分,就势必会导致不平衡。问题2,要增加一台服务器时,可以将该服务器计算出对应的哈希,只需要将它以及它之前的第一个服务器哈希值之间的数据进行迁移,而不影响其他,是对上述问题的改进,但是假设前一次服务器均分了,那么加入或者删除新的服务器,势必会不平衡。
继续改进:引入虚拟节点,为每个服务器分配多个节点(服务器性能相同,就分配一样多,不相同,就好的多分配点)然后将每个节点计算出对应的哈希值,数据存放和前面一样。这样子就解决了平衡问题,并且增加或者删除的时候,也可以继续保持均衡。若增加服务器,只需要增加一组节点,将该节点对应哈希值插入环内,迁移节点到逆时针第一个节点之间的数据。
3、并查集
必须初始化,
题目:假设用户给你一些字符(‘a’,‘b’,‘c’,‘d’,‘e’)可能的操作:查询任意两个是否在一个集合;将任意两个添加到一个集合。
思路:为每一个集合设置一个父节点,如果是一个集合的,就最上层的父节点一定相同。查询时,就查两个字符的最上层父节点是否相同,合并时,就只需要将最上层的合并为同一个即可。一开始将每个字符初始化为一个节点,并且将每个节点的父节点设置为自己。执行合并操作时,首先查询是否在一个集合(即查询各个字符对应节点的最上层父节点),若不是,则将其中一个的最上层节点改为另一个。(最基础的)
struct Node {
char val;
Node(char c) :val(c) {
};
Node() {
};
};
class UnionFindSet {
private:
unordered_map<char, Node*> elementmap;//将用户数据转换为节点
unordered_map<Node*, Node*> fathermap;//统计一个节点的父节点
unordered_map<Node*, int> sizemap;//统计作为父节点的节点是几个节点的父节点,故不为零的个数为集合数,对应大小为该集合下的元素数
public:
UnionFindSet(string s) {
for (auto c : s) {
Node* node = new Node(c);
elementmap.emplace(c, node);
fathermap.emplace(node, node);
sizemap.emplace(node, 1);
}
}
~UnionFindSet(){
for (char c : str)
delete elementmap[c];
}
bool IsSameSet(char c1, char c2) {
Node* node1 = elementmap[c1];
Node* node2 = elementmap[c2];
if(elementmap.count(c1)>0&&elementmap.count(c2)>0)//首先保真为已经添加过得元素才行
return FindHead(node1) == FindHead(node2);
return false;
}
void Union(char c1, char c2) {
Node* node1 = elementmap[c1];
Node* node2 = elementmap[c2];
Node* f1 = FindHead(node1);
Node* f2 = FindHead(node2);
if (f1 == f2) return;
if (sizemap[f1] < sizemap[f2]) {
fathermap[f1] = f2;
sizemap[f2] += sizemap[f1];
sizemap[f1] = 0;
}
else {
fathermap[f2] = f1;
sizemap[f1] += sizemap[f2];
sizemap[f2] = 0;
}
}
private:
Node* FindHead(Node* node) {
//函数的返回值会和传入参数一致。
stack<Node*> st;
while (node != fathermap[node]) {
st.push(node);
node = fathermap[node];
}
while (!st.empty()) {
fathermap[st.top()] = node;
st.pop();
}
return node;
}
};
4、KMP算法
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。
假设现在有字符换s1,s2,其中s1是一个长字符串,需要我们检查s1是否包含s2;首先计算s2中每个元素的最大前缀和后缀相等的元素个数。
最大前缀和最大后缀相等元素个数计算:如现在计算索引5,则索引5前的子串为abcda,第一次第一个a和最后的a相等,第二次前面的ab和倒数的da不相等,故个数为1。
索引: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
s1: | a | b | c | d | a | b | c | d | d | a | e |
s2: | a | b | c | d | a | b | c | d | e | ||
next: | -1 | 0 | 0 | 0 | 0 | 1 | 2 | 3 | 4 |
如上,next记录的是s2中每个元素的最大相等前后缀,一开始从索引0开始逐个比较,一直到索引8位置不相等。按暴力搜索的话,此时应该开始比较s1的1位置与s2的0位置比较.但KMP算法,此时去比较s1的8位置与s2的next(8)即4位置,若相等,则重复上面的步骤,直到完全匹配或者出现不相等的情况。若不相等,则去比较s1的8位置和s2的naxt(4)即0位置。此时s1的8位置和s2的0位置还是不相等,继续比较s1的8位置和s2的next(0)即-1,此时就知道在s1的0-8上没有能匹配s2的,直接重新开始比较s1的9位置和s2的0位置,重复上述过程。直到s1遍历完或者有完全匹配的情况即可。
next数组求解:next[0]=-1;next[1] = 0;
等于i位置,可以利用next[i-1]的信息
索引: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
s2: | a | b | c | d | a | b | c | d | e |
next: | -1 | 0 | 0 | 0 | 0 | 1 | 2 | 3 | 4 |
首先给出前两个索引的next,假设现在我们要求索引为7的时候,我们只到next[6]=2,则先比较s2[6],s2[next[6]] = s2[2],若相等,则next[7] = next[6]+1;若不相等则继续比较s2[6],s2[next[2]]=s2[0],若相等,则next[7]=next[2]+1;若不相等,则继续该过程,直到next[0] = -1或者相等,则next[7] = next[j]+1
class Solution{
public:
int KMP(string s1,string s2){
//若s1有等于s2的子串,则返回该子串的首元素索引,否则返回-1
if(s1.empty()||s2.empty()||s2.size()>s1.size()) return -1;
auto next = getNextArray(s2);
int x=0;
int y =0;
while(x<s1.size()&&y<s2.size()){
if(s1[x]==s2[y]){
x++;
y++;
}
else if(y==0) x++;
else y=next[y];
}
return y==s2.size()?x-y:-1;
}
vector<int> getNextArray(string s){
if(s.size()==1) return {
-1};
vector<int> next(s.size(),0);
s[0]=-1;
int i = 2;
int j = 0;//表示每次i-1的next的值
while(i<s.size()){
if(s[i-1]==s[j])
next[i++] = ++j;
else if(j >0)
j = next[j];
else next[i++] = 0;
}
return next;
}
};
5、Mancher算法
解决问题:字符串str中,最长回文子串的长度求解?如何做到o(n)?
//伪代码
manacher(string s){
//将s处理成间隔串str(1221 -> #1#2#2#1)
int R=-1;//表示回文串的最右边界
int C=-1;//表示最右边界对应的中心点
vector<int> parr(str.size(),0);
for