第4章 字符串2

面试题11 编程实现计算字符串的长度

【解析】
这个题目非常简单。我们知道字符串是以'\0'作为结束符的,所以只需要做一次遍历就可以了。但是需要注意的是,要尽量把程序写得简单且效率高。看下面的示例代码:

#include <stdio.h>
#include <assert.h>

int strlen1(const char* src)
{
    assert(NULL != src);                    //src必须有效
    int len = 0;                            //保持src首地址
    while (*src++ != '\0')                  //遇到结束符'\0'时退出循环
        len++;                              //每循坏一次,len加1
    return len;
}

int strlen2(const char* src)
{
    assert(NULL != src);                    //src必须有效
    const char *temp = src;                 //保存src首地址
    while (*src++ != '\0');                 //遇到结束符'\0'时退出循环
    return (src - temp - 1);                //返回尾部指针与头指针之差,即长度
}

int main()
{
    char p[] = "Hello World!";
    printf("strlen1 len:%d\n", strlen1(p)); //打印方法1得到的结果
    printf("strlen2 len:%d\n", strlen2(p)); //打印方法2得到的结果

    return 0;
}


strlen1和strlen2这两个函数都可以用来计算字符串长度。下面来比较它们的区别:
strlen1用一个局部变量len在遍历的时候做自增,然后返回len。因此,第当while循环一次,就需要执行两次自增操作。
strlen2用一个局部变量temp记录src遍历前的位置。while循环一次只需要一次自增操作。最后返回指针之间的位置差。
显示,strlen2比strlen1的效率要高,尤其是在字符串较长的时候。下面是程序的输出结果。


面试题12 编程实现字符串中子串的查找

请写一个函数,实现strstr,即从一个字符串中,查找另一个字符串的位置,如strstr("12345", "34")返回值为2,在2号位置找到字符串34.
【解析】
程序如下所示。

#include <stdio.h>
#include <assert.h>

const char *strstr(const char* src, const char* sub)
{
    const char *bp;
    const char *sp;

    if (src == NULL || sub == NULL ) //判断src与sub的有效性
    {
        return src;
    }
    while (*src) //遍历src字符串,也就是src的子串挨个与sub比较
    {
        bp = src;	//用于src的遍历
        sp = sub;	//用于sub的遍历
        do
        {	//遍历sub字符串
            if (!*sp)//如果到了sub字符串结束符位置
                return src;//表示找到了sub字符串,退出
        } while (*bp++ == *sp++);
        src += 1;
    }

    return NULL;
}

int main()
{
    char p[] = "12345";
    char q[] = "34";
    char *r = strstr(p, q);
    printf("r:%s\n", r);

    return 0;
}


main函数中的测试结果为:


可以看出,第32行调用strstr结束之后,r指向了数组p的第3个元素。这里strstr函数的方法是循环取src的子串也sub比较。以本题中的“12345”和“34”炎例,
比较步骤如下。
(1)“12345”和“34”比较,不满足匹配。
(2)“2345”和“34”比较,不满足匹配。
(3)“345”和“34”比较,满足匹配。


面试题13 编程实现字符串中各单词的翻转

编写函数,将“I am from Shanghai”倒置为"Shanghai from am I",即句子中的单词位置倒置,而不改变单词内部的结构。
【解析】
第一种方法的代码如下。

#include <iostream>
using namespace std;

void RevStr(char *src)
{
    char *start = src, *end = src, *ptr = src;

    while (*ptr++ != '\0')                      //遍历字符串
    {
        if (*ptr == ' ' || *ptr == '\0')        //找到一个单词
        {
            end = ptr - 1;                      //end指向单词末尾
            while (start < end)
                swap(*start++, *end--);         //把单词的字母逆置

            start = end = ptr + 1;              //指向下一个单词开头,单词之间间隔是空格不是'\0'
        }
    }
    start = src, end = ptr - 2;                 //start指向字符串开头,end指向字符串末尾
    while (start < end)                         //把整个字符串逆置
    {
        swap(*start++, *end--);
    }
}

int main()
{
    char src[] = "I am from Shanghai";
    cout << src << "\n";
    RevStr(src);
    cout << src << "\n";

    return 0;
}


程序输出结果:


第一种方法RevStr函数有两个转换步骤:代码第8~18行将src中所有的单词进行翻转,其结果是src的内容变为"I ma morfiahgnahS",然后代码第20~23行再进行全局翻转。
第二种方法的代码如下。

