查找具有特定约束的二进制数的数量

时间:2013-05-22 06:22:09

标签: algorithm

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

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

二进制数必须是这样的,从二进制数中取任何x个相邻数字应该至少包含y 1。

例如 -

  

Len = 6,x = 3,y = 2

     

0 1 1 0 1 1 - 长度为6,从此处取3个相邻的数字   会有2个人

我在采访中向我提出了这个C#编码问题,我无法找出解决这个问题的任何算法。不寻找代码(尽管欢迎它),任何形式的帮助,指针都很受欢迎

11 个答案:

答案 0 :(得分:2)

使用动态编程可以解决此问题。主要思想是根据最后的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,直到length等于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]为满足1位约束的组[length i, group j]中的二进制字符串数。如果y的二进制表示中至少有j*2 1,则对这些count[i,j]二进制字符串中的每一个追加0会产生组[length i+1, group (j*2)%(2^(x-1))]中的二进制字符串,该字符串也满足1位约束。因此,我们可以将count[i,j]添加到count[i+1,(j*2)%(2^(x-1))]中。附加1的情况类似。

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

答案 1 :(得分:1)

使用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位,因为所有后续模式都在之前的步骤中得到验证。

答案 2 :(得分:1)

考虑到输入值是可变的并且想要查看实际输出,我使用递归算法来确定给定长度的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

答案 3 :(得分:0)

天真的方法是树递归算法。

我们的递归方法会慢慢建立数字,例如它将从xxxxxx开始,返回与1xxxxx0xxxxx的通话总和,这些通话本身将返回1011的通话总和和0001等,除非x / y条件不满足它将通过调用自身构建的字符串,它不会沿着那条路走下去,如果你处于终端状态(建立一些正确的长度)你返回1.(注意,因为我们从左到右构建字符串,你不必检查整个字符串的x / y,也考虑新添加的数字!)

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

不知道时间复杂度的大O符号是什么,它可能与O(2^n)*O(checking x/y conditions)一样糟糕,但在大多数情况下它会修剪掉树上的许多分支。

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

答案 4 :(得分:0)

听起来像嵌套的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

答案 5 :(得分:0)

因此,在一系列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());
    }
}

答案 6 :(得分:0)

我的方法是首先获得最小数为1的所有二进制数,这很容易,只需得到y 1的长度为x的二进制数的每个唯一排列,并循环每个唯一的排列“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 :(得分:0)

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

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

条件:如果下一个数字的hypotetical值列表和之前的x-1个元素值的数字为0位&gt; d next number具体值必须为1,否则添加两个brances,其中1和0都是具体值。

开始:检查(条件)=&gt;由于0计数检查中的总零数,所以都是0,1。

Empty => add 0 and 1

第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

第3步:

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,但考虑检查特殊情况和扩展情况的算法。

注意:我很确定一些基于处理理论基础的算法应该是我算法的一个非常大的改进。

答案 8 :(得分:0)

包含至少Y 1位的长度X的模式数是可数的。对于案例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是满足X条件的Y长度模式的数量。现在,X长度模式是N位的子集。子模式有(N - x + 1)个“窗口”位置,可能有2^N个总模式。如果我们从任何M模式开始,我们知道向右追加1并转移到下一个窗口将导致我们已知的M模式之一。问题是,在M中,我们可以向0添加一个M模式,向右移动,还有一个有效模式?

由于我们要添加零,我们必须要么偏离零,要么我们必须已经在M中,我们有超过1位。为了解决这个问题,我们可以询问有多少M模式具有完全Y位,并以1开头。这与“有多少长度X-1具有Y-1位的模式”相同,我们知道如何回答:

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

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

totalCountOfMatchingPatterns = M + (N - X)*shiftablePatternCount
  • 编辑 - 意识到了一个错误。我需要计算生成的可移位模式的副本。我认为这是可行的。 (草稿仍然)

答案 9 :(得分:0)

  • 我不确定我的答案,但这是我的观点。请看一下,

  • 长度= 4,

  • X = 3,
  • Y = 2。

  • 我刚刚拿出两种模式,因为模式必须至少包含y的1.

  • X 1 1 X

  • 1 X 1 X

  • X - 表示不在乎

  • 现在计算第一个表达式为2 1 1 2 = 4

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

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

答案 10 :(得分:0)

我碰巧使用类似于你的问题的algoritem,试图找到改进它的方法,我找到了你的问题。所以我会分享

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;
}
我认为不是很有效,而是优雅的解决方案。