Python 计算最多包含m个偶数元素的不同子阵列的数量

Python 计算最多包含m个偶数元素的不同子阵列的数量,python,arrays,algorithm,data-structures,time-complexity,Python,Arrays,Algorithm,Data Structures,Time Complexity,您将得到一个整数数组,每个整数的范围为[0,1000],还有一些数字m。例如,您可能会得到以下输入: A=[5,6,7,8] m=1 问题是尽可能有效地确定数组A中最多包含m个偶数的不同的非空子数组的数量。例如,对于上述阵列,有八个不同的子阵列,最多有一个偶数,如下所示: [(5, 6, 7), (6, 7), (5, 6), (8), (5), (6), (7), (7, 8)] 以下是我迄今为止的解决方案,它在时间O(n3)内运行: def(A,m): subs=[tuple(A[i:j

您将得到一个整数数组,每个整数的范围为[0,1000],还有一些数字m。例如,您可能会得到以下输入:

A=[5,6,7,8] m=1
问题是尽可能有效地确定数组A中最多包含m个偶数的不同的非空子数组的数量。例如,对于上述阵列,有八个不同的子阵列,最多有一个偶数,如下所示:

[(5, 6, 7), (6, 7), (5, 6), (8), (5), (6), (7), (7, 8)]
以下是我迄今为止的解决方案,它在时间O(n3)内运行:

def(A,m):
subs=[tuple(A[i:j])表示范围(0,len(A))中的i,表示范围(i+1,len(A)+1)中的j]
uniqSubs=集合(子集合)

return len([n代表uniqSubs中的n,如果sum(int(i)%2==0代表in n中的i)我相信你可以通过使用后缀树在线性时间内实现这一点。这当然不是一个轻量级的解决方案-祝你好运,编写一个线性时间算法来构建一个具有可变大小字母表的后缀树!-但它表明这是可能的

想法是这样的。首先为数组构建后缀树,不要将其视为数字列表,而是将其视为字符串,其中每个字符都是一个数字。因为您知道所有数字最多为1000,不同字符的数量是一个常量,因此使用快速后缀树构造算法(例如,SA-is),您可以在时间O(n)内构建后缀树

后缀树在这里是一种很好的结构,因为它们将相同子字符串的重复副本合并到重叠的组中,这使得重复数据消除更加容易。例如,如果模式[1,3,7]在数组中多次出现,则根将恰好包含一条以[1,3,7]开头的路径

现在的问题是如何从后缀树到不同子数组的数量。现在,让我们来解决一个更简单的问题-如何计算不同子数组的数量,周期,完全忽略奇数和偶数的限制?幸运的是,这是一个研究得很好的问题,可以在线性时间内解决。本质上,后缀树中编码的每个前缀都对应于原始数组的一个不同子数组,因此您只需计算有多少前缀。这可以通过递归遍历树来完成,为树中的每一条边加上沿着该边的字符数。这可以在时间O(n)内完成因为一个长度为n的数组/字符串的后缀树有O(n)个节点,我们花固定的时间处理每个节点(只需查看其上方的边缘)

所以现在我们只需要加入对允许使用的偶数数量的限制。这使事情有点复杂,但原因是微妙的。直觉上,这似乎不应该是一个问题。毕竟,我们可以对后缀树进行DFS,并在进行时计算路径上偶数的数量e穿过,一超过m就停了下来

这种方法的问题是,即使后缀树中有O(n)个节点,但边隐式地编码长度可以高达n的范围。因此,扫描边的行为本身可能会将运行时间增加到Ω(n2):访问Θ(n)边并对每条边执行Ω(n)工作

但是,我们可以稍微加快速度。后缀树中的每条边通常表示为原始数组中的一对索引[start,stop]。因此,让我们想象一下,作为额外的预处理步骤,我们构建一个表Evens,使Evens[n]返回数组中直到并包括位置n的偶数数目。然后,我们可以通过计算Evens[start]-Evens[stop]来计算任意范围[start,stop]中偶数的数目。这需要时间O(1),这意味着我们可以将在一条路径上遇到的偶数的数量按时间比例聚合到后面的边数,而不是遇到的字符数

…除了有一个复杂的问题。如果我们有一个很长的边,在读取该边之前,我们知道我们低于偶数极限,而在读取该边之后,我们知道我们高于极限?这意味着我们需要中途停止,但我们不确定它到底在哪里。这可能需要让我们在边缘上进行线性搜索以找到交叉点,我们的运行时间就到了

但幸运的是,有一种方法可以摆脱这个小困境。(下一节包含@Matt Timmermans发现的一个改进)。作为预处理的一部分,除了Evens数组之外,还可以构建第二个表KthEven,其中KthEven[i]返回数组中第k个偶数的位置。这可以在时间O(n)内构建使用Evens数组。一旦你有了这个,让我们假设你有一个坏的边缘,一个会把你推到极限之外的边缘。如果你知道到目前为止遇到了多少个偶数,你可以确定将把你推到极限之外的偶数的索引。然后,你可以通过索引到KthEven表中来查找偶数在哪里在时间O(1)中,这意味着我们只需要在后缀树中的每条边上花费O(1)个工作,将我们的运行时间向下推到O(n)

总而言之,这是这个问题的线性时间解决方案:

  • 使用快速后缀树构造算法(如SA-IS或Ukkonen算法)为数组构建后缀树。这需要时间O(n),因为字符串中最多有1000个不同的数字,1000是一个常量

  • 在时间O(n)中计算表偶数[n]

  • 在时间O(n)中计算表KthEven[n]

  • 在树上执行DFS,跟踪到目前为止遇到的偶数数目。当遇到边[start,stop]时,使用偶数时间O(1)计算该范围内有多少偶数。如果该值低于限制,则继续递归。如果没有,则使用KthEven表计算出有多少边是我们的
    def  beautiful(A, m):
        subs = [tuple(A[i:j]) for i in range(0, len(A)) for j in range(i + 1, len(A) + 1)]
        uniqSubs = set(subs)
    
         return len([n  for n in uniqSubs if sum(int(i) % 2 == 0  for i in n)<=m ])