【问题标题】:Find number of binary numbers with certain constraints查找具有特定约束的二进制数
【发布时间】:2013-05-17 02:15:24
【问题描述】:

这更像是一个谜题而不是编码问题。我需要找出可以生成多少个满足某些约束的二进制数。输入是

(integer) Len - Number of digits in the binary number
(integer) x
(integer) y

二进制数必须使得从二进制数中取任意 x 个相邻数字应至少包含 y 个 1。

例如-

长度 = 6,x = 3,y = 2

0 1 1 0 1 1 - 长度为 6,从中取任意 3 个相邻的数字,然后 会有2个l的

我在一次采访中向我提出了这个 C# 编码问题,我想不出任何算法来解决这个问题。不寻找代码(虽然它是受欢迎的),任何形式的帮助,指针表示赞赏

【问题讨论】:

  • 011011 的长度是 6 还是 5?如果你想为你的问题创建一个算法,你需要弄清楚000101是否是算法找到的有效数字,它实际上是101(小于111111,考虑到的最大数字Len 变量)并且它也满足 (x,y) 条件。基本上,你必须知道你是在看000000 - 111111 区间还是只是在100000 - 111111
  • @AlexFilipovici 000101 不是有效数字,“二进制数中的任何 x 个相邻数字应至少包含 y 个 1”,而 000 则没有。
  • @FrancescoDeLisi, 101 小于Len = 6 变量(即111111)生成的最大数字。算法是否应该只在具有正好 6 位二进制表示的数字之间计算,还是在小于或等于111111 的所有数字之间计算?如果是后者,Len 对于101 将只有 3 个。
  • @AlexFilipovici 我认为你应该考虑整个序列而不是它的十进制表示。这是一个位序列。也许问题可能是“可以生成多少个 Length=Len 的二进制数......”
  • 生成数字的长度是多少?应该是 = Len 还是

标签: algorithm


【解决方案1】:

简单的方法是树递归算法。

我们的递归方法会慢慢增加数字,例如它将从xxxxxx 开始,返回与1xxxxx0xxxxx 的调用总和,它们本身将返回与10110001 等的调用总和. 除非 x/y 条件不满足它将通过调用自身构建的字符串,它不会沿着这条路径走,并且如果您处于终端条件(构建了正确长度的数字),则返回 1。(请注意,由于我们是从左到右构建字符串,因此您不必检查整个字符串的 x/y,只需考虑新添加的数字!)

通过返回所有调用的总和,那么所有返回的 1 将汇集在一起​​并由初始调用返回,等于构造字符串的数量。

不知道这个时间复杂度的大 O 表示法是什么,它可能和 O(2^n)*O(checking x/y conditions) 一样糟糕,但在大多数情况下它会从树上剪掉很多树枝。

更新:我的一个见解是,如果递归树的所有分支到目前为止具有相同的最后一个 x 数字,则可以“合并”它们,因为这样相同的检查将应用于以后的所有数字,因此您不妨将它们加倍并节省大量工作。这现在需要显式地构建树,而不是通过递归调用隐式地构建树,并且可能需要某种散列方案来检测分支何时具有相同的 x 结尾,但对于较大的长度,它将提供巨大的加速。

【讨论】:

  • 这是一个很长时间的方法。问题是“可以生成多少个数来满足这个条件?”。使用 Len = 200 的算法,您的答案将需要数年时间。
  • @Francesco De Lisi 是的,它确实感觉很慢,但问题并没有说'我将如何编写一个以 O(blah) 或更好的时间复杂度解决它的算法'所以我只是写了一个很容易证明的作品:)
  • @Francesco De Lisi 我想到了一种可能的加速方法,您还有其他想法吗?
  • 我认为位的选择是有原因的。也许 L 序列的 x 个古老位中 y 出现的次数有一种公式(排列?元素之间的最大/最小距离?)。当我找到解决方案时,我会更新!
【解决方案2】:

听起来像嵌套的 for 循环就可以了。伪代码(未测试)。

value = '0101010111110101010111'   // change this line to format you would need
for (i = 0; i < (Len-x); i++) {    // loop over value from left to right
     kount = 0
     for (j = i; j < (i+x); j++) { // count '1' bits in the next 'x' bits
         kount += value[j]         // add 0 or 1
         if kount >= y then return success
     }
}
return fail

