找到将零插入位模式的所有方法

时间:2010-05-29 18:44:58

标签: c algorithm math bitmap

由于某种原因,我一直在努力解决这个问题。我有15位代表一个数字。这些位必须与模式匹配。模式以比特开始的方式定义:它们处于该模式的最正确表示。所以说模式是1 4 1.比特将是:

000000010111101

因此,一般规则是,取模式中的每个数字,创建多个位(在这种情况下为1,4或1),然后至少有一个空格将它们分开。所以,如果它是1 2 6 1(它将是随机的):

001011011111101

从刷新版本开始,我想生成满足该模式的每个可能的数字。位数将存储在变量中。因此,对于一个简单的情况,假设它是5位,初始位模式是:00101。我想生成:

00101 01001 01010 10001 10010 10100

我正在尝试在Objective-C中执行此操作,但任何类似于C的内容都可以。我似乎无法为此提出一个很好的递归算法。在上面的例子中它是有道理的,但是当我开始进入12431并且必须跟踪它所发生的一切时。

6 个答案:

答案 0 :(得分:3)

希望这会让你更容易缠绕它(请用手中的笔和纸阅读)。

假设零的数量(从右边开始)是x 1 ,x 2 ,...,x n 。例如:如果位模式为00001110001001则x 1 = 0,x 2 = 2,x 3 = 3,x 4 = 4. n比一个块的数量多一个。观察知道x 1 ,x 2 ,...,x n 足以找出位模式。

现在,如果您拥有的1的总数是S,并且您可用的总位数是M,那么我们必须具有

x 1 + x 2 + ... + x n = M - S

且x 1 ≥0,x n ≥0,x 2 ≥1,x 3 ≥ 1,......

设z 1 = x 1 + 1 和z n = x n + 1

因此我们有

z 1 + x 2 + ... x n-1 + z n = M - S + 2

当z 1 ≥1,x 2 ≥1,x 3 ≥1,...,z n < / sub>≥1。

现在考虑一个M-S + 2项目的分区,其中每个分区至少有一个项目。任何分区对应于上述等式的解,并且解决方案对应于1-1方式的分区。

沿着一条线放置M-S + 2个项目。要获得分区,请考虑在项目之间的M-S + 2-1 = M-S + 1个点中放置n-1个分支。

因此,解决方案(最终是您所需的位模式)唯一对应于在M-S + 1点中选择n-1个点的方式。

在5位的情况下,1位为1和1.

你有n = 3,M = 5,S = 2.

因此你有M-S + 1选择n-1 = 4选择2 = 6个可能性。

枚举n选择r组合是一个标准问题,你应该在网上找到各种各样的解决方案(其中一些非常聪明!)。

有关示例,请参阅此处:http://compprog.files.wordpress.com/2007/10/comb1.c,它似乎支持“懒惰”枚举:next_combination并且不需要大量内存。

答案 1 :(得分:2)

我不打算给你Objective-C代码主要是因为:

  • 我只是在非常肤浅的层面上知道Objective-C。
  • 我不想编写使用像C这样的语言工作所需的所有内存管理代码,但它只会降低可读性。

相反,我会给你一些想法和一些代码,展示如何用更高的语言实现这一点,使用生成器和垃圾收集(在本例中为Python),并提示如何在没有生成器的情况下执行此操作。希望其他人可以为您自己移植代码。

我会以稍微不同的方式思考你的问题:

  • 您最初的“冲洗右”模式中有多少个前导零。
  • 有多少种方法可以将该数量的零分成n个分区。

在上一个示例中,您有两个前导零和三个分区“10”和“1”的分区:

2 0 0: 00101  
1 1 0: 01001   
1 0 1: 01010   
0 2 0: 10001   
0 1 1: 10010   
0 0 2: 10100

分隔符的格式始终为111..10,但最后一个只有111..1而没有尾随零。

要枚举上述分区,请使用Python中的以下函数:

def partitions(n, x):
    if n == 1:
        yield [x]
    else:
        for i in range(x + 1):
            for p in partitions(n - 1, x - i):
                yield [i] + p

for p in partitions(3, 2):
    print p

结果:

[0, 0, 2]
[0, 1, 1]
[0, 2, 0]
[1, 0, 1]
[1, 1, 0]
[2, 0, 0]

一旦有了这些分区,构建模式就很简单了。

一个挑战是Objective-C没有对yield结构的内置支持。以下重写上述函数可能更容易转换为Objective-C:

def partitions(n, x):
    if n == 1:
        return [[x]]
    else:
        result = []
        for i in range(x + 1):
            for p in partitions(n - 1, x - i):
                result.append([i] + p)
        return result

