查找子字符串,以及一些其他条件

时间:2013-10-20 17:46:38

标签: algorithm

我给了一个看起来像这样的字符串:

1011010100

我的任务是找到子串的长度,其中空值的数量始终为< = 1的数量。这应该始终在从右到左和从左到右'扫描'子串时发生。所以在这个例子中,答案是:

10110101 => 8

我知道复杂性应该是O(n)或O(n log n),因为长度最多可以达到10 ^ 6

有什么想法吗?

3 个答案:

答案 0 :(得分:1)

这是我的算法:

从右侧开始:

 1. if you find 0 increment the value of count
 2. if you find 1 decrement the count

将这些值存储在数组中,即v[]。 e.g。

a[] = {1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1}

v[] = {0, 1, 0,-1, 0, 1, 0, 1, 2, 1, 2, 1, 0, -1}

现在问题从V i, j v[i] < v[j] i<ji=0找到了索引。

<强>证据

如果您在此处看到j=11,则v[i]=0是可能的答案,值为v[j]=1j。 这意味着,直到0我们在字符串中有一个v[i]=0额外字符,而i to j表示从0窗口大小开始,额外的1会被放置额外{{1}}。因此答案。

希望它有所帮助。如果您有疑问,请告诉我。感谢。

答案 1 :(得分:1)

O(n)解决方案实际上非常简单,通过构建&#34; height数组&#34;,表示相对于0的数量的1的数量。所以2的高度意味着比0还要多2个1。一旦在某些条件下执行某些最大性检查,我们就会迭代高度数组。

关键观察

请注意,满足条件的子数组必须在开头具有最小高度,在结尾处最大(相对于子数组,而不是整个数组)。

问题中样本的高度数组可以这样绘制,标记为:

       v
   /\/\/\
/\/      \
^

证明:

假设高度在开始时不是最小值,这意味着子阵列内部有一个高度低于开头的点。此时,0的数量应大于1.矛盾。

假设高度在末尾不是最大值,这意味着子阵列中有一个高度大于结尾的点,比如在索引j处。然后在索引j到最后有0到1(因为高度减小),所以当我们&#34;扫描&#34;从右到左的子阵列,我们会在索引j找到0到1以上的数据。矛盾。

<强>算法

