这个问题可以分为两个任务:(1)找到一个数字数组的所有子序列,(2)将整数打包和解包成数字。
让我们考虑数组的子序列{a, b, c}
。您可以通过从左到右遍历数组并遵循两条路径来生成它们:一种是在子序列中包含当前元素,另一种是不包含。
这导致了一种递归方法,我们可以将其表示为一棵树:
{}
/ \
{} {a}
/ \ / \
{} {b} {a} {ab}
/ \ / \ / \ / \
{} {c} {b} {bc} {a} {ac} {ab} {abc}
当我们向左分支时,我们跳过当前元素,当我们向右分支时,我们包含该元素。元素本身就是树的深度:在第一层我们处理 element a
,在 nextb
和 last c
。
底行包含所有子序列。这包括您不想要的空序列和完整序列。但现在让我们将它们包括在内。(底行中的数组通常称为幂集,这是一个很好的网络搜索术语。)
我提到了递归,它需要递归函数,而函数已经出来了。
所以我们需要用另一种方式来解决这个问题。让我们把树翻过来。破折号表示该元素已被跳过。右边的符号使用另一种符号:0
表示元素被跳过,1
表示元素被包含:
- - - 000
- - c 001
- b - 010
- b c 011
a - - 100
a - c 101
a b - 110
a b c 111
我希望右边的代码看起来很熟悉,因为这就是你从二进制数000
到的方式。111
这是枚举我们的元素的好方法。现在我们需要一种方法来判断每个数字中设置了哪些位。
当数字为奇数时,设置最右边的位。我们可以通过重复将数字除以 2 来找出其他位,这在二进制中是向右移位,丢弃最右边的位。
现在如何从原始号码中提取数字?该数字是十进制数;它以 10 为底。我们可以使用与查找二进制数中的位相同的方法,因为位 0 和 1 是二进制数字。
从数字开始。最后一位数字是除以 10 后取余数的结果。然后将该数字除以 10 直到为零。此代码产生从右到左的数字。查找位的代码也是如此,这意味着我们可以在单个循环中查找是否设置了位以及要打印的数字,总是取最右边的位,如果设置了,则打印原始数字的最右边的数字。
空子序列和完整子序列是枚举中的第一项和最后一项。如果您不想要它们,请跳过它们。
如果数字有重复数字,这就留下了重复子序列的问题。除了 user3629249 建议创建子序列并稍后检查是否已经打印之外,我没有看到解决此问题的简单方法。
一个简单的方法是保留一个子序列数组。该数组有max
条目。填充该数组后,对其进行排序然后打印它,但跳过与前一个条目相等的条目。
这是一个使用数字数组的示例实现,因此不必每次都分解原始数字。它使用qsort
来自的排序函数<stdlib.h>
,这需要一个排序函数:
#include <stdlib.h>
#include <stdio.h>
#define NUM 412131
typedef unsigned int uint;
int uintcmp(const void *pa, const void *pb)
{
const uint *a = pa;
const uint *b = pb;
return (*a > *b) - (*a < *b);
}
int main(void)
{
uint digit[20]; // array of digits
size_t ndigit = 0; // length of array
uint num = NUM;
uint max = 1;
size_t i;
while (num) {
digit[ndigit++] = num % 10;
num /= 10;
max *= 2;
}
uint res[max]; // array of subsequences
for (i = 0; i < max; i++) {
uint mask = i; // mask for bit detection
uint j = ndigit; // index into digit array
uint s = 0;
while (j--) {
if (mask % 2) s = s*10 + digit[j];
mask /= 2;
}
res[i] = s;
}
qsort(res, max, sizeof(*res), uintcmp);
for (i = 1; i < max - 1; i++) {
if (res[i] != res[i - 1]) printf("%u\n", res[i]);
}
return 0;
}