我希望这对你有用。

答案 2 :(得分:1)

@Mark Byers'sMoron's答案的基础上,您的任务可以重新制定如下:

枚举将K个零放入N个地方的所有方法(请参阅combinations with repetitionStars and bars)。

示例:对于15位和1 2 6 1模式,有N = 5个位置(在数字之前/之后和1之间)以使K = 2个零(冲洗的前导零数 - 正确的号码)。方式的数量为binomial(N + K - 1,K),即二项式(5 + 2-1,2)= 15.

以下代码中的关键功能是next_combination_counts()comb2number()

C

中的完整程序
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>

#define SIZE(arr) (sizeof(arr)/sizeof(*(arr)))

#define PRInumber "u"
typedef unsigned number_t;

// swap values pointed to by the pointer
static void
iter_swap(int* ia, int* ib) {
  int t = *ia;
  *ia = *ib;
  *ib = t;
}

// see boost::next_combinations_counts()
// http://photon.poly.edu/~hbr/boost/combinations.html
// http://photon.poly.edu/~hbr/boost/combination.hpp
static bool 
next_combination_counts(int* first, int* last) {
  /*
0 0 2 
0 1 1 
0 2 0 
1 0 1 
1 1 0 
2 0 0 
   */
    int* current = last;
    while (current != first && *(--current) == 0) {
    }
    if (current == first) {
        if (first != last && *first != 0)
            iter_swap(--last, first);
        return false;
    }
    --(*current);
    iter_swap(--last, current);
    ++(*(--current));
    return true;
}

// convert combination and pattern to corresponding number
// example: comb=[2, 0, 0] pattern=[1,1] => num=5 (101 binary)
static number_t 
comb2number(int comb[], int comb_size, int pattern[], int pattern_size) {
  if (pattern_size == 0)
    return 0;
  assert(pattern_size > 0);
  assert(comb_size > pattern_size);

  // 111 -> 1000 - 1 -> 2**3 - 1 -> (1 << 3) - 1
  // 111 << 2 -> 11100
  number_t num = ((1 << pattern[pattern_size-1]) - 1) << comb[pattern_size];  
  int len = pattern[pattern_size-1] + comb[pattern_size];
  for (int i = pattern_size - 1; i--> 0; ) {
    num += ((1 << pattern[i]) - 1) << (comb[i+1] + 1 + len);
    len += pattern[i] + comb[i+1] + 1;
  }  

  return num;
}

// print binary representation of number
static void 
print_binary(number_t number) {
  if (number > 0) {
    print_binary(number >> 1);
    printf("%d", number & 1);
  }
}

// print array
static void
printa(int arr[], int size, const char* suffix) {
  printf("%s", "{");
  for (int i = 0; i < (size - 1); ++i)
    printf("%d, ", arr[i]);
  if (size > 0)
    printf("%d", arr[size - 1]);
  printf("}%s", suffix);
}

static void 
fill0(int* first, int* last) {
  for ( ; first != last; ++first)
    *first = 0;
}

// generate {0,0,...,0,nzeros} combination
static void
init_comb(int comb[], int comb_size, int nzeros) {
  fill0(comb, comb + comb_size);
  comb[comb_size-1] = nzeros;
}

static int
sum(int* first, int* last) {
  int s = 0;
  for ( ; first != last; ++first)
    s += *first;
  return s;
}

// calculated max width required to print number (in PRInumber format)
static int 
maxwidth(int comb[], int comb_size, int pattern[], int pattern_size) {
  int initial_comb[comb_size];

  int nzeros = sum(comb, comb + comb_size);
  init_comb(initial_comb, comb_size, nzeros);
  return snprintf(NULL, 0, "%" PRInumber, 
                  comb2number(initial_comb, comb_size, pattern, pattern_size)); 
}

static void 
process(int comb[], int comb_size, int pattern[], int pattern_size) {
  // print combination and pattern
  printa(comb, comb_size, " ");
  printa(pattern, pattern_size, " ");
  // print corresponding number
  for (int i = 0; i < comb[0]; ++i)
    printf("%s", "0");
  number_t number = comb2number(comb, comb_size, pattern, pattern_size);
  print_binary(number);
  const int width = maxwidth(comb, comb_size, pattern, pattern_size);
  printf(" %*" PRInumber "\n", width, number);
}

// reverse the array
static void 
reverse(int a[], int n) {
  for (int i = 0, j = n - 1; i < j; ++i, --j) 
    iter_swap(a + i, a + j);  
}

