给定一个字符串和一个固定长度l,我如何计算长度为l的不同子串的数量? 字符集的大小也是已知的。 (表示为s) 例如,给定一个字符串“PccjcjcZ”,s = 4,l = 3, 然后有5个不同的子串: “PCC”; “CCJ”; “CJC”; “JCJ”; “JCZ”
我尝试使用哈希表,但速度仍然很慢。 实际上我不知道如何使用字符大小。 我做过这样的事情
int diffPatterns(const string& src, int len, int setSize) {
int cnt = 0;
node* table[1 << 15];
int tableSize = 1 << 15;
for (int i = 0; i < tableSize; ++i) {
table[i] = NULL;
}
unsigned int hashValue = 0;
int end = (int)src.size() - len;
for (int i = 0; i <= end; ++i) {
hashValue = hashF(src, i, len);
if (table[hashValue] == NULL) {
table[hashValue] = new node(i);
cnt ++;
} else {
if (!compList(src, i, table[hashValue], len)) {
cnt ++;
};
}
}
for (int i = 0; i < tableSize; ++i) {
deleteList(table[i]);
}
return cnt;
}
答案 0 :(得分:2)
Hastables很好且实用,但请记住,如果子串的长度是L,并且整个字符串长度是N,则算法是Theta((N + 1-L)* L),即Theta( NL)对于大多数L.记住,只计算哈希需要Theta(L)时间。另外可能会发生碰撞。
可以使用后缀树,并提供有保证的O(N)时间算法(深度为L或更大的路径计数),但实现很复杂。保存优雅是你可以用你选择的语言找到现成的实现。
答案 1 :(得分:1)
使用哈希表的想法很好。它应该运作良好。
将您自己的哈希表实现为长度为2 ^ 15的数组的想法很糟糕。请改为Hashtable in C++?。
答案 2 :(得分:0)
您可以使用unorder_set
并将字符串插入集合中,然后获取集合的大小。由于集合中的值是唯一的,因此将不包括与先前找到的子字符串相同的子字符串。这应该使您接近O(StringSize - SubstringSize)复杂性
#include <iostream>
#include <string>
#include <unordered_set>
int main()
{
std::string test = "PccjcjcZ";
std::unordered_set<std::string> counter;
size_t substringSize = 3;
for (size_t i = 0; i < test.size() - substringSize + 1; ++i)
{
counter.insert(test.substr(i, substringSize));
}
std::cout << counter.size();
std::cin.get();
return 0;
}
答案 3 :(得分:0)
Veronica Kham对此问题的回答很好,但我们可以将此方法改进为预期的O(n)
,并且仍然使用简单的哈希表而不是后缀树或任何其他高级数据结构。
让X
和Y
是长度为L
的两个相邻子字符串,更准确地说是:
X = A[i, i + L - 1]
Y = B[i + 1, i + 1 + L - 1]
让我们的字母表中的每个字母分配一个非负整数,例如a := 1, b := 2
,依此类推。
现在让我们定义一个哈希函数h
:
h(A[i, j]) := (P^(L-1) * A[i] + P^(L-2) * A[i + 1] + ... + A[j]) % M
其中P
是理想情况下大于字母大小的素数,M
是一个非常大的数字,表示不同可能的哈希数,例如,您可以将M
设置为最大值您的系统中可用unsigned long long int
。
关键的观察结果如下:
如果您为
X
计算了哈希值,则可以计算Y
中的哈希值O(1)
时间。
假设我们计算了h(X)
,这显然可以在O(L)
时间内完成。我们想要计算h(Y)
。请注意,由于X
和Y
仅相差2个字符,因此我们可以使用加法和乘法轻松完成此操作:
h(Y) = ((h(X) - P^L * A[i]) * P) + A[j + 1]) % M
基本上,我们减去字母A [i]乘以h(X)
中的系数,将结果乘以P
,以便为其余字母获得适当的系数,最后,我们正在添加最后一个字母A[j + 1]
。
请注意,我们可以在开始时预先计算P
的权限,然后我们可以模拟M
。
由于我们的散列函数返回整数,我们可以使用任何散列表来存储它们。请记住使所有计算以M
为模,并避免整数溢出。
当然,可能会发生碰撞,但由于P
是素数且M
非常庞大,因此这是一种罕见的情况。
如果要降低碰撞的概率,可以使用两种不同的散列函数,例如在每种函数中使用不同的模数。如果碰撞的概率是p
使用一个这样的函数,那么对于两个函数它是p^2
,我们可以通过这个技巧使它变得任意小。
答案 4 :(得分:0)
答案 5 :(得分:0)
后缀自动机也可以在O(N)中完成。
编码很容易,但很难理解。
以下是关于它的论文http://dl.acm.org/citation.cfm?doid=375360.375365
http://www.sciencedirect.com/science/article/pii/S0304397509002370