如果你愿意在 C 中实现一个哈希映射,你可以用我认为是 O(n) 的方式来解决这个问题。
保持所有 As 和 B 的标准化计数。归一化是指 C 计数始终为零。 C 计数也是隐含的,因为您的字符串仅包含 As、Bs 和 Cs,因此当前字符串长度必须为 A + B + C。
从计数为 1 的 (0, 0) 计数的哈希映射开始。
通过字符串一次。当您传递 A 时,增加 A 计数。当你传递一个 B 时,增加 B 计数。当您通过 C 时,减少 A 和 B 的计数。如果当前计数 (A, B) 不存在,则将其添加到哈希映射中并递增。
举例说明:
0 0
A 1 0 *--------------
B 1 1 valid
A 2 1 substring
C 1 0 *--------------
A 2 0
最后,遍历所有哈希映射条目并将该条目的三角形和添加到您的总计数中。通过三角和t(n),我的意思是:t(1) = 0、t(2) = 1、t(3) = 2 + 1、t(4) = 3 + 2 + 1 等等。这反映了哈希条目表示有效子字符串的边界并且您可以合并相邻子字符串的事实:
ACB ABC BCA BCA +4
ACBABC ABCBCA BCABCA +3
ACBABCBCA ABCBCABCA +2
ACBABCBCABCA +1
在伪 C 中:
int nsubseq(const char *str)
{
Map *map = map_new();
const char *p;
int aa = 0;
int bb = 0;
map_add(map, key(aa, bb), 1);
for (p = str; *p; p++) {
if (*p == 'A') aa++;
else if (*p == 'B') bb++;
else if (*p == 'C') aa--, bb--;
int *q = map_find(map, key(aa, bb));
if (q) {
*q = *q + 1;
} else {
map_add(map, key(aa, bb), 1);
}
}
int count = 0;
for (int *p = map_begin(map); p; p = map_next(map)) {
int n = *p;
while (n--) count += n;
}
map_delete(map);
return count;
}
(这实际上是真正的 C,只是您必须实现所有 map 函数。当然,或者使用现有的哈希映射实现。)
代码的性能取决于哈希映射的实现,但哈希大小为 4096,我可以在不到一秒的时间内扫描一百万个平均分布的 As、Bs 和 Cs 的字符串。这是最理想的情况;字符串的分布越不均匀,性能就会下降。没有命中的极端情况(只有 As 或只有 As 和 Bs),基本上每个字符都会在哈希映射中创建一个新条目,大约需要十二倍的时间。
如果散列的条目是索引列表而不是计数,您甚至可以提取子字符串的值,尽管对于具有 100k 条目的字符串,这将是多余的。