// convert number to pattern
// 101101111110100 -> 1, 2, 6, 1
static int 
number2pattern(number_t num, int pattern[], int nbits, int comb[]) {
  // SIZE(pattern) >= nbits
  // SIZE(comb) >= nbits + 1
  fill0(pattern, pattern + nbits);
  fill0(comb, comb + nbits + 1);

  int i = 0;
  int pos = 0;
  for (; i < nbits && num; ++i) {
    // skip trailing zeros
    for ( ; num && !(num & 1); num >>= 1, ++pos)
      ++comb[i];
    // count number of 1s
    for ( ; num & 1; num >>=1, ++pos) 
      ++pattern[i];
  }
  assert(i == nbits || pattern[i] == 0);  
  const int pattern_size = i;  

  // skip comb[0]
  for (int j = 1; j < pattern_size; ++j) --comb[j];
  comb[pattern_size] = nbits - pos;

  reverse(pattern, pattern_size);
  reverse(comb, pattern_size+1);
  return pattern_size;
}

int 
main(void) {
  number_t num = 11769; 
  const int nbits = 15;

  // clear hi bits (required for `comb2number() != num` relation)
  if (nbits < 8*sizeof(number_t))
    num &=  ((number_t)1 << nbits) - 1;
  else
    assert(nbits == 8*sizeof(number_t));

  // `pattern` defines how 1s are distributed in the number
  int pattern[nbits];
  // `comb` defines how zeros are distributed 
  int comb[nbits+1];
  const int pattern_size = number2pattern(num, pattern, nbits, comb);
  const int comb_size = pattern_size + 1;

  // check consistency
  // . find number of leading zeros in a flush-right version
  int nzeros = nbits;
  for (int i = 0; i < (pattern_size - 1); ++i)
    nzeros -= pattern[i] + 1;
  assert(pattern_size > 0);
  nzeros -= pattern[pattern_size - 1];
  assert(nzeros>=0);

  // . the same but using combination
  int nzeros_comb = sum(comb, comb + comb_size);
  assert(nzeros_comb == nzeros);

  // enumerate all combinations 
  printf("Combination Pattern Binary Decimal\n");
  assert(comb2number(comb, comb_size, pattern, pattern_size) == num);
  process(comb, comb_size, pattern, pattern_size); // process `num`

  // . until flush-left number 
  while(next_combination_counts(comb, comb + comb_size))
    process(comb, comb_size, pattern, pattern_size);

  // . until `num` number is encounterd  
  while (comb2number(comb, comb_size, pattern, pattern_size) != num) {
    process(comb, comb_size, pattern, pattern_size);
    (void)next_combination_counts(comb, comb + comb_size);
  }

  return 0;
}

输出:

Combination Pattern Binary Decimal
{1, 0, 0, 1, 0} {1, 2, 6, 1} 010110111111001 11769
{1, 0, 1, 0, 0} {1, 2, 6, 1} 010110011111101 11517
{1, 1, 0, 0, 0} {1, 2, 6, 1} 010011011111101  9981
{2, 0, 0, 0, 0} {1, 2, 6, 1} 001011011111101  5885
{0, 0, 0, 0, 2} {1, 2, 6, 1} 101101111110100 23540
{0, 0, 0, 1, 1} {1, 2, 6, 1} 101101111110010 23538
{0, 0, 0, 2, 0} {1, 2, 6, 1} 101101111110001 23537
{0, 0, 1, 0, 1} {1, 2, 6, 1} 101100111111010 23034
{0, 0, 1, 1, 0} {1, 2, 6, 1} 101100111111001 23033
{0, 0, 2, 0, 0} {1, 2, 6, 1} 101100011111101 22781
{0, 1, 0, 0, 1} {1, 2, 6, 1} 100110111111010 19962
{0, 1, 0, 1, 0} {1, 2, 6, 1} 100110111111001 19961
{0, 1, 1, 0, 0} {1, 2, 6, 1} 100110011111101 19709
{0, 2, 0, 0, 0} {1, 2, 6, 1} 100011011111101 18173
{1, 0, 0, 0, 1} {1, 2, 6, 1} 010110111111010 11770

答案 3 :(得分:0)

假设我们有:

000101101

首先,计算0(5),并计算由1(1)包围的0组。我们现在可以忘记1s了。任何组合中每组的零数可以是描述为的列表(其中+表示“或更多”):

[0+, 1+, 1+, 0+] where the sum is 6

这只是类似问题的变体:找到总和为K的所有N个非负整数集。例如:

[0+, 0+, 0+, 0+] where the sum is 6

现在,要解决此问题,请从N = 1开始。解决方案显然只是[6]。当N = 2时,解决方案是:

[0,6] [1,5] [2,4] [3,3] [4,2] [5,1] [6,0]

