让我们指定一些名称来描述问题:
m
= 数组序列的长度(在您的示例中为 14)
n
= 数组序列中唯一元素的总数(在示例中为 3)
k
= 每个搜索区域的长度(在示例中为 5)
g
= 您正在寻找的组数(2例如)
一种选择是在每个 size 的搜索区域中汇总您的数据k
。在您的示例中,如下所示:
{B A x x C}
{A x x C x}
...
我们n
为每个部分制作大小向量,第一个元素表示第一种元素的外观,比如A
B A x x C --> [1,1,1] (one appearance of each)
A x x C x --> [1,0,1]
等等。
我们可以为我们正在搜索的组做同样的事情:
{A A C} --> [2,0,1]
{A B C} --> [1,1,1]
现在问题变得很明显了。假设我们考虑搜索区域的摘要 [3,2,5] 和我们正在搜索的组的摘要 [0,1,2],我们可以通过认识到我们有第二个元素有 2 个选项,第三个元素有 (5x4)/(1x2) 个选项,所以总共有 20 个选项。
因此对于部分摘要 S,[s 1 , s 2 ,..,s n ] 和单个感兴趣的组 G, [g 1 , g 2 ,...g n ],我们可以计算总和我们可以从 S 中提取 G 的方法(c++ 样式代码,除了“!”表示阶乘):
int total_options = 1; // total ways to select G from S
for (int i = 0; i < n; ++i)
{
if(g[i] == 0)
continue; // this is an element that doesn't appear in G, so it shouldn't effect our count
if(s[i] < g[i])
return 0; // not enough elements in S for G
for (int d = 1, f = s[i]; f > max(g[i], s[i] - g[i]); --f, ++d)
total_options = total_options / d * f; // f, d are effectively factorials
// the previous loop is a more efficient version of:
// total_options *= (s[i]!) /(g[i]! * (s[i] - g[i])!);
}
return total_options;
您将为每个部分以及您正在搜索的每个组执行此操作。
时间复杂度:O( · g*m*(k + n)
)(我们必须k
在这里包括,因为最坏情况的阶乘计算)
空间复杂度:O( m + g*n
)(我们可以计算每个部分,因此不需要同时存储多个部分)
然后我们可以通过意识到每个连续的“部分”仅通过考虑离开的“尾”元素和进入的“头”元素来改进这一点,所以我们应该计算这两个在我们迭代时如何改变“选项计数”到下一节。我们将通过保持之前的“选项计数”计算来做到这一点,以及 NF(失败次数),即该区域中对于搜索组来说太少的元素数量。诀窍是保持一个正的“选项计数”,仅当 NF 为 0 时才会添加到总数中。这将为您G
在迭代 size 的主数组时为每个提供恒定时间的结果m
。
时间复杂度:O( g*m + g*n
)
空间复杂度:O( g*n + m
)
当主数组中的每个元素都是唯一的并且这些元素中的每一个在某些搜索中至少出现一次时,该算法的性能最差(否则我们可以将任何没有出现在任何搜索中的元素都处理相同,例如您的示例中的“x”)。因此,最坏情况的复杂性可以简化为:
时间复杂度:O( g*m
)
空间复杂度:O( g*m
)
我看不出如何获得更好的时间复杂度,但我很想知道是否有聪明人能想出一种空间复杂度更低的方法。
如果您在仅考虑头部和尾部的恒定时间迭代方面不知道我在说什么,请告诉我,我将用一个示例进行解释。