现在问题可以解释为找到最长的子阵列,该子阵列以子阵列中的最高高度结束,同时保持最小值不超过开头的高度。这与klrmlr提到的maximum subarray problem非常相似(&#34;数组的连续子序列&#34;更好地说是&#34;子阵列&#34;)。而且这个想法并没有保持O(n)状态,而是保持到目前为止最大的状态&#34;并且&#34;此时最大值&#34;。

遵循该算法,下面是伪代码,遍历数组一次:

程序Balance_Left_Right

  1. 记录目前为止的最低点和最高点
  2. 如果此时的高度低于目前的最低点,则在此点之后将起点更改为索引
  3. 如果此时的高度高于或等于目前为止的最高点,那么这是一个有效的子阵列,记录长度(以及开始和结束指数,如果你愿意的话)
  4. 然而,我们很快就会发现这个测试用例的问题(正如Adam Jackson通过个人通信所指出的那样):1100101,可视化如下:

     /\
    /  \/\/
    

    正确的答案是3(最后的101),但上面的算法将得到2(前11)。这是因为我们的答案显然隐藏在一座高山之后#34; (即答案中的最低点不低于山峰,答案中的最高点不高于山峰。)

    所以我们需要确保当我们运行Procedure Balance_Left_Right(上面)时,没有&#34;高山&#34;隐藏答案。因此,解决方案是从右侧遍历数组一次,尝试将数组划分为多个部分,在每个部分中,此属性保持:&#34; 1的数量始终是&gt; =数字从0和#34;以及每个部分遍历的0,它不能再向左延伸。

    然后,在每个部分中,当从左侧移动时,将在该部分的末尾具有最大高度,这是最大值。并且可以证明,使用此属性,方法balance_left_right将为此部分找到正确的答案。所以,我们只是在每个部分调用我们的balance_left_right方法,然后在这些方法中获得最大的答案。

    现在,您可能会问,为什么在每个部分运行Balance_Left_Right足够了?这是因为答案要求属性从左侧和右侧保持,因此它必须位于其中一个部分内,因为每个部分都满足属性的一半。

    算法仍为O(n),因为我们只访问每个元素两次,一次是从右边开始,一次是从左边开始。

    最后一个测试用例将按如下方式进行分区:

     /|\ |
    / | \|/\/
    **    ***
    

    其中只有标有星号(*)的部分。

    所以新算法如下:

    程序Max_Balance_Left_Right

    1. 对输入进行分区,其中1> =的数字=右边的0(从右边使用Balance_Left,或者可以称之为Balance_right)
    2. 在每个分区上运行Balance_Left_Right
    3. 取最大值
    4. 这是Python中的代码:

      def balance_left_right(arr):
          lower = 0
          upper = -2**32
          lower_idx = 0 # Optional
          upper_idx = -1 # Optional
          result = (0,0,0)
          height = 0
          length = 0
          for idx, num in enumerate(arr):
              length += 1
              height += 1 if num==1 else -1
              if height<lower:
                  lower = height # Reset the lowest
                  upper = height # Reset the highest
                  lower_idx = idx+1 # Optional, record the starting point
                  length = 0 # Reset the answer
              if height>=upper:
                  upper = height
                  upper_idx = idx # Optional, record the end point
                  if length > result[0]: # Take maximum length
                      result = (length, lower_idx, upper_idx)
          return result
      
      def max_balance_left_right(arr):
          all_partitions = []
          start = 0
          end = len(arr)
          right_partitions = balance_left(reversed(arr[start:end]))
          for right_start, right_end in right_partitions:
              all_partitions.append((end-right_end, end-right_start))
          result = (0,0,0)
          for start, end in all_partitions:
              candidate = balance_left_right(arr[start:end])
              if result[0] < candidate[0]:
                  result = (candidate[0], candidate[1]+start, candidate[2]+start)
          return result
      
      def balance_left(arr):
          lower = 0
          start_idx = 0
          end_idx = -1
          height = 0
          result = []
          for idx, num in enumerate(arr):
              height += 1 if num==1 else -1
              if height < lower:
                  if end_idx != -1:
                      result.append((start_idx,end_idx))
                  lower = height
                  start_idx = idx+1
                  end_idx = -1
              else:
                  end_idx = idx+1
          if end_idx != -1:
              result.append((start_idx, end_idx))
          return result
      
      test_cases = [
              [1,0,1,1,0,1,0,1,0,0],
              [0,0,1,0,1,0,0,1,0,1,0,0,1,1,0,1,0,1,0,0,1],
              [1,1,1,0,0,0,1,0,0,1,1,0,1,1,0,1,1,0],
              [1,1,0,0,1,0,1,0,1,1,0,0,1,0,0],
              [1,1,0,0,1,0,1],
              [1,1,1,1,1,0,0,0,1,0,1,0,1,1,0,0,1,0,0,1,0,1,1]
              ]
      for test_case in test_cases:
          print 'Balance left right:'
          print test_case
          print balance_left_right(test_case)
          print 'Max balance left right:'
          print test_case
          print max_balance_left_right(test_case)
          print
      

      将打印:

      Balance left right:
      [1, 0, 1, 1, 0, 1, 0, 1, 0, 0]
      (8, 0, 7)
      Max balance left right:
      [1, 0, 1, 1, 0, 1, 0, 1, 0, 0]
      (8, 0, 7)
      
      Balance left right:
      [0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1]
      (6, 12, 17)
      Max balance left right:
      [0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1]
      (6, 12, 17)
      
      Balance left right:
      [1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0]
      (8, 9, 16)
      Max balance left right:
      [1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0]
      (8, 9, 16)
      
      Balance left right:
      [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0]
      (10, 0, 9)
      Max balance left right:
      [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0]
      (10, 0, 9)
      
      Balance left right:
      [1, 1, 0, 0, 1, 0, 1]
      (2, 0, 1)
      Max balance left right:
      [1, 1, 0, 0, 1, 0, 1]
      (3, 4, 6)
      
      Balance left right:
      [1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1]
      (5, 0, 4)
      Max balance left right:
      [1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1]
      (6, 8, 13)
      

      为了您的眼睛享受,测试用例的高度数组:

      First:
             v
         /\/\/\
      /\/      \
      ^
      
      Second:
      \
       \/\/\           v
            \/\/\  /\/\/\
                 \/      \/
                  ^
      
      Third:
                      v
        /\            /\
       /  \        /\/
      /    \/\  /\/
              \/
               ^
      
      Fourth:
               v
       /\      /\
      /  \/\/\/  \/\
      ^             \
      
      Fifth:
       /\   v
      /  \/\/
          ^
      
      Sixth:
          /\       v
         /  \      /\
        /    \/\/\/  \/\    /
       /      ^         \/\/
      /
      

      关于问题的澄清

      由于一些读者对OP究竟想要什么感到困惑,尽管问题已经明确说明了,但让我通过一些例子来解释这个问题。

      首先,来自问题的任务:

        

      我的任务是找到子串的长度,其中空值的数量始终为&lt; = 1的数量。这应该始终发生在扫描时#39;子串从右到左,从左到右

      这指的是&#34; Catalan Number选票问题&#34;或&#34; Available Change Problem&#34;。在Wiki中,您可以检查&#34;单调路径&#34;问题,你可以在哪里映射&#34;向右移动&#34; as&#34; 1&#34;并且&#34;向上移动&#34; as&#34; 0&#34;。

      问题是找到原始数组的子数组,这样,当从左到右和从右到左遍历子数组时,此属性成立:

        

      到目前为止看到的0的数量不应超过到目前为止看到的1的数量。

      例如,字符串1010从左到右保存属性,因为如果我们从左到右扫描数组,则总会有更多1&# 39;而不是0。但是这个属性并不是从右到左,因为从右边遇到的第一个字符是0,所以在开始时我们有更多的0(有一个)而不是1&#39; s(没有)。

      例如,由OP给出,我们看到字符串1011010100的答案是前八个字符,即:10110101。为什么呢?

      好的,所以当我们从左到右遍历子阵列时,我们发现总有比0更多的1。当我们从左到右遍历数组时,让我们检查1和0的数量:

      1: num(0) = 0, num(1) = 1
      0: num(0) = 1, num(1) = 1
      1: num(0) = 1, num(1) = 2
      1: num(0) = 1, num(1) = 3
      0: num(0) = 2, num(1) = 3
      1: num(0) = 2, num(1) = 4
      0: num(0) = 3, num(1) = 4
      1: num(0) = 3, num(1) = 5
      

      我们可以看到,在任何时间点,0的数量总是小于或等于1的数量。这就是该物业从左到右持有的原因。同样的检查可以从右到左进行。

      那么为什么不是1011010100并回答?

      让我们看看当我们从右到左遍历字符串时:

      0: num(0) = 1, num(1) = 0
      0: num(0) = 2, num(1) = 0
      1: num(0) = 2, num(1) = 1
      ...
      

      我没有进行完整的遍历,因为自第一步以来该属性已被违反,因为我们有num(0) > num(1)。这就是字符串1011010100无法满足问题约束的原因。

      你也可以看到我的&#34;身高阵列&#34;实际上是1的数量和0的数量之间的差异,即:num(1) - num(0)。因此,为了拥有财产,我们必须使[相对]高度为正。这可以通过使高度不小于初始高度来显示。

答案 2 :(得分:0)

(几乎正确,即巧妙错误)线性时间解决方案

有两个问题的回忆(一个稍后删除......)和一个滑动窗口。

编码A

您可以压缩输入以产生后续零或1的数量:

  

+1 -1 +2 -1 +1 -1 +1 -2

这会产生编码A 并需要O(n)时间。

编码B

现在,在编码A时,只要遇到两个连续数字,总计达到&gt; 0,你进一步压缩。在编码B 中,括号中的数字表示子字符串的长度:

  

+2(4)-1 +1 -1 +1 -2 ==&gt; +2(6)-1 +1 -2 ==&gt; +2(8)-2

这也需要O(n)。在这里,我们立即得到解决方案:一个长度为8的字符串,其中两个比0更多1。让我们尝试一个更复杂的实例(在编码A中给出):

  

+5 -8 +4

这里,对编码B的转换没有帮助:

  

+5(5)-8 +4(4)

还要考虑以下实例(编码B):

  

+5(9)-6 +4(4)-6 +5(7)-6 +4(6)-6 +5(9)

此序列将用于演示......

滑动窗口

首先,确定从左侧开始的最佳解决方案:

  

+5 -6 +4 -6 +5&gt; 0 ==&gt; 9 + 6 + 4 + 6 + 7 = 32

现在,扩展此项以找到从第三个位置开始的最佳解决方案(+4(4)):

  

+ 4-6 +5-6 +4&gt; 0 ==&gt; 4 + 6 + 7 + 6 + 6 = 29

这个解决方案并不比我们发现的第一个好。继续:

  

+5 -6 +4 -6 +5&gt; 0 ==&gt; 7 + 6 + 6 + 6 + 9 = 34

这是最好的解决方案。该算法可以在O(n)中实现,因为头部和尾部仅向前移动。

上面的简要描述并未涵盖所有细微之处(编码B左侧的负数,头尾相遇,......)。此外,可能不需要重新编码,滑动窗口可以直接在0-1表示上实现。但是,我只有在重新编码后才能完全理解这个问题。

摆脱编码B

实际上,正如米莉史密斯所指出的那样,“编码B”可能是有损的,这意味着它可能导致某些(尚未确定的)极端情况下的劣质解决方案。但是滑动窗口算法在编码A时也能正常工作,因此甚至可能需要跳过转换为编码B.(太懒了,不能重写算法的解释......)