重要的是要注意一个潜在的主题:左侧变得更富,右侧变得更穷。我将使用Haskell来描述算法,因为它对于这类事物来说非常优雅:

sumsTo k n
    | n == 1 = [[k]]
    | n > 1  = do
        i <- [0..k]
        s <- sumsTo (k-i) (n-1)
        return (i : s)

n == 1案例很容易理解:它只返回一个组合:[k]。在n > 1的情况下,这里实际上有一个嵌套循环。它基本上是在说:

for each number i from 0 to k
    for each s in sumsTo (k-i) (n-1)
        prepend i to s

虽然这个算法并不能很好地解决你的问题,但一般来说都很有用。

现在,我们希望算法以不同的方式运行,以处理中间列表项不能为零的方式。对于这些情况,我们希望使用i <- [1..k]而不是i <- [0..k]。对于结束号码没有关系,因为它没有自由意志(它仅取决于之前项目的总和)。越接近,我们可能会说:

sumsTo k n
    | n == 1 = [[k]]
    | n > 1  = do
        i <- [1..k]
        s <- sumsTo (k-i) (n-1)
        return (i : s)

但是,我们希望我们的第一项能够从零开始。为了补丁,我们可以说:

sumsTo k n first_start
    | n == 1 = [[k]]
    | n > 1  = do
        i <- [first_start..k]
        s <- sumsTo (k-i) (n-1) 1
             -- make subsequent sumsTo calls start from 1 instead of 0
        return (i : s)

这为我们提供了我们需要的序列K(0的数量)和N(0的内部组的数量加上2)。剩下的就是对序列进行字符串化(例如将[1,1,0]转换为“01”)。我们可以直接将字符串化嵌入到递归算法中。

总结一下,这是Haskell的解决方案:

import Data.List

sumsTo k n first_start
    | n == 1 = [[k]]
    | n > 1  = do
        i <- [first_start..k]
        s <- sumsTo (k-i) (n-1) 1
        return (i : s)

-- remove any xes found at the beginning or end of list
trim x list
    = dropWhile (== x)
    $ reverse
    $ dropWhile (== x)
    $ reverse
    $ list

-- interleave [1,3,5] [2,4] = [1,2,3,4,5]
interleave xs ys = concat $ transpose [xs,ys]

solve input = solutions where
    -- pull out groups of 1s and put them in a list
    groupsOfOnes = filter ((== '1') . head) $ group input

    -- count 0s
    k = length $ filter (== '0') input

    -- trim outer 0s
    trimmed = trim '0' input

    -- count inner groups of 0s
    innerGroups = length $ filter ((== '0') . head) $ group trimmed

    -- n is number of outer groups (which is innerGroups + 2)
    n = innerGroups + 2

    -- compute our solution sequences
    -- reverse them so our answer will be in lexicographic order
    sequences = reverse $ sumsTo k n 0

    -- to transform a sequence into a group of zeros,
    -- simply make strings of the indicated number of zeros
    groupsOfZeros seq = [replicate n '0' | n <- seq]

    -- a solution for a sequence is just the zeros interleaved with the ones
    solution seq = concat $ interleave (groupsOfZeros seq) groupsOfOnes

    solutions = map solution sequences

main = do
    input <- getLine
    putStr $ unlines $ solve input

总之,我建议学习Haskell,以便更快地对原型进行原型设计: - )

答案 4 :(得分:0)

这是理论上还是实际上的问题?您是否需要最佳的O(N)或相当好的执行时间?如果这是一个实际问题,除非它在内部循环中,只需检查每个15位数字应该足够快。这只是32k的数字。

只需获取您的号码的分区,如下所示:

void get_groups(ushort x, int* groups) {
  int change_group = 0;
  while(x) {
    if (x & 1) {
      ++(*groups);
      change_group = 1;
    } else {
      if (change_group) {
        ++groups;
        change_group = 0;
      }
    }
    x >>= 1;
  }
}

然后,对于每个15位数字,检查它是否产生与初始数字相同的组数组。

注意:groups数组应该能够容纳最大数量的组(例如,对于15位数字应该具有8的大小)。

答案 5 :(得分:0)

如果您的模式为00101,如示例所示,那么您可以考虑生成六种模式的一种方法是:

查看模式0000,然后选择两个零以更改为1。现在你将拥有像0011这样的东西。现在只需在每个0之后插入一个1(最后一个除外)。现在你将拥有00101

请注意,您选择4个地方中的2个,并且有6种不同的可能方法(与您所写的内容一致)。现在你只需要一种方法来选择要翻转的两位。您可以使用此链接指定的算法:

http://compprog.wordpress.com/2007/10/17/generating-combinations-1/