#include <iostream>
using namespace std;

void RevStr(char *src)
{
    char *start = src, *end = src, *ptr = src;

    while(*ptr++ != '\0');
    end = ptr - 2;                      //找到字符串末尾
    while (start < end)
    {
        swap(*start++, *end--);         //逆置整个字符串
    }

    start = src, end = ptr - 2;
    ptr = start;
    while (*ptr++ != '\0')
    {
        if(*ptr == ' ' || *ptr == '\0') //找到单词
        {
            end = ptr - 1;              //end指向单词末尾
            while(start < end)
                swap(*start++, *end--); //逆置单词

            start = end = ptr + 1;      //指向下一个单词开头
        }
    }
}

int main()
{
    char src[] = "I am from Shanghai";
    cout << src << "\n";
    RevStr(src);
    cout << src << "\n";

    return 0;
}


程序输出结果:

第二种方法RevStr函数转换步骤与第一种方法相反:代码第10~11行将src进行全局翻转。其结果是src的内容变为“iahgnahS morf ma I”,然后代码第17~27行再把所有的单词进地翻转。从上面的代码分析可以看出,两种方法都是采用两个步骤,即字会串全局翻转和各个单词局部翻转,只是步骤顺序不同,得到的结果都是一致的。


面试题14 编程判断字符串是否为回文

判断一个字符串是不是回文,例如单词"level"是回文。
【解析】
根据题目要求,我们可以从一个字符串的两端进行遍历比较。例如,对于"level"字符串,我们可以进行下面的步骤。
(1)计算需要比较的次数。由于“level”字符串长度为5,是奇数,因此比较两次。
(2)第一次比较:看"level"的第一个字符与最后一个字符是否相等,若相等,则进行第二次比较。
(3)第二次比较:看"level"的第二个字符与倒数经二个字符是否相等,若相等,则是回文。
如果在上面的比较过程中有一个不相等,则字符串不是回文。根据上面的思路,我们可以写出如下的程序代码。

#include <iostream>
using namespace std;

int IsRevStr(char *str)
{
    int i, len;
    int found = 1;                              //1表示回文字符串,0表示不是

    if (str == NULL)                            //判断str的有效性
    {
        return -1;
    }
    len = strlen(str);                          //获取字符串长度
    for (i = 0; i < len / 2; i++)
    {
        if (*(str + i) != *(str + len - i - 1)) //遍历中如果发现相应的头尾字符不等,则字符串不是回文字符串
        {
            found = 0;
            break;
        }
    }
    return found;
}

int main()
{
    char str1[10] = "1234321";                  //回文字符串
    char str2[10] = "1234221";                  //非回文字符串

    int test1 = IsRevStr(str1);                 //测试str1是不是回文
    int test2 = IsRevStr(str2);                 //测试str2是不是回文

    cout << "str1 is " << (test1 == 1 ? "" : " not") << " reverse string." << endl;
    cout << "str2 is " << (test2 == 1 ? "" : " not") << " reverse string." << endl;

    return 0;
}


这个程序中的IsRevStr()函数用于判断字符串是否是回文字符串,如果是,则返回1,否则返回0。输出结果:


面试题15 编程实现stcmp库函数

【解析】
此题实际上就是做一个C/C++库函数中的strcmp的实出,对于两个字符串str1和str2,若相等,则返回0,若str1大于str2,则返回1,
若str1小于str2,则返回-1.
程序代码如下。

#include <iostream>
using namespace std;

