第4章 字符串3

面试题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。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值