【问题标题】:Binary String permutations二进制字符串排列
【发布时间】:2011-10-19 03:45:41
【问题描述】:

我在http://www.interviewstreet.com 遇到了一个问题。

Bob 收到了 Alice 发送的长度为 N 的二进制字符串。他知道由于传输错误,最多可能有 K 位被损坏(并因此被翻转)。但是,他也知道 Alice 打算传输的字符串不是周期性的。如果一个字符串不能被表示为一个较小的字符串连接了一些次数,那么它就不是周期性的。例如,“0001”、“0110”不是周期性的,而“00000”、“010101”是周期性字符串。 现在他想知道 Alice 可以传输多少个可能的字符串。

所以首先我用二项式定理做了一些测试,通过使用它,我能够找到在给定一个字符串和一些损坏位的情况下可以用多少种不同的方式来表示一个字符串。我的第二步是找到一种方法来查找周期性字符串的数量。我看到这可以很容易地用素数长度的字符串来完成。这是通过检查是否有足够的 0 或 1 以仅用 0 或 1 填充字符串来完成的。

1111111 或 0000000

现在我使用一种纯粹的蛮力算法,当涉及到任何类型的大字符串时,它不会削减它。是否有人可以指出我可以帮助解决此问题的任何组合数学技术?谢谢。

【问题讨论】:

  • 我已经解决了这个问题并提交了。但是我的解决方案在 2 个带有“时间超出”错误的测试用例后失败。考虑一个案例,字符串 len = 54 和损坏的位 = 6,您将生成的总字符串组合将是:25,827,165,您必须在不到 3 秒的时间内处理所有这些,而无需按原样存储任何地方。因为你只有 32mb 空间。所以,Lior Kogan 的方案绝对不可行。

标签: algorithm permutation


【解决方案1】:

Lior 走在了正确的轨道上。

长度为N的字符串总数为2^N。其中一些是周期性的。其他人不是。我们将周期字符串的数量称为A(N),将非周期字符串的数量称为B(N)。那么

A(N) + B(N) = 2^N

如果我们定义长度为 1 的字符串是非周期性的,那么

A(1) = 0
B(1) = 2

现在假设N > 1。那么长度为N 的周期字符串集合包括周期短于N 的周期字符串。但是,对于长度为N 的非周期字符串集,情况并非如此。

长度为N的周期字符串集由长度为n的除数的非周期字符串的重复组成,包括长度为1的字符串。换句话说:

A(N) = sum(B(k) where k divides N and k < N)

例如:

A(6) = B(1) + B(2) + B(3)
     = (2^1 - A(1)) + (2^2 - A(2)) + (2^3 - A(3))
     = 2 + (4 - B(1)) + (8 - B(1))
     = 2 + 2 + 6
     = 10

所以我们现在有一个周期和非周期长度为N 的字符串数量的递归方程。

很遗憾,这对我们回答实际问题没有太大帮助。

这个问题暗示 Bob 收到了一个特定的字符串,他想知道有多少非周期性字符串与该字符串最多相差 K 位。接收到的字符串有C(N,K) 可能的突变,可能是传输的字符串。我们需要从中减去该集合中周期性字符串的数量。我们该怎么办?

首先,我们可以观察到任何周期性字符串都是非周期性字符串的重复。因此,对于每个潜在周期 kN 的除数),我们查看长度为 k 的子串。如果所有字符串与公共字符串的差异不超过K 位的组合,那么这个公共字符串是周期性字符串的基础,我们应该将计数减一。如果最小距离是dK - d &gt; N/k,那么我们可以翻转每个子字符串中的各个位并且仍然有匹配,我们必须相应地减少我们的计数。