int mystrcmp(const char *src, const char *dst)
{
    int ret = 0;
    while (!(ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
    {                                   //循环比较两个字符是否相等,
        ++src;                          //如果不等或者到了dst字符的末尾,
        ++dst;                          //则退出循环
    }
    if (ret < 0)                        //ret保存着字符比较的结果
        ret = -1;
    else if (ret > 0)
        ret = 1;
    return ret;
}

int main()
{
    char str[10] = "1234567";
    char str1[10] = "1234567";          //str1==str
    char str2[10] = "12345678";         //str2 > str1
    char str3[10] = "1234566";          //str3 < str

    int test1 = mystrcmp(str, str1);    //测试str与str1比较
    int test2 = mystrcmp(str, str2);    //测试str与str2比较
    int test3 = mystrcmp(str, str3);    //测试str与str3比较

    cout << "test1= " << test1 << endl;
    cout << "test2= " << test2 << endl;
    cout << "test3= " << test3 << endl;

    return 0;
}


mystrcmp()函数对src和dst两个字符串同时进行了一次遍历,当发现它们存在不同值时停止循环,最后根据它们的最后一个字符的大小,返回相应的结果。程序输出结果:

 


面试题16 编程查找两个字符串的最大公共子串

【解析】
对于两个字符串A和B,如果A=“aocdfe”, B="pmcdfa",则输出“cdf”.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *commonstring(char *str1, char *str2)
{
    int i, j;
    char *shortstr, *longstr;
    char *substr;

    if (str1 == NULL || str2 == NULL)               //判断两个字符的有效性
    {
        return NULL;
    }

    if (strlen(str1) <= strlen(str2))               //shortstr和longstr分别指向较短和较长的字符串
    {
        shortstr = str1;
        longstr = str2;
    }
    else {
        shortstr = str2;
        longstr = str1;
    }

    if (strstr(longstr, shortstr) != NULL)          //如果在较长的字符串中找到较小的字符串,
    {                                               //则返回短字符串
        return shortstr;
    }

    substr = (char *)malloc(sizeof(char)*(strlen(shortstr) + 1));//申请 堆内存 存放返回结果
    //循环取短串的子串放入堆内存,调用strstr函数检查长串中是否包含这个子串,如果有,则返回堆内存。这个短串长度不断减少的
    for (i = strlen(shortstr) - 1; i > 0; i--)
    {
        for (j = 0; j <= strlen(shortstr) - i; j++)
        {
            memcpy(substr, &shortstr[j], i);        //将短字符串的一部分复制到substr,
            substr[i] = '\0';                       //其长度逐渐减小
            if (strstr(longstr, substr) != NULL)    //如果在lognstr中找到substr,则返回substr
                return substr;
        }
    }

    return NULL;
}

int main()
{
    char *str1 = (char *)malloc(256);               //分配堆内存存放字符串str1和str2
    char *str2 = (char *)malloc(256);
    char *common = NULL;

    gets_s(str1,20);                                //从终端输入str1和str2
    gets_s(str2,20);

    common = commonstring(str2, str1);              //取最大的相同子串

    printf("the longest common string is:%s\n", common);

    return 0;
}


为了方便,我们可以利用库函数strstr中一个字符串寻找子串。这个程序的步预如下。
(1)代码第11~14行,检查参数str1和str2的有效性。
(2)计算两个字符串的长短,这样在调用strstr时就会比较方便。
(3)调用strstr直接进行两个字符串的整串比较,如果不为NULL,说明短串被长串所包含,直接返回短串即可,否则进行下一步。
(4)申请一块大小为短串长度加1的堆内存,这块内存用于保存最大公共子串。
(5)循环取短串的子串放入串内存,调用strstr函数检查长串中是否包含这个子串,如果有,则返回堆内存。注意短串的长度是不断减小的。
下面是程序执行结果。


面试题17 不使用printf,将十进制数以二进制和十六进制的形式输出

【解析】
如果不能使用printf系列库函数,我们可以通过位运算得到这个十进制数的二进制和十六进制形式的字符串,再将字符串打印。源代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//
char *get2String(long num)                  //得到二进制形式的字符串
{
    int i = 0;
    char *buffer;
    char* temp;
    //long型整数是4个字节,每个8位,一共32位。申请了33个字节(包括'\0')的堆内存存放结果
    buffer = (char*)malloc(33);
    temp = buffer;                          //temp
    for (i = 0; i < 32; i++)
    {                                       //给数组的32个元素赋值0或者1
        temp[i] = num&(1 << (31 - i));
        temp[i] = temp[i] >> (31 - i);
        temp[i] = (temp[i] == 0) ? '0' : '1';
    }
    buffer[32] = '\0';                      //字符串结束符
    return buffer;
}

char *get16String(long num)                 //得到十六进制形式的字符串
{
    int i = 0;
    char *buffer = (char*)malloc(11);
    char *temp;

    buffer[0] = '0';
    buffer[1] = 'x';
    buffer[10] = '\0';                      //字符串结束符
    temp = buffer + 2;

    for (i = 0; i < 8; i++)
    {                                       //给数组的8个元素赋值
        temp[i] = (char)(num << 4 * i >> 28);
        temp[i] = temp[i] >= 0 ? temp[i] : temp[i] + 16;
        temp[i] = temp[i] < 10 ? temp[i] + 48 : temp[i] + 55;
    }
    return buffer;
 }

int main()
{
    char *p = NULL;
    char *q = NULL;
    int num = 0;

    printf("input num:");                   //输入整数
    scanf_s("%d", &num);

    p = get16String(num);                   //得到十六进制的字符串
    q = get2String(num);                    //得到二进制形式的字符串

    printf("%s\n", p);
    printf("%s\n", q);

    return 0;
}


这个程序中,get2String和get16String分别用于得到二进制字符串和十六进制字符串。long型的整数是4个字节,32位,每一位用0或1表示。
get2String函数申请了33个字节(包括'\0')的堆内存存放结果,get16String函数申请了11个字节(包括‘0’,‘X’和‘\0’)。然后把每个位的值赋给堆内存的对应位置。
程序执行结果:


面试题18 编程实现转换字符串、插入字符的个数

【解析】
根据题意,需要在字符串插入字符统计的个数。例如字符串aaab,插入字符个数后变成aaa3b1。源程序如下。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXCOUNT 2*100

char *transformation(char *str)
{
    int len = strlen(str);
    char *buf = new char[len + 1];          //申请llen(str)+1个堆内存来存放字符串数字相关的信息

    char *p = str;
    char *q = p + 1;
    int count = 1;
    while (*q)
    {                                       //如果前一个和后一个相等,指针后移,计数增加
        if (*p == *q)
        {
            count++;
            p++;
            q++;
        }
        else
        {                                   //如果不等
            itoa(count, buf, 10);           //把整数转成字符串
            int nbits = strlen(buf);
            strcat(buf, q);                 //buf里面保存3b
            *q = 0;
            strcat(str, buf);               //str里面保存aaa
            q = q + nbits;
            p = q;
            q = p + 1;
            count = 1;
        }
    }

    itoa(count, buf, 10);
    strcat(str, buf);

    delete[]buf;                            //释放堆内存
    buf = NULL;
    return str;
}

int main()
{
    char str[MAXCOUNT];

    printf("please input a string:");
    scanf("%s", &str);
    printf("before transformation:%s\n", str);
    char *pstr = transformation(str);
    printf("after transformation:%s\n", pstr);

    return 0;
}


这个程序的transformation()函数用来转换字符串。我们以字符串aaab为例来说明其执行过程。为了计算方便,首先申请了5个字节的堆内存buf(aaab长度为4,加一个结束符为5个)来存放字符串数字相关的 信息。初始计数为1,然后进行下面的步骤:
遍历aaab,直到找到不同的字符,然后在buf中保存3b,把str变为aaa(字符‘b’位置内存设为0)。然后执行strcat(str, buf),此时str变为aaa3b,计数设为1。
如果到字符串末尾(碰到结束符'\0'),则退出循环,否则继续进行以上的步骤。
如果退出循环,则将最后一个字符个数存入buf(这里为b的个数1),此时str中为aaa3b并且调用strcat(str, buf),结果str变为aaa3b1。
释放buf堆内存并返回str.程序执行结果为:

 


面试题19 字符串编码例题

Give an implementation of encoding a string which contains less than 20 chars. There three rules:


【解析】
下面是原题的中文翻译。
对一个长度小于20的字符串进行编码,遵循3个规则:
(1)把字符串中的字母替换成它的第4个字母。例如,a->e, A->E, X->b, y->c, z->d.
(2)如果字符不是字母,忽略替换。
(3)翻转整个字符串。
整个过程可以分为两部分:替换字符和翻转字符串,其中替换字符还包括一个检查的操作,即检查是否是在字母表中的字符,也就是英文的26个字符。程序示例:

#include <iostream>
using namespace std;

char LowerCaseAlphabets[] = {
    'a','b','c','d','e','f','g','h',
    'i','j','k','l','m','n','o','p',
    'q','r','s','t','u','v','w','x','y','z'};

char UpperCaseAlphabets[] = {
    'A','B','C','D','E','F','G','H',
    'I','J','K','L','M','N','O','P',
    'Q','R','S','T','U','V','W','X','Y','Z'};

char GetFourthChar(char chrSource, char alphabets[])
{
    for (int i = 0; i < 26; i++)
    {
        if (alphabets[i] == chrSource)
        {
            int index = (i + 4) % 26;
            return alphabets[index];
        }
    }
    return '\0';
}

void ReplaceChars(char chars[], int len)
{
    for (int i = 0; i < len; i++)
    {
        if ('a' <= chars[i] && chars[i] <= 'z')
        {
            chars[i] = GetFourthChar(chars[i], LowerCaseAlphabets);
        }
        else if('A' <= chars[i] && chars[i] <= 'Z')
        {
            chars[i] = GetFourthChar(chars[i], UpperCaseAlphabets);
        }
    }
};
void ReverseString(char str[], int len) //翻转整个字符串
{
    int begin = 0, end = len - 1;
    if (str[end] == '\0')
        end--;

    char hold;
    while (begin < end)
    {
        hold = str[begin];
        str[begin] = str[end];
        str[end] = hold;

        begin++;
        end--;
    }
};

void EncodeString(char str[], int len)
{
    ReplaceChars(str, len);             //替代字符
    ReverseString(str, len);            //翻转整个字符串
}

int main()
{
    char hello[] = "hasd11H";

    EncodeString(hello, strlen(hello));
    cout << hello << endl;

    return 0;
}


代码第61~62行,EncodeString()函数调用了ReplaceChars()函数和ReverseString()函数,前者替代字符,后者翻转整个字符串。
ReplaceChars()函数调用GetFourthChar()函数来查找后面第四个字符并替换。GetFourthChar()函数的实现非常简单,它使用查找两个全局数组,这两个数组分别包含了所有的大小写字母。
ReverseString()函数里使用了begin和end两个分别指向字符串头尾的指针。然后头尾的内容不断交换,begin指针往尾移动,end指针往头移动
如此循环,直到两个指针碰砂。程序执行结果如下。


面试题20 反转字符串,但其指定的子串不反转

给定一个字符串、一个这个字符串的子串,将第一个字符串反转,但保留子串的顺序不变。例如


【解析】
对于本题,采取的一般步骤为:
(1)扫描一遍第一个字符串,然后用stack把它反转,同时记录下子串出现的位置。
(2)扫描一遍把记录下来的子串再用stack反转。
(3)将堆栈里的字符弹出,这样子子串又恢复了原来的顺序。
这里使用一遍扫描数组的方法。扫描中如果发现子串,就将子串倒过来压入堆栈。最后将堆栈里的字符弹出,这样子又恢复了原来的顺序。C++标准库中的stack表示一个后进先出的栈结构,使用stack可以轻松地完成这个转换。源程序如下。

#include <iostream>
#include <cassert>
#include <stack>
using namespace std;

const char* reverse1(const char* s1, const char* token)
{                                   //这边s1是字符串,token是字符串的子串
    stack<char> stack1;             //这边建立一个堆
    const char* ptoken = token, *head = s1, *rear = s1;
    assert(s1 && token);
    while (*head != '\0')
    {                                       //表示该字符串没有到头
        while (*head != '\0'&&*ptoken == *head)
        {                                   //表示字符串里面与子串相等
            ptoken++;
            head++;
        }
        if (*ptoken == '\0')                //这边表示子串已经走到最后了,走到最后则将它倒过来压入堆栈
        {
            const char* p;
            for (p = head - 1; p >= rear; p--)
            {
                stack1.push(*p);
            }
            ptoken = token;
            rear = head;
        }                                   //否则直接压入堆栈
        else
        {
            stack1.push(*rear++);
            head = rear;
            ptoken = token;
        }
    }
    char *pReturn = new char[strlen(s1) + 1];
    int i = 0;
    while (!stack1.empty())
    {
        pReturn[i++] = stack1.top();
        stack1.pop();
    }
    pReturn[i] = '\0';

    return pReturn;
}

int main(int argc, char* argv[])
{
    char welcome[] = "Welcome you, my friend";	//字符串
    char token[] = "you";                       //字符串的子串
    const char *pRev = reverse1(welcome, token);

    cout << "before reverse:" << endl;
    cout << welcome << endl;
    cout << "after reverse:" << endl;
    cout << pRev << endl;

    return 0;
}


reverse()函数中,代码第13~17行搜索字符串的字符串位置。其中指针ptoken记录当前扫描的子串位置,如果其内容为结束符'\0'
(代码第18~27行),那么已经搜索到了这个子串,于是将它倒过来压入堆栈,否则直接压入堆栈(第码第28~33行)。最后申请一块堆内存,使用退栈把栈中内容存入堆内存中并返回堆内存。程序执行结果:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值