面试题21 编写字符串反转函数strrev
编写字符串反转函数:strrev。要求时间和空间效率都尽量高。测试用例:输入“abcd”,输出应为“dcba”。
【解析】
看到这个题目,想到最简单、最直觉的解法就是:遍历字符串,将第一个字符和最后一个交换,第二个和倒数第二个交换,依次循环。
于是有了解法1:
char* strrev1(const char* str)
{
int len = strlen(str);
char* tmp = new char[len + 1];
strcpy(tmp, str);
for (int i = 0; i < len / 2; ++i)
{
char c = tmp[i];
tmp[i] = tmp[len - i - 1];
tmp[len - i - 1] = c;
}
return tmp;
}
解法1是通过数组下标的方式访问字符串字符的,也可以用指针直接操作。下面是解法2:
char* strrev2(const char* str)
{
char* tmp = new char[strlen(str) + 1];
strcpy(tmp,str);
char* ret = tmp;
char* p = tmp + strlen(str) - 1;
while (p > tmp)
{
char t = *tmp;
*tmp = *p;
*p = t;
--p;
++tmp;
}
return ret;
}
解法1和解法2都没有考虑时间和空间的优化,一个典型的优化策略就是两个字符交换的算法优化,我们可以借助异或运算符(^)完成两个字
符的交换,对应这里的解法3和解法4.解法3:
char* strrev3(const char* str)
{
char* tmp = new char[strlen(str) + 1];
strcpy(tmp, str);
char* ret = tmp;
char* p = tmp + strlen(str) - 1;
while (p > tmp)
{
*p ^= *tmp;
*tmp ^= *p;
*p ^= *tmp;
--p;
++tmp;
}
return ret;
}
解法4:
char* strrev4(const char* str)
{
char* tmp = new char[strlen(str) + 1];
strcpy(tmp, str);
char* ret = tmp;
char* p = tmp + strlen(str) - 1;
while (p > tmp)
{
*p = *p + *tmp;
*tmp = *p - *tmp;
*p = *p - *tmp;
--p;
++tmp;
}
return ret;
}
我们还可以使用递归来解问心有愧这个问题。每次交换首尾两个字符,中间部分则又变为和原来字符串同样的问题。解法5:
char* strrev5(char* str, int len)
{
if (len <= 1)
return str;
char t = *str;
*str = *(str + len - 1);
*(str + len - 1) = t;
return (strrev5(str + 1, len - 2) - 1);
}
注意,这里5个解法中只有解法5修改了输入字符串,其他4种都是在函数体内申请堆内存。下面给出测试用的main()函数:
int main(int argc, char *argv[])
{
const char *str = "123456";//C++不充许从const char*初始化为char*
cout << "str = " << str << endl;
char *str2 = strrev1(str);
cout << "str2= " << str2 << endl;
char *str3 = strrev2(str2);
cout << "str3= " << str3 << endl;
char *str4 = strrev3(str3);
cout << "str4= " << str4 << endl;
char *str5 = strrev4(str4);
cout << "str5= " << str5 << endl;
char *str6 = strrev5(str5, strlen(str5));
cout << "str6= " << str6 << endl;
return 0;
}
程序输出结果:
面试题22 编程实现任意长度的两个正整数相加
【解析】
我们知道在C/C++中有int、float、double等类型来表示数字,但是它们的长度都是有限的。而本题要求可以是任意长度,这里可以用字符串表示数字,结果也用字符串表示。因此,我们所要做的就是做一个类似整数加法的字符串转换,主要是字符做加法运算并且要考虑进位。示例程序如下所示。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
char* addBigInt(char* num1, char* num2)
{
int c = 0; //c为进位,开始最低进位为0;
int i = strlen(num1) - 1; //i指向第一个加数的最低位
int j = strlen(num2) - 1; //j指向第二个加数的最低位
//两个数中较大数的位数
int maxLength = strlen(num1) >= strlen(num2) ? strlen(num1) + 1 : strlen(num2) + 1;
char* rst = (char*)malloc(maxLength + 1); //保留结果
int k;
if (rst == NULL) //这边判断申请的内存是否成功
{
printf("malloc error!\n");
exit(1);
}
rst[maxLength] = '\0'; //字符串最后一位为'\0'
k = strlen(rst) - 1; //指向结果数组的最低位
while ((i >= 0) && (j >= 0))
{
rst[k] = ((num1[i] - '0') + (num2[j] - '0') + c) % 10 + '0';
c = ((num1[i] - '0') + (num2[j] - '0') + c) / 10;
--i;
--j;
--k;
}
while (i >= 0) //这边是个弥补
{
rst[k] = ((num1[i] - '0') + c) % 10 + '0';
c = ((num1[i] - '0') + c) / 10;
--i;
--k;
}
while (j >= 0) //这边是个弥补
{
rst[k] = ((num1[j] - '0') + c) % 10 + '0';
c = ((num2[j] - '0') + c) / 10;
--j;
--k;
}
rst[0] = c + '0';
if (rst[0] != '0') //如果结果最高位不等于0,则输出结果
{
return rst;
}
else
{
return rst + 1;
}
}
int main()
{
char num1[] = "123456789323";
char num2[] = "45671254563123";
char *result = NULL;
result = addBigInt(num1, num2);
printf("%s + %s = %s\n", num1, num2, result);
return 0;
}
程序执行结果:
面试题23 编程实现字符串的循环右移
【解析】
根据题意,我们编写的函数能把一个char组成的字符串循环右移n个。例如原来是"abcdefghi",如果n=2,移位后应该是"hiabcdefgh"。程序代码如下。
#include <stdio.h>
#include <stdlib.h>
void loopMove(char *str, int n)
{
int i = 0;
char *temp = NULL;
int strLen = 0;
char *head = str; //指向字符串头
while (*str++); //这边str走到字符串末尾了
strLen = str - head - 1; //计算字符串长度
n = n % strLen; //计算字符串尾部移到头部的字符
temp = (char *)malloc(n); //分配内存
for (i = 0; i < n; i++)
{
temp[i] = head[strLen - n + i]; //临时存放从尾部移到头部的字符
}
for (i = strLen - 1; i >= n; i--)
{
head[i] = head[i - n]; //从头部字符移到尾部
}
for (i = 0; i < n; i++)
{
head[i] = temp[i]; //从临时内存区复制尾部字符
}
free(temp); //释放内存
}
int main()
{
char string[] = "123456789";
int steps = 0;
printf("string:%s\n", string);
printf("input step:");
scanf("%d", &steps);
loopMove(string, steps); //向右循环移位
printf("after loopMove %d:%s\n", steps, string);
return 0;
}
程序执行结果:
程序中首先计算字符串尾部移到头部的字符个数,然后分配一个相同大小的堆内存来临时保存这些字符,最后做两次循环分别把
头部字符移位到尾部,以及把堆内存中的内容复制到字符串头部。
面试题24 删除指定长度的字符
编程实现从字符串的指定位置开始,删除指定长度的字符。
【解析】
根据题意,假设一个字符串"abcdefg",如果从第二个开始(索引为1),删除两个字符,则删除后的字符串是"adefg"。程序代码如下。
#include <stdio.h>
#include <string.h>
//str:字符串 pos:从该位置开始 len:删除的长度
char *deleteChars(char *str, int pos, int len)
{
char *p = str + pos - 1; //指向pos位置字符
int tt = strlen(str); //计算字符长度
if ((pos < 1) || (p - str) > tt) //检查pos是否不大于1
{ //或者pos超出字符串长度
return str;
}
if ((p + len - str) > tt) //len大于pos后剩余的字符个数
{ //只需对pos位置赋'\0'
*p = '\0';
return str;
}
//删除len个字符
while (*p &&*(p + len)) //len小于或等于pos后剩余的字符个数
{ //删除中间len个字符
*p = *(p + len);
p++;
}
*p = '\0';
return str;
}
int main()
{
char string[] = "abcdefg";
int pos = 0;
int len = 0;
int steps = 0;
printf("string:%s\n", string);
printf("input pos:");
scanf_s("%d", &pos);
printf("input len:");
scanf("%d", &len);
deleteChars(string, pos, len);
printf("after delete %d chars behind pos %d:%s\n", len, pos, string);
return 0;
}
程序执行结果:
deleteChars函数首先检查传入pos以及len的合法性,然后找到字符串的pos位置,最后删除len个字符。
面试题25 字符串的排序及交换
编写一个函数将一条字符串分成两部分,将前半部分按ASCII码升序排序,后半部分不变,(如果字符串是奇数,则中间的字符不变)再将前后两部分交换,最后将该字符串输出。测试字符串“ADZDDJKJFIEJHGI”。
【解析】
程序代码如下:
#include <stdio.h>
#include <stdlib.h>
//冒泡排序算法
void mysort(char *str, int num)
{
int i, j;
int temp = 0;
for (i = 0; i < num; i++)
{
for (j = i + 1; j < num; j++)
{
if (str[i] < str[j]) //如果下一个值比当前值大,
{ //则交换两个元素值
temp = str[i];
str[i] = str[j];
str[j] = temp;
}
}
}
}
char *foo(char *str)
{
int len = 0;
char *start = NULL;
if (str == NULL) //检查参数str的有效性
{
return NULL;
}
start = str; //保存头部位置
while (*str++);
len = str - start - 1; //计算字符串长度
len = len / 2; //计算需要排序的字符个数
str = start;
mysort(str, len); //从大到小排序
return str;
}
int main()
{
char string[] = "ADZDDJKJFIEJHGI";
printf("before transformation:%s\n", string);
foo(string); //调用冒泡排序方法进行排序
printf("after transformation:%s\n", string);
return 0;
}
程序执行结果:
foo()函数首先获得字符串的长度,然后计算需要排序的字符个数,最后调用mysort函数(使用冒泡排序方法)对字符进行排序。
面试题26 编程实现删除字符串中所有指定的字符
【解析】
根据题意,假设字符串为“cabcdefcgchci”,把该字符串中所有的字符'c'删除后,结果应该是“abdefghi”。程序代码如下。
#include <stdio.h>
char *deleteChar(char *str, char c)
{
char *head = NULL; //head与p都设置为头节点那边
char *p = NULL;
if (str == NULL) //检查str的有效性
{
return NULL;
}
head = p = str; //指向字符串头,准备遍历
while (*p++)
{
if (*p != c) //如果不等于c的值,则在str中记录
{
*str++ = *p;
}
}
*str = '\0';
return head;
}
int main()
{
char string[] = "cabcdefcgchci";
char c = 0;
printf("input char:");
scanf("%c", &c);
printf("before delete:%s\n", string);
deleteChar(string, c); //删除所有的c
printf("after delete:%s\n", string);
return 0;
}
程序执行结果:
deleteChar()函数首先判断传入字符指针的有效性,然后使用两个指针进行操作。其中一个指针用来做记录,后一个指针进行遍历字符串。
面试题27 分析代码——使用strcat连接字符串
下面的程序代码有什么问题吗?输出是什么?
#include <iostream>
using namespace std;
int main()
{
const char *str1 = "hello";
const char *str2 = "china";
char *str3 = NULL;
str3 = new char[strlen(str1) + strlen(str2) + 1];
str3[0] = '\n';
strcat(str3, str1);
strcat(str3, str2);
cout << str3 << endl;
return 0;
}
【解析】
代码第12行和第13行调用strcat函数。strcat函数是库函数,其原型如下。
extern char *strcat(char *dest, const char *src);
它把src字符串加到dest字符串之后,dest字符串结束会的位置就是新连接的src的位置。然而代码第10行中
用new申请的堆内存是没有被初始化的,内存中的值都是随机数。代码第12行调用strcat不能把str1的内容复制到堆内存块中,
并且会导致数组越界。同样的问题发生在代码第13行调用中。应在代码第11行把str3[0]赋为结束符'\0'。正确的代码应为:
#include <iostream>
using namespace std;
int main()
{
const char *str1 = "hello";
const char *str2 = "china";
char *str3 = NULL;
//这边用new申请的堆内存没有被初始化
str3 = new char[strlen(str1) + strlen(str2) + 1];
//str3[0] = '\n'; //上面的堆内存没有初始化,不含有字符串结束符,输出的是随机值。
str3[0] = '\0'; //str3[0]赋为结束符'\0',以便strcat能正常调用。
strcat(str3, str1); //str3变为"hello"
strcat(str3, str2); //str3变为"hello china"
cout << str3 << endl;
return 0;
}
程序执行结果:
【答案】
原题中str3指向的堆内存没有初始化,不含有字符串结束符。输出是随机值。
面试题28 编程实现库函数strcat
【解析】
库函数strcat是把一个字符串内容连接到目标字符串的后面,所以应该从目标字符串的末尾,也就是结束符'\0'的位置插入另一个字符串的内容。
#include <stdio.h>
#include <stdlib.h>
char *mystrcat(char *dest, const char *src)
{
char *ret;
ret = dest; //保存目的字符串首地址以便返回
while (*dest++);
dest--; //此时dest指向字符串结束符
while (*dest++ = *src++); //循环复制
return ret;
}
int main()
{
char *dest = NULL;
char *str1 = "Hello ";
char *str2 = "World!";
dest = (char *)malloc(256);
*dest = '\0'; //为把目标字符串置为空,将结束符放在其开头
mystrcat(mystrcat(dest, str1), str2); //链式表达式连接str1和str2
printf("dest:%s\n", dest);
free(dest);
dest = NULL;
return 0;
}
程序执行结果:
面试题29 编程计算含有汉字的字符串长度
编写gbk_strlen函数,计算含有汉字的字符串的长度,汉字作为一个字符处理;已知:汉字编码为双字符,其中首字节<0,
尾字节在0~63以外(如果一个字节是-28~127)。
【解析】
程序代码如下:
#include <iostream>
using namespace std;
int gbk_strlen(const char *str)
{
const char *p = str; //头节点,p也用于后面的遍历
//使用两个指针指向的地址之差来获取字符串长度
while (*p) //若是结束符0,则结束循环
{
//汉字编码为双字节,其中首字节<0,尾字节在0~63以外
if (*p < 0 && (*(p + 1) < 0 || *(p + 1) > 63)) //中文汉字情况
{
str++; //str移动一位,p移动两位,因此长度+1
p = p + 2;
}
else
{
p++; //str不动,p移动一位,长度加1
}
}
return p - str; //返回地址之差
}
int main()
{
char str[] = "abc你好123中国456";
int len = gbk_strlen(str);
cout << str << endl;
cout << "len = " << len << endl;
return 0;
}
gbk_strlen()函数中,使用了两个指针指向的地址之差来获得字符串长度。当遇到中文汉字时,由于中文汉字占两个字节,
因此p移动两个指向中文汉字的后一个字符;而同时为了使汉字的长度算1个,则需要将src移动一位。程序执行结果如下。
面试题30 找出01字符串中0和1连续出现的最大次数
【解析】
程序代码如下。
#include <iostream>
using namespace std;
void Calculate(const char *str, int *max0, int *max1)
{
int temp0 = 0; //保存连续是'0'的最大长度
int temp1 = 0; //保存连续是'1'的最大长度
while (*str) //遍历字符串
{
if (*str == '0') //当前字符为‘0’
{
(*max0)++; //‘0’的计算长度加1
if (*(++str) == '1') //如果下一个字符是'1'
{
if (temp0 < *max0) //判断当前最大长度是否需要保存
{
temp0 = *max0;
}
*max0 = 0;
}
}
else if (*str == '1') //当前字符是‘1’
{
(*max1)++; //‘1’的计算长度加1
if (*(++str) == '0') //如果下一个字符是‘0’
{
if (temp1 < *max1) //判断当前最大长度是否需要保存
{
temp1 = *max1;
}
*max1 = 0;
}
}
}
*max0 = temp0; //‘0’的最大长度返回max0
*max1 = temp1; //‘1’的最大长度返回max1
}
int main()
{
char string[] = "00001110110000001100110101101001010101011111010";
int max0 = 0; //保存连续是'0'的最大长度
int max1 = 0; //保存连续是'1'的最大长度
Calculate(string, &max0, &max1);
cout << "max0 = " << max0 << endl;
cout << "max1 = " << max1 << endl;
return 0;
}
程序输出结果:
面试题31 编程实现字符串的替换
用C++写一个小程序,先请用记输入3个字符串,然后把在第一个字符串中出现的所有第2个字符替换成第3个字符串,最后输出新的字符串。
【解析】
程序代码如下:
#include <iostream>
using namespace std;
//str为原字符串,sub1:为第2个字符串,sub2为第3个字符串,output为结果
char *replace(const char *str, const char *sub1, const char *sub2, char *output)
{
char *pOutput = NULL;
const char *pStr = NULL;
int lenSub1 = strlen(sub1); //子串sub1的长度
int lenSub2 = strlen(sub2); //子串sub2的长度
pOutput = output;
pStr = str; //用于寻找子串
while (*pStr != 0)
{
pStr = strstr(pStr, sub1); //在str中寻找sub1子串
if (pStr != NULL) //找到sub1子串
{
memcpy(pOutput, str, pStr - str); //复制str的前一部分output
pOutput += pStr - str;
memcpy(pOutput, sub2, lenSub2); //复制sub2子串到output
pOutput = pOutput + lenSub2;
pStr = pStr + lenSub1; //为了下一次复制做准备
str = pStr;
}
else //找不到sub1子串
{
break;
}
}
*pOutput = '\0';
if (*str != '\0')
{
strcpy(pOutput, str); //复制str剩余部分到output
}
return output;
}
int main()
{
char str[50] = ""; //源字符串str
char sub1[10] = ""; //被替换的字符串sub1
char sub2[10] = ""; //用来替换sub2
char output[100] = ""; //输出字符串
cout << "str:";
cin >> str;
cout << "sub1:";
cin >> sub1;
cout << "sub2:";
cin >> sub2;
cout << replace(str, sub1, sub2, output) << endl;
return 0;
}
程序执行结果:
replace()函数把str中的所有sub1子串替换为sub2子串,结果存入output指向的内存块中。它循环使用了库函数strstr寻找字符串中的子串,如果找到sub1子串,就往output复制str中不属于sub1子串的部分以及sub2字符串。如果找不到sub1子串,则退出循环,
并且把str剩余的部分复制到output。