【讨论】:

  • 此代码检查长度为Len 的二进制数的一个实例是否满足条件。问题是找出存在多少这样的数字。你真的认为你可以试试2^Len 号码吗?
  • 我误解了这个问题,所以我改变了我的伪代码。由于 Len 是有符号整数,因此只有大约 40 亿个值来检查整数是否为 32 位(我猜负数也是有效的。)为了优化,我会考虑查找表。
【解决方案3】:

因此,在一系列 Len 二进制数字中,您正在寻找包含 y 个 1 的 x 长段 ..

查看执行:http://ideone.com/xuaWaK

这是我的 Java 算法:

import java.util.*;
import java.lang.*;

class Main
{
    public static ArrayList<String> solve (String input, int x, int y)
    {
        int s = 0;
        ArrayList<String> matches = new ArrayList<String>();
        String segment = null;

        for (int i=0; i<(input.length()-x); i++)
        {
            s = 0;
            segment = input.substring(i,(i+x));

            System.out.print(" i: "+i+" ");

            for (char c : segment.toCharArray())
            {
                System.out.print("*");

                if (c == '1')
                {
                    s = s + 1;
                }
            }

            if (s == y)
            {
                matches.add(segment);
            }

            System.out.println();
        }

        return matches;
    }

    public static void main (String [] args)
    {
        String input = "011010101001101110110110101010111011010101000110010";

        int x = 6;

        int y = 4;

        ArrayList<String> matches = null;

        matches = solve (input, x, y);

        for (String match : matches)
        {
            System.out.println(" > "+match);
        }

        System.out.println(" Number of matches is " + matches.size());
    }
}

【讨论】:

  • 不,他不是在寻找单个 x-long 段,而是在 Len-long 二进制数字中的 每个 x-long 段必须包含 y 个。
  • 是的,我正在寻找可以生成的满足条件的此类二进制数的数量,而不是验证给定数字是否满足条件
  • @Arun 检查我提供的链接,查看执行
  • 我不是说你的代码错了,你误解了我的问题,我正在寻找满足我标准的一定长度的二进制数的计数
  • @KhaledAKhunaifer:我检查了链接,报告的匹配项中只有少数是实际匹配项。
【解决方案4】:

这个问题可以用动态规划来解决。主要思想是根据最后 x-1 位和每个二进制数的长度对二进制数进行分组。如果将位序列附加到一个数字会产生一个满足约束的数字,那么将相同的位序列附加到同一组中的任何数字都会导致一个数字也满足约束。

例如,x = 4,y = 2。01011 和 10011 都具有相同的最后 3 位 (011)。对它们中的每一个附加一个 0,得到 010110 和 100110,都满足约束。

这是伪代码:

mask = (1<<(x-1)) - 1
count[0][0] = 1
for(i = 0; i < Len-1; ++i) {
    for(j = 0; j < 1<<i && j < 1<<(x-1); ++j) {
        if(i<x-1 || count1Bit(j*2+1)>=y)
            count[i+1][(j*2+1)&mask] += count[i][j];
        if(i<x-1 || count1Bit(j*2)>=y)
            count[i+1][(j*2)&mask] += count[i][j];
    }
}
answer = 0
for(j = 0; j < 1<<i && j < 1<<(x-1); ++j)
    answer += count[Len][j];

此算法假设 Len >= x。时间复杂度为O(Len*2^x)。

编辑

count1Bit(j) 函数计算 j 的二进制表示中 1 的个数。

这个算法的唯一输入是Len, x, and y。它从一个空的二进制字符串[length 0, group 0] 开始,并反复尝试追加 0 和 1,直到长度等于 Len。它还对每组中满足 1 位约束的二进制字符串的数量进行分组和计数。该算法的输出为answer,即满足约束条件的二进制字符串(数字)的个数。

对于[length i, group j] 组中的二进制字符串,将0 附加到组[length i+1, group (j*2)%(2^(x-1))] 中的二进制字符串;将 1 附加到它会在组 [length i+1, group (j*2+1)%(2^(x-1))] 中生成一个二进制字符串。

