【问题标题】:Finding the maximum subsequence binary sets that have an equal number of 1s and 0s查找具有相同数量的 1 和 0 的最大子序列二进制集
【发布时间】:2010-06-29 11:12:10
【问题描述】:

我在互联网上发现了以下问题,想知道我将如何解决它:

给你一个包含 0 和 1 的数组 '。求O(n)时间和O(1)空间算法,求1和0个数相等的最大子序列。

例子:

  1. 10101010 - 满足问题的最长子序列就是输入本身
  2. 1101000 - 满足问题的最长子序列是110100

【问题讨论】:

  • 这是家庭作业吗?
  • “寻找一种算法”的意思是“用你的大脑来创造一个”,而不是“用谷歌搜索”。
  • 这实际上是一个棘手的问题......
  • 如果你在网上找到的,你可以给个链接,不是吗?
  • 某人有兴趣解决一个小问题并不一定意味着这是家庭作业。

标签: c algorithm language-agnostic


【解决方案1】:

更新

我必须完全改写我的答案。 (如果你对早期版本投了赞成票,那你就被骗了!)

让我们再次总结一下这个简单的案例,把它排除在外:

找到最长的前缀包含的位串 相同数量的 1 和 0 数组。

这很简单:需要一个简单的计数器,计算我们拥有的 1 比 0 多多少,并在维护这一点的同时迭代位串。该计数器最后一次变为零的位置是最长搜索前缀的结尾。 O(N) 时间,O(1) 空间。 (我现在完全相信这是最初的问题所要求的。)

现在让我们切换到更困难的版本:我们不再要求子序列是前缀——它们可以从任何地方开始。

经过反复思考,我认为可能没有线性算法。例如,考虑前缀“111111111111111111...”。其中每一个 1 都可能是最长子序列的开始,没有候选子序列开始位置占主导地位(即总是给出比任何其他位置更好的解决方案),所以我们不能丢弃它们中的任何一个(O(N)空间)并且在任何步骤中,我们必须能够在 O(1) 时间内从线性众多的候选者中选择最佳开始(与当前位置具有相同数量的 1 和 0) . 事实证明这是可行的,而且也很容易实现,因为我们可以根据 1s (+1) 和 0s (-1) 的运行和来选择候选者,它的大小最多为 N,我们可以将我们达到每个总和的第一个位置存储在 2N 个单元格中 - 请参阅下面 pmod 的答案(yellowfog 的 cmets 和几何洞察力)。

没有发现这个技巧,我用一个缓慢但可靠的算法替换了一个快速但错误的算法,(因为正确的算法比错误的算法更可取!):

  • 构建一个数组A,从开始到该位置累积的 1 数,例如如果位串为“001001001”,则数组将为 [0, 0, 1, 1, 1, 2, 2, 2, 3]。使用它,我们可以在 O(1) 中测试子序列 (i,j) 是否有效:isValid(i, j) = (j - i + 1 == 2 * (A[j] - A[i - 1]),即如果它的长度是其中 1 数量的两倍,则它是有效的。例如,子序列 (3,6) 是有效的,因为 6 - 3 + 1 == 2 * A[6] - A[2] = 4。
  • 普通的旧双循环:

    maxSubsLength = 0 对于 i = 1 到 N - 1 对于 j = i + 1 到 N if isValid(i, j) ... #maintain maxSubsLength 结尾 结束

这可以通过跳过比当前 maxSubsLength 短的 i/j 序列来使用一些分支定界来加快速度,但渐近地说这仍然是 O(n^2)。慢,但有一个很大的优势:正确!

【讨论】:

  • 我必须承认我也对“家庭作业”cmets 感到厌烦 :) 您基于堆栈的解决方案非常有趣。
【解决方案2】:

严格来说,答案是不存在这样的算法,因为由相等数量的 0 和 1 组成的字符串语言不规则。

当然,每个人都忽略了这样一个事实,即在空间中存储一个大小为n 的整数是O(log n),并将其视为空间中的O(1)。 :-) 几乎所有的大 O,包括时间的,都充满(或者说没有)缺少 log n 因素,或者等效地,他们假设 n 受机器字大小的限制,这意味着你'真的在看一个有限的问题,一切都是O(1)

【讨论】:

  • 好,关于不可能的问题。还要注意我对下推自动机的回答与下推自动机的相似之处(例如,自动机操作堆栈、查看堆栈、推送和弹出符号)。
  • 您能否详细说明“不存在这样的算法”假设?听起来你可以证明这一点。如果是这样,我会对证明感兴趣。
  • Wikipedia's page on the pumping lemma (see en.wikipedia.org/wiki/Pumping_lemma_for_regular_languages) 证明 0^p1^p 是一个字符串,它不能正确地用于语言 L=0^p1^p。它也可以用来表明 OP 描述的语言也不是规则的。至于为什么这很重要,请记住,正则语言可以用等价的有限自动机来表示;有限自动机没有超出维持状态所需的最低限度的记忆。问题是空间上的 O(1),但解析非正则语言在空间上不一定是 O(n)。
  • 嗨,亚当。正如 Platinum Azure 所说,Pumping Lemma 可以用来证明语言不规则。此外,任何可以在常数空间中识别的语言都是有规律的,这是一个定理,所以如果你假设有一个常数空间算法,那么你就遇到了矛盾。