【讨论】:

    【解决方案2】:

    计算长度为n的非周期字符串的个数:

    • 字符串总数:2ⁿ
    • 减去长度为n的周期性字符串的数量

    统计长度为n的周期性字符串个数:

    • 找出 n 的所有除数,除了 n 本身。例如:如果 n=6 - 除数是 1,2,3。

      (这个方法已经讨论过here

    • 每个除数 m 可用于表示 2^m 个周期字符串。例如

    • m=1:{0,1} - 2^1 个周期性字符串
    • m=2:{00,01,10,11} - 2^2 个周期性字符串
    • m=3: {000,...111} - 2^3 个周期性字符串

      所以对于 n=6,有 2+4+8 个周期串

      正如 Jeffery Sax 和 ANeves 所指出的,其中一些周期性字符串是相同的(例如 0* = 00* = 000*),因此我们必须消除这些。

      一种简单的方法是将所有这些字符串添加到存储唯一元素的关联容器(例如 C++ 中的set),并计算该容器中的元素数量。

      更好的优化是:对于 m=m1,找到 m1 的所有除数,并避免添加这些集合中已有字符串的周期性字符串。

    下一步是计算任何这些周期性字符串与接收到的字符串之间的Hamming distance。如果它小于 K- 计算它。


    编辑:大 N 小 K 的更好解决方案

    检查字符串是否周期性的算法:

    这可以通过将字符串与其自身的移位版本进行比较来完成。如果一个字符串与它的 p 位循环移位相同 - 那么它的循环为 p。

    因此,一次循环地移动字符串一位 - 我们可以检测它是否是周期性的,直到 floor(N/2) 个字符串比较。

    计算可能传输的字符串

    如果没有非周期性传输要求,并且我们收到了 N 位消息 - 可以传输的可能消息数为 C(N, 0) + C(N, 1) + C(N, 2) + ... + C(N, K)

    对于 N=1000 和 K=3:C(1000,0)+C(1000,1)+C(1000,2)+C(1000,3)= 166,667,501

    (这是在原始字符串中切换0/1/2/3位的组合数)。

    根据这个数字,我们需要减少无法传输的周期性字符串的数量。

    例如:如果接收到的字符串是 000000 并且 K=2,我们可以确定传输的字符串不在 {000000,001001,010010,100100} 中。这些都是周期性的,与接收到的字符串之间的汉明距离最大为 K。

    C(6,0)+C(6,1)+C(6,2)=1+6+15=22 其中,有 4 种组合是周期性的。

    算法:

    我们将从收到的字符串开始,并生成上述所有组合。对于每个组合,我们将检查它是否是周期性的。如果是这样 - 我们会将计数减少 1。

    【讨论】:

    • 您不止一次地计算一些周期性字符串。在您的示例中,您将全 0 和全 1 计算 3 次。
    • @Jeffrey Sax:正确。谢谢你。我会解决这个问题。
    • 我相信m=2 包含m=1 中的所有内容。所以对于n=6,应该有8个周期字符串。
    • 这可行,但上限是 n=1000 所以 m=500 即 2^500 并且生成和比较这么多字符串需要数年...
    • 在提问者的上下文中这不是一个可行的算法,因为我们需要在
    【解决方案3】:

    Lior 和 Jeffrey 的答案构成了解决问题的基础,但本文中还有一个有趣的问题有待解决,如何有效地计算给定 [input string, N, K] 的周期性字符串的数量。我的回答将主要集中在这一点上。

    正如 Lior 和 Jeffrey 所指出的,在检查周期性字符串时,我们只需要关心长度等于 n 的除数的子字符串。让我们看一个例子,看看我们能如何高效地实现这一目标。

    周期为 m 的周期字符串数

    设输入字符串为

    0110 0011 0101 0001
    

    让我们尝试找出周期为 m=4 的周期字符串的数量

    第一位

    如果我们比较每个子字符串的第一位,我们会发现它们都是0s。如果我们假设在所有子字符串中所有后续位都相同,那么当执行 0 位翻转或 4 位翻转时,输入字符串可以是周期性的(周期为 4)。

    0110 0011 0101 0001
    ^    ^    ^    ^
    Number of 0s = 4
    Number of 1s = 0
    Number of bitflips to make all 0s to 1s = 4
    Number of bitflips to make all 1s to 0s = 0
    
    
    Number of periodic strings with period=4 for:
    k = 0  =>  1
    k = 4  =>  1
    

    所以现在我们知道存在 2 个周期性字符串,一个用于 k=0,另一个用于 k=4(假设所有子字符串中的后续位都相同)。

    第二位

    现在让我们进入第二个部分。

    0110 0011 0101 0001
     ^    ^    ^    ^
    Number of 0s = 2
    Number of 1s = 2
    Number of bitflips to make all 0s to 1s = 2
    Number of bitflips to make all 1s to 0s = 2
    

    但是等等,上面的陈述是正确的,如果子字符串中当前位之前的所有位也有助于使字符串周期性。我们知道只有k=0k=4,每个都使字符串周期性地达到第一位。

    所以当考虑到第 2 位之前的所有位时,我们可以在以下 4 种情况下得到一个周期性字符串:

    When previousK = 0:
        Flip the 2 `0`s to `1`s => new k = 2
        Flip the 2 `1`s to `0`s => new k = 2
    When previousK = 4:
        Flip the 2 `0`s to `1`s => new k = 6
        Flip the 2 `1`s to `0`s => new k = 6
    
    Number of periodic strings with period=4 for:
    k = 2  =>  2
    k = 6  =>  2
    

    第三位

    转到第三位,我们将看到:

    0110 0011 0101 0001
      ^    ^    ^    ^
    Number of 0s = 2
    Number of 1s = 2
    Number of bitflips to make all 0s to 1s = 2
    Number of bitflips to make all 1s to 0s = 2
    
    We can get a periodic string in the following 4 cases:
    When previousK = 2:
        Flip the 2 `0`s to `1`s => new k = 4
        Flip the 2 `1`s to `0`s => new k = 4
    When previousK = 6:
        Flip the 2 `0`s to `1`s => new k = 8
        Flip the 2 `1`s to `0`s => new k = 8
    
    Number of periodic strings with period=4 for:
    k = 4  =>  4
    k = 8  =>  4
    

    第四位

    对于我们的第四个也是最后一点:

    0110 0011 0101 0001
       ^    ^    ^    ^
    Number of 0s = 1
    Number of 1s = 3
    
    We can get a periodic string in the following 4 cases:
    When previousK = 4:
        Flip the 1 `0`s to `1`s => new k = 5
        Flip the 3 `1`s to `0`s => new k = 7
    When previousK = 8:
        Flip the 1 `0`s to `1`s => new k = 9
        Flip the 3 `1`s to `0`s => new k = 11
    
    Number of periodic strings with period=4 for:
    k = 5  =>  4
    k = 7  =>  4
    k = 9  =>  4
    k = 11 =>  4
    

    我们现在完成了子字符串的最后一位,最后一步中各种 k 值的周期字符串的总和为 16。

    递归关系和伪代码

    让我们使用 R[k] 表示任何 k 的周期字符串的当前计数,它从 1K 不等。对于每次迭代,我们都需要查找上一次迭代的R[] 值。

    我们在每次迭代中最终要做的事情是:

    for offset = 0 to periodLen - 1
        flip R[] and previousR[]
    
        for currentK = 1 to K
            R[currentK] = 0
    
        numZeroes = 0
        for (pos = offset; pos < n; pos += periodLen)
            if (str[pos] == '0')
                ++numZeros
    
        numOnes = (n / m) - numZeroes;
    
        for currentK = 1 to K
            if m == 0
                R[currentK + numZeroes] = 1
                R[currentK + numOnes] = 1
            else if (previousR[currentK] > 0)
                R[currrentK + numZeroes] += previousR[currentK]
                R[currentK + numOnes] += previousR[currentK]
    
        totalPeriodicCount = 0
        for currentK = 1 to K
            totalPeriodicCount += R[currentK]
    

    如果我们通过从最低到最高迭代所有周期来执行上述过程,我们将获得所有周期字符串的计数。将选择的周期将是N 的除数,它们小于N。将它们从低到高遍历会有优势,请阅读下一节了解详细信息。

    计算可被较小期间整除的期间

    仔细观察,您会发现我们还不止一次地计算某些周期性字符串。

    例如。以下周期性字符串:

    0001 0001 0001 0001
    

    最终将被计算为 m = 4 和 m = 8 的一部分

    C[m] 表示使用上述伪代码获得的一段长度为m 的周期字符串的总数。令C[m'] 表示使用长度为m 的周期获得的周期字符串的实际计数,但不计算可以使用periods &lt; m 形成的周期字符串

    更具体地说,如果当前周期 m 的除数 tuv 小于 m,那么我们将计算周期字符串的数量 t , uv 也一样,即

    C[m] = C[t'] + C[u'] + C[v'] + C[m']
    

    在计算所有m值的周期字符串总数时,我们需要注意排除C[t]C[u]C[v],只考虑C[m']

    由于我们在计算C[m] 时,我们已经计算了C[t']C[u']C[v'] 的值,我们只需查找它们并从C[m] 中减去它们即可得到@ 987654362@。我将把这个简单的部分作为练习留给读者。

    C[m'] = C[m] - C[t'] - C[u'] - C[v']
    

    【讨论】:

      【解决方案4】:
      checkperiodic(char [] string, int i, int length)
      {
      for(l=i; l <=length-i ; l=l+i)
      if(strncmp(string, &string[l],i) != 0)
          return 0;
      return 1;
      }
      
      N=strlen(string);
      n= sqrt(N);
      for(i=2;i <= n; i++)
      {
      if (N%i == 0)
          if(checkperiodic(string,i,N) || checkperiodic(string,N/i,N))
              break;
      }
      

      复杂度 N^1/2 * N * (除数之和)

      【讨论】:

        【解决方案5】:

        扩展 Lior Kogan 的回答 Binary String permutations

        令 F(n) 为给定长度 n 可形成的非周期串的数量。

        要找到 n 的非周期串数 F(n),我们需要从 n 的所有排列中消除所有 F(m),其中 m 是 n 的除数。

        | n |     m | F(m)                     | answer |
        | 1 |       | 2^1                      |      2 |
        | 2 |     1 | 2^2-F(1)                 |      2 |
        | 3 |     1 | 2^3-F(1)                 |      6 |
        | 4 |   1,2 | 2^4-F(2)-F(1)            |     12 |
        | 5 |     1 | 2^5 - F(1)               |     30 |
        | 6 | 1,2,3 | 2^6 - F(1) - F(2) - F(3) |     54 |
        

        朴素的python实现

        nperiodics = [0, 2] # for n = 1(0,1) and n = 2(01, 10)
        
        def divisors(n):
            for d in xrange(1, n / 2 + 1):
                if n %  d == 0:
                    yield d
        
        def periodic(n, counts):
            return 2**n - sum(counts[d] for d in divisors(n))
        
        
        for i in xrange(2, 10):
            nperiodics.append(periodic(i, nperiodics))
        
        print nperiodics
        

        输出:[0, 2, 2, 6, 12, 30, 54, 126, 240, 504]

        【讨论】:

          【解决方案6】:

          我使用以下非常基本的算法解决了这个问题。希望我解决的问题是您所问问题的答案。

          如果我在一个字符串中有 8 个二进制字符,并且我想获得它的所有可能排列,那么以下算法将正确地为您提供这些值。在其中,我特别跳过了排列“00000000”,因为它对我没有任何价值:)。

          下面的代码是用 Ruby 编写的:

          size = 8
          max_binary_value = (2 ** size) - 1  # In this case, 255
          
          permutations = 1.upto(max_binary_value).map do |num|
            # This will zero-padd the string, ie: "%08d" % "10" # => "00000010",
            #   while to_s(2) will convert it to Base 2 in String form:
            "%0#{size}d" % num.to_s(2)
          end
          

          【讨论】:

            猜你喜欢
            • 2011-07-11
            • 1970-01-01
            • 1970-01-01
            • 2021-11-09
            • 2017-03-22
            • 2016-02-26
            • 2012-08-27
            • 2015-11-21
            • 1970-01-01
            相关资源
            最近更新 更多