面试题精解之二: 字符串、数组(1)
本篇文章发表在下面三个博客中,如果出现排版问题,请移步到另一个博客。
http://www.cppblog.com/flyinghearts
http://www.cnblogs.com/flyinghearts
http://blog.csdn.net/flyinghearts
1 在一个字符串中找到第一个只出现一次的字符,如输入abac,则输出b。
2 输出字符串的所有组合,如"abc"输出a、b、c、ab、ac、bc、abc。
1 在一个字符串中找到第一个只出现一次的字符,如输入abac,则输出b。
本题看似很简单,开个长度为256的表,对每个字符hash计数就可以了,但很多人写的代码都存在bug,可能会发生越界访问。这是C/C++语言上的一个陷阱,C/C++中的char有三种类型:char、signed char和unsigned char。char类型的符号是由编译器指定的,一般是有符号的。在对字符进行hash时,应该先将字符转为无符号类型,不然,下标为负值时,就会出现越界访问。
另外,可以用一个cache数组,记录当前找到的只出现一次的字符,避免对原字符串进行第二次遍历。
char get_first_only_one(const char str[])
{
if (str == NULL) return 0;
const int table_size = 256; //最好写成: 1 << CHAR_BIT 或 UCHAR_MAX + 1
unsigned count[table_size] = { 0};
char cache[table_size];
char *q = cache;
for (const char* p = str; *p != 0; ++p)
if (++count[(unsigned char)*p] == 1) *q++ = *p; //要先转成无符号数!!!
for (const char* p = cache; p < q; ++p)
if (count[(unsigned char)*p] == 1) return *p;
return 0;
}
2 输出字符串的所有组合,如"abc"输出a、b、c、ab、ac、bc、abc。
本题假定字符串中的所有字符都不重复。根据题意,如果字符串中有n个字符,那么总共要输出2^n – 1种组合。这也就意味着n不可能太大,否则的话,以现在CPU的运算速度,程序运行一次可能需要跑几百年、几千年,而且也没有那么大的硬盘来储存运行结果。因而,可以假设n小于一个常数(比如64)。
本题最简洁的方法,应该是采用递归法。遍历字符串,每个字符只能取或不取。取该字符的话,就把该字符放到结果字符串中,遍历完毕后,输出结果字符串。
n不是太小时,递归法效率很差(栈调用次数约为2^n,尾递归优化后也有2^(n-1))。注意到本题的特点,可以构照一个长度为n的01字符串(或二进制数)表示输出结果中最否包含某个字符,比如:"001"表示输出结果中不含字符a、b,只含c,即输出结果为c,而"101",表示输出结果为ac。原题就是要求输出"001"到"111"这2^n – 1个组合对应的字符串。
//迭代法
void all_combine(const char str[])
{
if (str == NULL || *str == 0) return;
const size_t max_len = 64;
size_t len = strlen(str);
if (len >= max_len ) {
puts("输入字符串太长。\n你愿意等我一辈子吗?");
return;
}
bool used[max_len] = { 0}; //可以用一个64位无符号数表示used数组
char cache[max_len];
char *result = cache + len;
*result = 0;
while (true) {
size_t idx = 0;
while (used[idx]) { //模拟二进制加法,一共有2^len – 1个状态
used[idx] = false;
++result;
if (++idx == len) return;
}
used[idx] = true;
*--result = str[idx];
puts(result);
}
}
//递归解法
static void all_combine_recursive_impl(const char* str, char* result_begin, char* result_end)
{
if (*str == 0) {