【解决方案3】:

新解决方案: 假设我们有 n 位输入位数组 2*n 大小的数组来保持位的位置。因此,数组元素的大小必须有足够的大小来保持最大位置数。对于 256 输入位数组,需要 256x2 字节数组(字节足以保持 255 - 最大位置)。

从位数组的第一个位置移动,我们使用规则将位置从数组的中间(索引为n)开始放入数组中:

1.如果我们传递“1”位,则增加位置,当传递“0”位时减少位置

2。当遇到已经初始化的数组元素时 - 不要改变它并记住位置之间的差异(从数组元素中获取的当前减去) - 这是局部最大序列的大小。

3.每次我们遇到局部最大值时,将其与全局最大值进行比较,如果后者更小,则更新。

例如:位序列为0,0,0,1,0,1

   initial array index is n
   set arr[n] = 0 (position)
     bit 0 -> index--
   set arr[n-1] = 1 
     bit 0 -> index--
   set arr[n-2] = 2
     bit 0 -> index--
   set arr[n-3] = 3
     bit 1 -> index++
   arr[n-2] already contains 2 -> thus, local max seq is [3,2] becomes abs. maximum
      will not overwrite arr[n-2]
     bit 0 -> index--
   arr[n-3] already contains 3 -> thus, local max seq is [4,3] is not abs. maximum
     bit 1 -> index++
   arr[n-2] already contains 2 -> thus, local max seq is [5,2] is abs. max

因此,我们只通过了整个位数组一次。 这能解决任务吗?

input:
    n - number of bits
    a[n] - input bit-array

track_pos[2*n] = {0,};
ind = n;
/* start from position 1 since zero has
  meaning track_pos[x] is not initialized */
for (i = 1; i < n+1; i++) {
    if (track_pos[ind]) {
        seq_size = i - track_pos[ind];
        if (glob_seq_size < seq_size) {
            /* store as interm. result */
            glob_seq_size = seq_size;
            glob_pos_from = track_pos[ind];
            glob_pos_to   = i;
        }
    } else {
        track_pos[ind] = i;
    }

    if (a[i-1])
        ind++;
    else
        ind--;
}

output:
    glob_seq_size - length of maximum sequence
    glob_pos_from - start position of max sequence
    glob_pos_to   - end position of max sequence

【讨论】:

  • 这会找到数组中最长的 prefix 等于 1s 和 0s。也许这种特殊情况是寻求的答案。请参阅我的答案,了解从哪里开始找到最大子序列的方法。
  • 其实又找到了一个,还没和你比。
  • 嗯,你正在使用的这个额外的数组,从中间开始,当你看到 1 时“向右”,当你看到 0 时向“左”,基本上是一个堆;我在回答中提到的是双堆栈。在我的情况下,它包含 1 或 0,因为在您的情况下,它要么在右侧(1),要么在左侧(0)。你说“当你遇到已经初始化的时候......”——这只发生在你向中间移动时——pop()/match!您放入数组中的数字类似于我所说的 leftSeqIndex 是我的伪代码。因此,您描述了类似的内容,但缺少其他部分。
  • 对空间没有要求,对计算复杂度有要求。而且您的堆栈推送/弹出操作比数组操作要多得多。
  • 昨晚我在思考这个问题,得出了以下结论。如果您绘制一个位图,其中您增加 1 并减少 -1,那么我们正在寻找的是我们可以在相同“高度”的两个点之间绘制的最大线。我认为您正在找到那条线。
【解决方案4】:

