动态编程:在线性时间内可以找到偶数1和0的区间吗?

时间:2011-08-06 15:32:26

标签: algorithm dynamic-programming

在网上找到以下内容:

  

你有一个数组   0和1,你想输出所有的间隔(i,j)   0的数量和1的数量相等。示例

    pos = 0 1 2 3 4 5 6 7 8
          0 1 0 0 1 1 1 1 0
     

一个间隔是(0,1)因为有数字   0和1相等。还有许多其他间隔,找到所有这些间隔   在线性时间。

我认为没有线性时间算法,因为可能有n ^ 2个这样的间隔。 我对吗?我怎样才能证明有n ^ 2这样的?

4 个答案:

答案 0 :(得分:8)

这是我能想到的最快的方式,它与间隔的数量呈线性关系。

设L为原始数字列表,A为空数组的哈希值,其中最初为A [0] = [0]

sum = 0
for i in 0..n
  if L[i] == 0:
    sum--
    A[sum].push(i)
  elif L[i] == 1:
    sum++
    A[sum].push(i)

现在A本质上是序列总和的x y图(x是列表的索引,y是总和)。每次有两个x值x1和x2到y值时,你有一个间隔(x1,x2),其中0和1的数量是相等的。

有m(m-1)/ 2(1到m-1的算术和)间隔,其中A中每个数组M的和为0,其中m = M.length

使用您的示例手动计算A我们使用此图表

L           #   0  1  0  1  0  0  1  1  1  1  0
A keys      0  -1  0 -1  0 -1 -2 -1  0  1  2  1
L index    -1   0  1  2  3  4  5  6  7  8  9  10

(我添加了#来表示列表的开头,键为-1。同时删除了所有不是0或1的数字,因为它们只是分散注意力)A看起来像这样:

[-2]->[5]
[-1]->[0, 2, 4, 6]
[0]->[-1, 1, 3, 7]
[1]->[8, 10]
[2]->[9]

对于任何M = [a1,a2,a3,...],(ai + 1,aj),其中j> 1。我将是一个与1相同数量的0的区间。例如,在[-1] - > [0,2,4,6]中,间隔为(1,2),(1,4),(1,6),(3,4),(3) ,6),(5,6)。

构建数组A是O(n),但是从A打印这些间隔必须在线性时间内完成间隔数。事实上,这可能是你的证据,即在n的线性时间内完成此操作是不可能的,因为它可能有比n更多的间隔,并且你至少需要间隔迭代次数来打印它们。

除非您认为构建A足以找到所有区间(因为它从A的区间显而易见),然后它与n:P

呈线性关系

答案 1 :(得分:2)

线性解决方案是可能的(对不起,早些时候我认为这必须是n ^ 2)如果你小心不打算实际打印结果!

首先,让我们为任意一组零和一组定义一个“得分”,即1的数量减去零的数量。所以(0,1)得分为0,而(0)为-1,(1,1)为2。

现在,从右边开始。如果最右边的数字是0,那么它可以与左边任何得分为1的组合。所以我们需要知道左边有哪些组可用,按分数索引。这表明递归过程会累积具有分数的组。扫描过程为O(n),并且在每个步骤中,过程必须检查它是否已创建新组并扩展已知组的表。检查新组是常量时间(在哈希表中查找)。扩展已知组的表也是恒定时间(起初我认为它不是,但你可以保持一个单独的偏移量,避免更新表中的每个条目)。

所以我们有一个特殊的情况:过程的每一步都确定了一组大小为O(n)的结果,但这样做所需的计算是恒定时间(在该步骤内)。因此,过程本身仍然是O(n)(与步数成比例)。当然,实际打印结果(在步骤期间或结束时)会产生O(n ^ 2)。

我会编写一些Python代码来测试/演示。

我们走了:

SCORE = [-1,1]

class Accumulator:

    def __init__(self):
        self.offset = 0
        self.groups_to_right = {} # map from score to start index
        self.even_groups = []
        self.index = 0

    def append(self, digit):
        score = SCORE[digit]
        # want existing groups at -score, to sum to zero
        # but there's an offset to correct for, so we really want
        # groups at -(score+offset)
        corrected = -(score + self.offset)
        if corrected in self.groups_to_right:
            # if this were a linked list we could save a reference
            # to the current value.  it's not, so we need to filter
            # on printing (see below)
            self.even_groups.append(
                (self.index, self.groups_to_right[corrected]))
        # this updates all the known groups
        self.offset += score
        # this adds the new one, which should be at the index so that
        # index + offset = score (so index = score - offset)
        groups = self.groups_to_right.get(score-self.offset, [])
        groups.append(self.index) 
        self.groups_to_right[score-self.offset] = groups
        # and move on
        self.index += 1
        #print self.offset
        #print self.groups_to_right
        #print self.even_groups
        #print self.index

    def dump(self):
        # printing the results does take longer, of course...
        for (end, starts) in self.even_groups:
            for start in starts:
                # this discards the extra points that were added
                # to the data after we added it to the results
                # (avoidable with linked lists)
                if start < end:
                    print (start, end)

    @staticmethod
    def run(input):
        accumulator = Accumulator()
        print input
        for digit in input:
            accumulator.append(digit)
        accumulator.dump()
        print

Accumulator.run([0,1,0,0,1,1,1,1,0])

输出:

dynamic: python dynamic.py 
[0, 1, 0, 0, 1, 1, 1, 1, 0]
(0, 1)
(1, 2)
(1, 4)
(3, 4)
(0, 5)
(2, 5)
(7, 8)

您可能会担心在显示结果的转储例程中完成一些额外的处理(start < end的过滤)。但那是因为我正在研究Python缺少链表(我希望扩展一个列表并在固定时间内保存以前的值)。

结果是 size O(n ^ 2)似乎令人惊讶,而找到结果的过程是O(n),但它很容易看看这是怎么可能的:在一个“步骤”,该过程通过关联当前点(self.index中的appendend来识别多个组(大小为O(n)) dump())中包含终点列表(self.groups_to_right[...]ends)。

更新:还有一点。 “右边的组”表将具有sqrt(n)条目的“典型宽度”(这取决于中心极限定理 - 它基本上是1D中的随机游走)。由于在每一步添加了一个条目,因此平均长度也是sqrt(n)(在sqrt(n)个bin上共享的n个值)。这意味着,如果包括打印结果,此算法的预期时间(即使用随机输入)为O(n ^ 3/2),即使最坏情况为O(n ^ 2)

答案 2 :(得分:1)

直接回答问题:

你必须构建一个超过O(N)匹配的例子:

让N采用2 ^ k的形式,输入如下:

 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1  (here, N=16)

匹配次数(其中0是起始字符):

length    #
2         N/2 
4         N/2 - 1
6         N/2 - 2
8         N/2 - 3
..
N         1

匹配总数(从0开始)是:(1 + N / 2)*(N / 2)/ 2 = N ^ 2/8 + N / 4

从1开始的匹配几乎相同,期望每个长度减少一个。

总计:(N ^ 2/8 + N / 4)* 2 - N / 2 = N ^ 2/4

答案 3 :(得分:0)

每个时间间隔至少包含一个(0,1)(1,0)的序列。因此,只需找到(0,1)(1,0)的每一次出现,然后查看是否与现有解决方案相邻,或者两个书挡元素是否形成另一种解决方案。

通过一些存储技巧,您将能够在线性时间内找到所有解决方案。枚举它们将是O(N ^ 2),但你应该能够在O(N)空间中对它们进行编码。