编辑:由于不必要的二进制搜索,我以前有 O(nlog(n))。
我想到了一个解决方案,技术上 O(n),其中n 是字符串的长度,但常数很大。
为简单起见,让我们考虑一个类似的情况,只有两个字母 A 和 B(以及它们的小写字母),并让 l 成为字母表的大小以供将来参考。我处理了一个示例字符串ABabBaaA。
我们首先计算每个字母出现次数的前缀计数。在这种情况下,我们得到
i: 0, 1, 2, 3, 4, 5, 6, 7, 8
----------------------------
A: 0, 1, 1, 1, 1, 1, 1, 1, 2
a: 0, 0, 0, 1, 1, 1, 2, 3, 3
B: 0, 0, 1, 1, 1, 2, 2, 2, 2
b: 0, 0, 0, 0, 1, 1, 1, 1, 1
这样,假设我们从1开始索引字符串(为了实现,你可以在开头添加一个额外的字符,比如美元符号$),我们可以得到每个字母的出现次数在恒定时间的任何子字符串上(或者更确切地说 - 在 O(l) 中,但在我的情况下,l 设置为 2,在你的情况下为 l = 26,所以从技术上讲,这是恒定时间)。
好的,现在我们准备字符索引的数组/向量/队列,因此如果字符A 出现在索引1 和8 上,则结构将由1 和8 组成。我们得到
A: 1, 8
a: 3, 6, 7
B: 2, 5
b: 4
重要的是,在数组和向量中,我们可以通过逐一丢弃小于每个索引的索引,在摊销常数时间内查找某些“大于”的最小元素。
现在,算法。从大于0 的每个(左)索引开始,我们将找到与[left_index, right_index] 绑定的子字符串平衡的最早的右索引。我们这样做如下:
-
以left_index = right_index = i 开头为i = 1, ..., n。
-
读取right_index 的前缀计数数组并减去left_index - 1 的前缀计数,接收子字符串[left_index, right_index] 的计数。找到任何未通过“余额”检查的字母。如果没有,您找到了从left_index 开始的最短平衡子串。
-
查找第一次出现的大于left_index 的“缺失”字母。将right_index 设置为该事件的索引。转到第 1 步,保留修改后的right_index。
例如:以left_index = right_index = 1开头我们看到子字符串中每个字母出现的次数是1, 0, 0, 0,所以a检查失败。 a最早出现的是3,所以我们设置right_index = 3。我们回到第 1 步,接收一个新的事件数组:1, 1, 1, 0。现在b检查失败,它最早出现大于1的是4,所以我们将right_index设置为4。我们转到第 1 步,接收到出现的数组 1, 1, 1, 1,它通过了余额检查。
另一个例子:从left_index = right_index = 2 开始,我们在步骤 1 中得到一个出现的数组0, 0, 1, 0。现在b 未通过检查。 b大于left_index的最早出现是4,所以我们将right_index设置为4。现在我们得到了一个出现的数组0, 1, 1, 1,所以A 没有通过检查。 A 大于 left_index 的最早出现是 8,因此我们将 right_index 设置为该值。现在,出现的数组是2-1, 3-0, 2-0, 1-0,即1, 3, 2, 1,它通过了余额检查。
最终我们会找到最短的平衡子串是bB 和left_index = 4。
该算法的复杂度为 O(nl^2),因为:我们从 n 不同的索引开始,并且我们在 O 中执行最多 l 查找(对于 l 可能无法通过检查的不同字母) (1)。对于每次查找,我们必须计算 l 前缀和的差异。但由于l 是恒定的(尽管它可能很大,比如 26),这简化为 O(n)。