将输入的任意a-z字符串中含有的元素组成集合,输出此集合的所有子集
题目来自July《面试和算法心得》第1章 字符串
原题
如果输入“abcac”,则它包含的元素有a、b、c三种,将abc组成一个集合{a,b,c}, 则该集合的子集有:空, {a}, {b}, {c}, {a,b}, {a, c}, {b, c}, {a, b, c}一共8种。
在不考虑空集的条件下,以字符串的形式输出这些子集。
样例输入:
babcac
样例输出:
a
b
ab
c
ac
bc
abc
算法分析
此题的关键在于对于一个给定的集合,如何输出它的所有子集。首先逐步分解此题:
第一步:获得原a-z字符串中含有的元素
假设原字符串为a[100],再引进一个标记数组 x[26],其初值为0 。
x[0]代表字符‘a’, x[ 4] 代表字符‘e’。
引入整形变量s, 其二进制表示对应x[]。
遍历数组a[],根据当前字符更改其对应x[]为1 。
例如:原字符串“aefea”对应x[]为{1,0,0,0,1,1,0,0,0,…,0}, s二进制为00…00110001(共26位)
得到x[]后,引进字符数组b[26],b[]按升序存放原字符串含有的所有元素。
例如:“aefea”对应的b[]为{a,e,f}
第二步:打印集合b[] 的所有子集
观察如下表格:
二进制数 i | 对应序列 |
---|---|
<———————— | ————————> |
1 | a |
10 | _b |
11 | ab |
100 | __c |
101 | a_c |
… | … |
10100111 | abc__f_g |
从这个表格可以清晰地看出一种思路:
二进制数i的第k位如果为1,则打印该位对应字母,否则不打印
-
至此,算法已经显而易见了
- 令 i从 0开始累加,对于每个 i,通过它的二进制形式输出 b[]中对应 字母即可。
在不考虑空集的条件下,含n个元素的集合有2^n - 1个子集,因此算法的时间复杂度为O(2^n)。
数据结构分析
类型 | 变量名 | 作用 |
---|---|---|
char | a[100] | 存放输入的字符串 |
int | x[26] | 标记数组 |
int | s | 对应x[]的二进制数 |
char | b[26] | 保存字符串所含元素 |
代码块
#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <math.h>
char a[100];
int x[26];
char b[26];
int num;
int s = 0;
void print1(char b[], int count)
{
for (int i = 1; i < (int)pow(2, (double)count); ++ i) {
int key = s & i;
int m = 0;
while (key > 0) {
int pos = key % 2;
key >>= 1;
if (pos) {
printf("%c",'a' + m);
}
m ++;
}
printf("\n");
}
}
int main(int argc, const char * argv[]) {
// init
scanf("%s",a);
num = (int)strlen(a);
memset(x, 0, sizeof(x));
for (int i = 0; i < num; ++ i) {
x[a[i] - 'a'] = 1;
if (x[a[i] - 'a'] == 1) {
s = (1 << (a[i] - 'a')) | s;
}
}
for (int i = 0, m = 0; i < num; ++ i) {
if (x[i]) {
b[m] = 'a' + m;
m++;
}
}
// print1
print1(b, (int)strlen(b));
return 0;
}
细节说明:二进制数s的初始化方法是移位,在print1函数中,对累加的整数逐位判断 0 / 1 时也用移位
第一次用markdown,并且也是第一次写算法博客,如有不足之处,日后再加删改。
markdown语法参考网站:[http://www.jianshu.com/p/1e402922ee32]