在此线程 (http://discuss.techinterview.org/default.asp?interview.11.792102.31) 中,发帖人 A.F. 给出了一种算法,该算法在 O(n) 时间内运行并使用 O(sqrt(n log n)) 位。

【讨论】:

    【解决方案5】:

    蛮力:从数组的最大长度开始计算 o 和 l。如果 o 等于 l,你就完了。否则将搜索长度减少 1 并对减少长度的所有子序列(即最大长度减去减少长度)执行算法,依此类推。当减法为 0 时停止。

    【讨论】:

    • 听起来像二次时间复杂度
    • 是的,但也许这是他达成更好解决方案的一个很好的起点?
    【解决方案6】:

    正如用户“R..”所指出的,严格来说,没有解决方案,除非您忽略“log n”空间复杂度。在下文中,我将考虑数组长度适合机器寄存器(例如 64 位字)并且机器寄存器的大小为 O(1)。

    需要注意的重要一点是,如果 1 多于 0,那么您要查找的最大子序列必然包括所有 0 和那么多 1。所以这里的算法:

    符号:数组长度n,索引从0到n-1

    1. 第一遍:计算 1 (c1) 和 0 (c0) 的数量。如果 c1 = c0 那么你的最大子序列是整个数组(算法结束)。否则,令 d 为出现频率较低的数字(d = 0 如果 c0 ,否则 d = 1)。
    2. 计算 m = min(c0, c1) * 2。这是您要查找的子序列的大小。
    3. 第二遍:扫描数组以找到第一次出现d的索引j
    4. 计算k = max(j, n - m)。子序列从索引 k 开始,长度为 m

    请注意,可能有多种解决方案(符合标准的多个最大长度子序列)。

    简单来说:假设 1 比 0 多,那么我考虑包含所有 0 的最小子序列。根据定义,该子序列被一串 1 包围。所以我只是从侧面抓住足够的 1。

    编辑:正如所指出的,这不起作用......“重点”实际上是错误的。

    【讨论】:

    • 第 1 步没问题。之后,找到第一次出现的 d。从那里处理位串,保持一个计数器初始化为 1 并在遇到 d 时递增,并在遇到 1-d 时递减。每当计数器达到 0 时,您就有一个具有相同数量的 0 和 1 的子序列。存储当前位置并继续处理。当您到达位串的末尾时,存储的最后一个位置将给出一个最大的子序列。
    • 第 2 步不能产生正确的结果,除非您解释子序列不需要是连续的。 Nabb 的反例是使这个逻辑无效的最简单的例子——这决定了子序列的长度为 4,但你需要扔掉一个零,从而破坏链。
    • @Nate,你是对的,错过了那个案子。如果你在位串的末尾没有得到相等数量的 0 和 1,你需要从头开始备份你离开的位置数。
    【解决方案7】:

    试试这样的:

    /* bit(n) is a macro that returns the nth bit, 0 or 1. len is number of bits */
    int c[2] = {0,0};
    int d, i, a, b, p;
    for(i=0; i<len; i++) c[bit(i)]++;
    d = c[1] < c[0];
    if (c[d] == 0) return; /* all bits identical; fail */
    for(i=0; bit(i)!=d; i++);
    a = b = i;
    for(p=0; i<len; i++) {
      p += 2*bit(i)-1;
      if (!p) b = i;
    }
    if (a == b) { /* account for case where we need bits before the first d */
      b = len - 1;
      a -= abs(p);
    }
    printf("maximal subsequence consists of bits %d through %d\n", a, b);
    

    完全未经测试但模数愚蠢的错误应该可以工作。根据我对 Thomas 的回答在某些情况下失败的回复。

    【讨论】:

      【解决方案8】:

      新解决方案: 空间复杂度O(1),时间复杂度O(n^2)

              int iStart = 0, iEnd = 0;
              int[] arrInput = { 1, 0, 1, 1, 1,0,0,1,0,1,0,0 };
      
              for (int i = 0; i < arrInput.Length; i++)
              {
                  int iCurrEndIndex = i;
                  int iSum = 0;
                  for (int j = i; j < arrInput.Length; j++)
                  {                    
                      iSum = (arrInput[j] == 1) ? iSum+1 : iSum-1;
                      if (iSum == 0)
                      {
                          iCurrEndIndex = j;
                      }
      
                  }
                  if ((iEnd - iStart) < (iCurrEndIndex - i))
                  {
                      iEnd = iCurrEndIndex;
                      iStart = i;
                  }
              }
      

      【讨论】:

      • 要求是:O(n)时间
      【解决方案9】:

      我不确定你所指的数组是0和1的int数组还是bitarray??

      如果是关于位数组,这是我的方法:

      int isEvenBitCount(int n)
      {
          //n ... //Decimal equivalent of the input binary sequence
          int cnt1 = 0, cnt0 = 0;
          while(n){
              if(n&0x01) { printf("1 "); cnt1++;}
              else { printf("0 "); cnt0++; }
              n = n>>1;
          }
          printf("\n");
          return cnt0 == cnt1;
      }
      
      int main()
      {
          int i = 40, j = 25, k = 35;
      
          isEvenBitCount(i)?printf("-->Yes\n"):printf("-->No\n");
          isEvenBitCount(j)?printf("-->Yes\n"):printf("-->No\n");
          isEvenBitCount(k)?printf("-->Yes\n"):printf("-->No\n");
      }
      

      使用按位运算,时间复杂度也几乎为 O(1)。

      【讨论】:

      • 如果我们必须在十进制数的二进制中也考虑前导 0,它会变得更简单...total_bits = sizeof(n); 然后 while 循环仅计算设置位数 while(n) { cnt1++; n=n&amp;(n-1); }return ((total_bits-cnt1) == cnt1);
      • 我不明白它是如何解决任务的,如何从位数组中获取具有相等零和一的最长子序列,例如1,1,1,0,1,1,1,0,0,0?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-15
      • 1970-01-01
      • 2022-01-21
      • 1970-01-01
      • 2011-04-17
      • 1970-01-01
      • 2021-10-20
      相关资源
      最近更新 更多