在二进制字符串中查找最长子字符串,其中的子字符串不小于零

时间:2013-10-21 06:09:31

标签: algorithm language-agnostic

如何在二进制字符串中查找余额的最长子字符串,即1和0之间的差值,是> = 0?

示例:

  

01110000010 - > 6:011100

     

1110000011110000111 - > 19:整个字符串

虽然这个问题看起来与Maximum Value Contiguous Subsequence (Maximum Contiguous Sum)问题非常相似,但动态编程解决方案似乎并不明显。在分而治之的方法中,如何进行合并?毕竟是一种“高效”的算法吗? (一个简单的O(n ^ 2)算法将迭代所有可能起点的所有子串。)

这是Finding a substring, with some additional conditions的修改后的变体。不同之处在于,在链接问题中,只允许这样的子串,其中余额永远不会低于零(在向前或向后方向上查看字符串)。在给定的问题中,如果余额在稍后阶段恢复,则允许余额低于零。

5 个答案:

答案 0 :(得分:5)

我的解决方案需要O(n)额外的内存和O(n)时间。

让我们将索引h(i)的“高度”表示为

h(i) = <number of 1s in the substring 1..i> - <number of 0s in the same substring>

现在可以将问题重新表述为:查找ij,例如h(i) <= h(j)j-i -> max

显然,h(0) = 0,如果是h(n) = 0,那么解决方案就是整个字符串。

现在让我们计算数组B,以便B[x] = min{i: h(i) = -x}。换句话说,让B[x]成为i所在的最左侧索引h(i)= -x

数组B[x]的长度最多为n,并在一次线性传递中计算。

现在我们可以迭代原始字符串,并为每个索引i计算最长序列的长度,其中非负余额以i结尾,如下所示:

Lmax(i) = i - B[MIN{0, h(i)}]

所有Lmax(i)中最大的i将为您提供所需的长度。

我将证据留作练习:)如果你无法弄明白,请联系我。

此外,我的算法需要2次传递原始字符串,但您可以将它们合并为一个。

答案 1 :(得分:3)

这可以在O(n)中使用“高度数组”轻松回答,表示相对于0的数字的1的数量。与链接问题中的my answer一样。

现在,我们不再专注于原始数组,而是专注于两个索引高度的数组,一个将包含最小的索引,如找到高度,另一个将包含最大的索引这样的高度被找到。由于我们不想要负指数,我们可以将所有内容都移开,这样最小高度为0。

因此对于示例案例(我在最后添加了两个1以表明我的观点):

1110000011010000011111
Array height visualization
  /\
 /  \
/    \
      \  /\/\        /
       \/    \      /
              \    /
               \  /
                \/
(lowest height = -5)
Shifted height array:
[5, 6, 7, 8, 7, 6, 5, 4, 3, 4, 5, 4, 5, 4, 3, 2, 1, 0, 1, 2, 3]
     Height:   0  1  2  3  4  5  6  7  8
first_view = [17,16,15, 8, 7, 0, 1, 2, 3]
last_view  = [17,18,19,20,21,22, 5, 4, 3]

请注意,我们有22个数字和23个不同的索引,0-22,代表数字之间的23个空格和填充数字

我们可以在first_view中构建last_viewO(n)数组。

现在,对于first_view中的每个高度,我们只需要检查last_view中的每个更高的高度,并获取与first_view索引最大差异的索引。例如,从高度0开始,较大高度的索引的最大值为22.因此,从索引17 + 1开始的最长子串将在索引22处结束。

要查找last_view数组的最大索引,您可以将其转换为O(n)中右侧的最大值:

last_view_max = [22,22,22,22,22,22, 5, 4, 3]

因此,找到答案只是从first_view

中减去last_view_max
first_view    = [17,16,15, 8, 7, 0, 1, 2, 3]
last_view_max = [22,22,22,22,22,22, 5, 4, 3]
result        = [ 5, 6, 7,14,15,22, 4, 2, 0]

并且从起始索引0到结束索引22(即整个字符串)获得最大值(再次在O(n)中),即22。 = d

正确性证明:

假设最大子字符串从索引i开始,以索引j结束。 如果索引i的高度与索引k<i的高度相同,那么k..j将是一个更长的子字符串,仍然满足要求。因此,考虑每个高度的第一个指数就足够了。类似于最后一个指数。

答案 2 :(得分:0)

压缩二次运行时