count[i,j] 为组@98​​7654331@ 中满足1 位约束的二进制字符串的数量。如果在j*2 的二进制表示中至少有y 1,那么将0 附加到这些count[i,j] 二进制字符串中的每一个都会在组[length i+1, group (j*2)%(2^(x-1))] 中生成一个二进制字符串,它也满足1 位约束。因此,我们可以将count[i,j] 添加到count[i+1,(j*2)%(2^(x-1))] 中。附加1的情况类似。

上述算法中i&lt;x-1的条件是在长度小于x-1时保持二进制字符串增长。

【讨论】:

  • 您的解决方案需要一组二进制数作为输入,而要求是计数或生成然后计数满足条件的二进制数
  • @Arun 不,这个算法的输入只有 Len、x 和 y。编辑了关于算法的更多解释。
  • @CS.Ferng 那么算法的复杂度是关于 y 的指数吗?
  • 我想我需要成为一个数学天才才能理解这个解决方案。我正在尝试对此进行编码以验证输出,但我被困在倒数第二行“for(j = 0; j
  • 您能否以您认为合适的语言提供您的算法实现...就像@Arun 我尝试对其进行编码(在 C 中,只需复制粘贴并添加一些 ;)到没用。
【解决方案5】:

使用 LEN = 6、X = 3 和 Y = 2 的示例...

为 X 位构建一个详尽的位模式生成器。一个简单的二进制计数器可以做到这一点。例如,如果 X = 3 那么从 0 到 7 的计数器将生成所有可能的长度为 3 的位模式。

模式是:

000
001
010
011
100
101
110
111

在构建模式时验证邻接要求。拒绝任何不符合条件的模式。 基本上,这归结为拒绝任何包含少于 2 个“1”位 (Y = 2) 的模式。该列表精简为:

011
101
110
111

对于修剪列表的每个成员,添加一个“1”位并重新测试前 X 位。如果通过了新模式,则保留新模式 邻接测试。对“0”位执行相同操作。例如,此步骤如下:

1011  <== Keep
1101  <== Keep
1110  <== Keep
1111  <== Keep
0011  <== Reject
0101  <== Reject
0110  <== Keep
0111  <== Keep

哪个叶子:

1011
1101
1110
1111
0110
0111

现在重复此过程,直到修剪后的集合为空或成员长度变为 LEN 位长。到底 剩下的唯一模式是:

111011
111101
111110
111111
110110
110111
101101
101110
101111
011011
011101
011110
011111

数一数就完成了。

请注意,您只需在每次迭代中测试前 X 位,因为所有后续模式都已在前面的步骤中验证。

【讨论】:

  • 我认为你误解了这个问题,你必须至少有 y 位而不是完全 y 位
  • @Patashu 固定为基于至少设置 Y 位来接受/拒绝模式。
  • 这与我给出的解决方案一致,但如果 Length=10 则需要太多时间。
  • @Arun 长度问题比 X 和 Y 之间的差异要小。当 Y = 0 时,答案集包含 2^Len 模式,因为所有位模式都很重要(无论 X 在这点)。当 Y = X 时,答案集中只有 1 个包含所有“1”位的模式。由于“随用随剪策略”,Y 相对于 X 较大的解决方案很快就能找到,但随着 Y 相对于 X 的减小,Y 会以指数速度变慢/变大。因此,该算法具有 O(2^Len) 最坏情况空间和时间要求,但随着 Y 相对于 X 的增加而变得更好。
  • 哎呀,我的意思是 Length=100 和 x=10,因为那样你会生成一定数量的 10 位二进制数,然后你必须循环所有这些直到达到 100 位(再添加 90 个 0 或/和 1),而且需要太多时间
【解决方案6】:

我的方法是首先获取具有最小 1 数的所有二进制数,这很简单,您只需获取长度为 x 的二进制数与 y 1 的每个唯一排列,然后循环每个唯一排列“Len “次。通过在每个可能的组合中翻转这些种子的 0 位,我们可以保证迭代所有符合条件的二进制数。

from itertools import permutations, cycle, combinations

def uniq(x):
    d = {}
    for i in x:
        d[i]=1
    return d.keys()


def findn( l, x, y ):
    window = []
    for i in xrange(y):
        window.append(1)
    for i in xrange(x-y):
        window.append(0)

    perms = uniq(permutations(window))
    seeds=[]
    for p in perms:
        pr = cycle(p)
        seeds.append([ pr.next() for i in xrange(l) ]) ###a seed is a binary number fitting the criteria with minimum 1 bits

    bin_numbers=[]
    for seed in seeds:
        if seed in bin_numbers: continue
        indexes = [ i for i, x in enumerate(seed) if x == 0] ### get indexes of 0 "bits"
        exit = False
        for i in xrange(len(indexes)+1):
            if( exit ): break
            for combo in combinations(indexes, i): ### combinatorically flipping the zero bits in the seed
                new_num = seed[:]
                for index in combo: new_num[index]+=1
                if new_num in bin_numbers:
                    ### if our new binary number has been seen before
                    ### we can break out since we are doing a depth first traversal
                    exit=True
                    break
                else:
                    bin_numbers.append(new_num)

    print len(bin_numbers)

findn(6,3,2)

这种方法的增长绝对是指数级的,但我想我会分享我的方法,以防它帮助其他人获得更低复杂度的解决方案...

【讨论】:

    【解决方案7】:

    设置一些条件并引入简单的帮助变量。

    L = 6, x = 3 , y = 2 introduce d = x - y = 1
    

    条件:如果下一个数字假设值和前面x-1个元素值的列表有多个0位> d下一个数字具体值必须为1,否则添加两个括号以 1 和 0 作为具体值。

    开始:check(Condition) => 都是 0,1,因为 0 计数检查中的总零数。

    Empty => add 0 and 1
    

    第一步:检查(条件)

    0 (number of next value if 0 and previous x - 1 zeros > d(=1)) -> add 1 to sequence
    1 -> add both 0,1 in two different branches
    

    第2步:检查(条件)

    01 -> add 1
    
    10 -> add 1
    11 -> add 0,1 in two different branches
    

    第三步:

    011 -> add 0,1 in two branches
    
    101 -> add 1 (the next value if 0 and prev x-1 seq would be 010, so we prune and set only 1)
    
    110 -> add 1
    111 -> add 0,1
    

    第 4 步:

    0110 -> obviously 1
    0111 -> both 0,1
    
    1011 -> both 0,1
    
    1101 -> 1
    1110 -> 1
    1111 -> 0,1
    

    第 5 步:

    01101 -> 1
    01110 -> 1
    01111 -> 0,1
    
    10110 -> 1
    10111 -> 0,1
    
    11011 -> 0,1
    11101 -> 1
    11110 -> 1
    11111 -> 0,1
    

    第 6 步(完成):

    011011 
    011101 
    011110
    011111
    
    101101 
    101110 
    101111
    
    110110
    110111
    
    111011 
    111101 
    111110
    111111
    

    现在数一数。我也测试过 L = 6、x = 4 和 y = 2,但请考虑检查算法的特殊情况和扩展情况。

    注意:我很确定一些具有处置理论基础的算法应该是对我的算法的巨大改进。

    【讨论】:

    • 这类似于 Neal 发布的答案,但效率较低,因为它从 1 位开始并从那里开始,而我们从 x 位开始
    • @Arun 不同之处在于序列生成。这里我们先测试,然后生成。在尼尔的回答中,他生成然后测试然后修剪。这就像一个展望,也许我们可以从这个起点构建一个更好的解决方案。
    【解决方案8】:

    长度为 X 且至少包含 Y 1 位的模式的数量是可数的。对于x == y 的情况,我们知道2^x 可能的模式中只有一种模式符合标准。对于较小的y,我们需要将具有多余1 位的模式数和恰好具有y 位的模式数相加。

     choose(n, k) = n! / k! (n - k)!
    
     numPatterns(x, y) {
         total = 0
         for (int j  = x;  j >= y; j--)
            total += choose(x, j)
         return total
     }
    

    例如:

    X = 4, Y = 4 : 1 pattern
    X = 4, Y = 3 : 1 + 4 = 5 patterns
    X = 4, Y = 2 : 1 + 4 + 6 = 11 patterns
    X = 4, Y = 1 : 1 + 4 + 6 + 4 = 15 patterns
    X = 4, Y = 0 : 1 + 4 + 6 + 4 + 1 = 16 
      (all possible patterns have at least 0 1 bits)
    

    所以让M 成为满足Y 标准的X 长度模式的数量。现在,X 长度模式是N 位的子集。子模式有(N - x + 1)“窗口”位置,并且可能有2^N 总模式。如果我们从任何M 模式开始,我们知道将1 附加到右侧并移动到下一个窗口将导致我们已知的M 模式之一。问题是,我们可以将0 添加到多少M 模式,右移,并且在M 中仍然有一个有效模式?

    由于我们要添加一个零,我们必须要么远离零,要么我们必须已经在一个M 中,我们有多余的1 位。反过来,我们可以询问M 模式中有多少恰好具有Y 位,并以1 开头。这与“有多少长度X-1Y-1 位的模式”相同,我们知道如何回答:

    shiftablePatternCount = M - choose(X-1, Y-1)
    

    所以从 M 个可能性开始,当我们向右滑动时,我们将增加 shiftablePatternCount。新窗口中的所有模式都在M 的集合中,现在有一些模式重复了。我们将多次将N 填充(N - X),每次将计数增加shiftablePatternCount,所以完整的答案应该是:

    totalCountOfMatchingPatterns = M + (N - X)*shiftablePatternCount
    
    • 编辑 - 发现一个错误。我需要计算生成的可移动模式的重复项。我认为这是可行的。 (草稿)

    【讨论】:

      【解决方案9】:
      • 我不确定我的答案,但这是我的观点。看看吧,

      • Len=4,

      • x=3,
      • y=2.

      • 我刚刚取出了两个pattern,因为pattern必须至少包含y的1。

      • X 1 1 X

      • 1 X 1 X

      • X - 表示不关心

      • 现在第一个表达式的计数是 2 1 1 2 =4

      • 对于第二个表达式 1 2 1 2 =4

      • 但是 2 模式在两者之间是共同的,所以减去 2..所以总共会有 6 对满足条件。

      【讨论】:

      • 所以您的解决方案需要先生成模式?这就是问题所在,因为生成所有模式会花费太多时间。用 X 代替 2 和最后的 -2 背后的逻辑是什么? 1 x 1 x 也不满足条件,因为取最后 3 位数字,没有 2 个 1
      【解决方案10】:

      考虑到输入值是可变的并且想要查看实际输出,我使用递归算法来确定给定长度的 0 和 1 的所有组合:

          private static void BinaryNumberWithOnes(int n, int dump, int ones, string s = "")
          {
              if (n == 0)
              {
                  if (BinaryWithoutDumpCountContainsnumberOfOnes(s, dump,ones))
                      Console.WriteLine(s);
                  return;
              }
              BinaryNumberWithOnes(n - 1, dump, ones, s + "0");
              BinaryNumberWithOnes(n - 1, dump, ones, s + "1");
          }
      

      BinaryWithoutDumpCountContainsnumberOfOnes判断二进制数是否符合条件

      private static bool BinaryWithoutDumpCountContainsnumberOfOnes(string binaryNumber, int dump, int ones)
          {
              int current = 0;
              int count = binaryNumber.Length;
              while(current +dump < count)
              {
                  var fail = binaryNumber.Remove(current, dump).Replace("0", "").Length < ones;
                  if (fail)
                  {
                      return false;
                  }
                  current++;
              }
      
              return true;
          }
      

      调用 BinaryNumberWithOnes(6, 3, 2) 将输出所有匹配的二进制数

      010011
      011011
      011111
      100011
      100101
      100111
      101011
      101101
      101111
      110011
      110101
      110110
      110111
      111011
      111101
      111110
      111111
      

      【讨论】:

        【解决方案11】:

        我碰巧正在使用类似于您的问题的算法,试图找到一种改进它的方法,我找到了您的问题。所以我来分享一下

        static int GetCount(int length, int oneBits){
        int result = 0;
        double count = Math.Pow(2, length);
            for (int i = 1; i <= count - 1; i++)
            {
                string str = Convert.ToString(i, 2).PadLeft(length, '0');
                if (str.ToCharArray().Count(c => c == '1') == oneBits)
                {
                    result++;
                }
            }
            return result;
        }
        

        我认为不是很有效,但很好的解决方案。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-03-11
          • 1970-01-01
          • 1970-01-01
          • 2023-04-08
          • 2021-12-10
          • 1970-01-01
          • 2018-11-08
          相关资源
          最近更新 更多