我们将从头开始寻找(本地)最长的子串,余额为零。我们将忽略零的字符串。 (转角情况:全零 - &gt;空字符串,余额永远不会再达到零 - &gt;整个字符串。)在这些余额为零的子字符串中,将删除所有尾随零。

由B表示具有余额的子串&gt; 0和Z是一个只有零的子串。每个输入字符串可以如下分解(伪正则表达式):

  

乙? (Z B)* Z?

每个B都是最大可行解决方案,这意味着它不能在不减少平衡的情况下向任一方向扩展。但是,如果在折叠后余额仍然大于零,则可能会折叠BZB或ZBZ的序列。

请注意,如果ZBZ部分具有平衡&gt; = 0,则总是可以将BZBZB的序列折叠为单个B.(可以在线性时间内一次完成。)一旦所有这些序列都被折叠,每个ZBZ部分的平衡低于零。尽管如此,仍有可能存在平衡度大于零的BZB部分 - 即使在平衡值低于零的BZBZB序列中,前导和尾随BZB部分的平衡均为零。在这一点上,似乎很难决定哪个BZB崩溃。

仍然是二次方......

无论如何,通过这种简化的数据结构,人们可以尝试所有的Bs作为起点(如果还有余额,可能会向左延伸)。运行时间仍然是二次方,但(实际上)的n值要小得多。

答案 3 :(得分:0)

分而治之

另一个经典。应该在O(n log n)中运行,但很难实现。

最长的可行子串位于左半部分,右半部分,或者经过边界。为两半调用算法。对于边界:

假设问题大小为n。对于跨越边界的最长可行子字符串,我们将计算子字符串左半部分的余额。

确定,每个可能的余额在-n / 2和n / 2之间,在左半部分,最长字符串的长度在边界处结束并具有此(或更大)平衡。 (线性时间!)对于从边界开始的右半部分和最长的字符串执行相同的操作。结果是两个大小为n + 1的数组;我们反转其中一个,按元素添加它们并找到最大值。 (再次,线性。)

为什么会起作用?

跨越边界的余额> gt = 0的子字符串可以具有余额&lt;如果另一部分对此进行补偿,则左侧或右侧部分为0。 (“借款”余额。)关键问题是借款多少;我们迭代所有潜在的“平衡信用”并找到最佳权衡。

为什么这是O(n log n)?

因为合并(查看边界交叉串)只需要线性时间。

为什么合并O(n)?

练习留给读者。

答案 4 :(得分:0)

动态编程 - 线性运行时间(最后!)

受到this blog post的启发。简单而有效,一次通过online algorithm,但需要一些时间来解释。

上面的链接显示了一个不同的问题:最大子序列总和。它不能以1:1的方式映射到给定的问题,这里需要O(n)的“状态”,与原始问题的O(1)相反。不过,状态可以在O(1)中更新。

让我们重新解释一下这个问题。我们正在寻找输入中最长的子字符串,其中余额,即01之间的差异大于零。

状态类似于我的另一个分而治之的解决方案:我们针对每个可能的余额 {{1}计算每个职位i 最长字符串的起始位置b,余额s(i, b)或更高,结束位置b 。也就是说,从索引i开始到s(i, b) + 1结束的字符串的余额为i或更高,并且不再有以b结尾的字符串。 我们通过最大化i来找到结果。

算法

当然,我们不会将所有i - s(i, 0)保留在内存中,只保留当前s(i, b)(我们对输入进行迭代)的内容。我们从i的{​​{1}}和s(0, b) := 0的{​​{1}}开始。对于每个b <= 0,我们会使用以下规则进行更新:

  1. 如果读取:= undefinedb > 0
  2. 如果读取i1如果已定义,则s(i, b) := s(i - 1, b - 1)如果0未定义。
  3. 函数s(i, b) := s(i - 1, b + 1)(对于当前s(i, 0) := i)可以实现为指向长度为s(i - 1, 1)的数组的指针;根据输入,此指针向前或向后移动。在每次迭代中,我们都会注意到s的价值。

    它是如何运作的?

    状态函数i生效,尤其是从开始到2n + 1的余额为负数时。对于尚未读取的所有可能s(i, 0)个数,它记录了达到零余额的最早起点。

    为什么会起作用?

    因为状态函数的递归定义等同于其直接定义 - 最长字符串的起始位置,其余为s或更大,以位置i结束。

    为什么递归定义是正确的?

    通过归纳证明。