字符串类编程题

1. 替换空格

实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
思路
首先要确定是不是在本身上修改,如果是,则要题目保证内存足够。否则,可以考虑自己new空间。
代码

class Solution {
public:
    void replaceSpace(char *str, int length) {
          if (str == nullptr || length < 0) return ; //首要判断字符串是否为空串,满足一个条件即为空串
          int counter = 0;
          for (int i=0; i < length; i++)
                if(str[i]==' ')  counter++;  //先判断字符串中有多少个空格字符,从而防止进行下一步操作时出现越界现象
           // 假设是在原有的基础上进行替换,且保证内存足够
          int newlength = length + counter*2;//替换空格后的字符串长度
          int p = length;
          while(newlength >= 0)
          {
              if(str[p] != ' ')
                  str[newlength--] = str[p--];
              else
              {
                  str[newlength--] = '0';
                  str[newlength--] = '2';
                  str[newlength--] = '%';
                  p--;
              }
          }   
    }
};

2.字符串的排列

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

思路:本题求整个字符串的全排列可以看做两步
1)首先求出所有可能出现在第一位置的字母,即begin与后面所有与它不同的字母进行交换
2)固定第一个字母,求后面字母的全排列,即递归此时begin = begin+1

在这里插入图片描述
代码

class Solution {
public:
    vector<string>  (string str) {
        vector<string> res;//函数返回的是一个字符串类型的vector。
        if(str.size() <= 0)
            return res;
        Perm(res, str, 0); //从字符串数组的第0位开始
        sort(res.begin(), res.end()); //vector使用sort函数进行排序(按字典序),注意res.end指向的是最后一个元素指向的下一个位置
        return res;
    }
    void Perm(vector<string> &res, string str, int begin)//善于在参数中使用引用符号
    {
        //第一次遍历结束条件
        if(begin == str.size() - 1)
            res.push_back(str);
        for(int i=begin; i<str.size(); i++)
        {
            //有与begin位重复的字符串不进行交换,跳过
            if(i != begin && str[i] == str[begin])
                continue;
            swap(str, i, begin);
            Perm(res, str, begin+1);
            swap(str, i, begin);//为了防止重复的情况,还需要将begin处的元素重新换回来
        }
    }
    void swap(string &str, int i, int j)
    {
        char c;
        c = str[i];
        str[i] = str[j];
        str[j] = c;
    }
};

扩展
如果不是求字符的所有排列,而是求字符的所有组合,应该怎么办呢? 还是输入三个字符 a、b、c,则它们的组合有 a、b、c、ab、ac、bc、abc。 当交换字符串中的两个字符时,虽然能得到两个不同的排列,但却是同一个组合。比如 ab 和 ba 是 不同的排列,但只算一个组合。

思路: 如果输入 n 个字符,则这 n 个字符能构成长度为 1 的组合、长度为 2 的组合、….、长度为 n 的组合。在求 n 个字符的长度为 m ( 1<=m<=n) 的 组合的时候,我们把这 n 个字符分成两部分:第一个字符和其余的所有字符。如果组合里包含第一个字符,则下一步在剩余的字符里选取 m-1个字 符:如果组合里不包含第一个字符,则下一步在剩余的 n-1 个字符里选取 m 个字符。也就是说,我们可以把求 n 个字符组成长度为 m 的组合的问题分解成两个子问题,分别求 n-1 个字符串中长度为 m-1 的组合,以及求 n-1 个字符的长度为 m 的组合。这两个子问题都可以用递归的方式解决。

实现方法:位图法。
譬如选择表示为“1”,不选择表示为“0”,那么abc的组合方法有:001(a),010(b),011(ab),100(c),101(ac),110(cb),111(abc)。000是没选择,不行。共有2strlen(“abc”)−1中组合。

代码

#include<stdio.h>
#include<string.h>
void Combination(char *str)
{
    if(str == NULL)
        return ;
    int len = strlen(str);
    int n = 1<<len;//1左移len位,左移一位相当于乘以2,右移一位相当于除以2,求得的n表示共有几种组合
    int i=1;
    int j=0;
    for(i=1;i<n;i++)    //求长度为i的组合个数
    {
        for(j=0;j<len;j++)
        {
            int temp = i;
            if(temp & (1<<j))   //对应位上为1,则输出对应的字符,与运算符和求相应数减1的模相等价,如2&1等价于2%2.
            {
                printf("%c",*(str+j));
            }
        }
        printf("\n");
    }
}

3. 字符串中第一个只出现一次的字符

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

思路:最直观的想法是从头开始扫描这个字符串中的每个字符。当访问到某字符时拿这个字符和后面的每个字符相比较,如果在后面没有发现重复的字符,则该字符就是只出现一次的字符。如果字符串有n个字符,每个字符可能与后面的O(n)个字符相比较,因此这种思路的时间复杂度是O(n2)

**以空间换时间:**定义一个哈希表(外部空间),其键值(Key)是字符,而值(Value)是该字符出现的次数。
 我们需要从头开始扫描字符串两次:
(1)第一次扫描字符串时,每扫描到一个字符就在哈希表的对应项中把次数加1。(时间效率O(n))
(2)第二次扫描时,每扫描到一个字符就能从哈希表中得到该字符出现的次数。这样第一个只出现一次的字符就是符合要求的输出。(时间效率O(n))
(由于256个ASCII字符,空间长度为常数,所以空间复杂度为O(1)).

代码

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        if(str.length() == 0) return -1;
        int hash[256] = {0};
        for(int i = 0; i < str.length(); i++)
        {
            hash[str[i]] ++;
        }
        for(int i = 0; i < str.length(); i++)
            if(hash[str[i]] == 1)
                return i;
        return -1;
    }
};

4. 在字符流中第一个只出现一次的字符

请实现一个函数,用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符go时,第一个只出现一次的字符是g;当从该字符流中读出前6个字符google时,第一个只出现一次的字符是l。

思路
字符只能一个一个从字符流中读出来,因此要定义一个容器来保存字符以及其在字符流中的位置。
由于要在O(1)时间内往数据容器中插入字符,及其对应的位置,因此这个数据容器可以用哈希表来实现,以字符的ASCII码作为哈希表的键值key,字符对应的位置作为哈希表的值value。
开始时,哈希表的值都初始化为-1,当读取到某个字符时,将位置存入value中,如果之前读取过该字符(即value>=0),将value赋值为-2,代表重复出现过。最后对哈希表遍历,在value>=0的键值对中找到最小的value,该value即为第一个只出现一次的字符,ASCII码为key的字符即为所求字符。

代码

class Solution
{
private:
    int occ[256];
    int index;
public:
    //无参数的构造函数
    Solution(){
        index = 0;
        for(int i = 0; i < 256; i++)
            occ[i] = -1; // -1没出现过 -2出现过 >0出现顺序
    }
    //Insert one char from stringstream
    void Insert(char ch)
    {
        if(occ[ch] == -1) occ[ch] = index;//存放的是该字符在字符流中对应字符的位置
        else if(occ[ch] >= 0) occ[ch] = -2;//表示该字符重复出现
        index++;
    }
    //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
        int minIndex = -1;
        char ch;
        for(int i = 0; i < 256; i++)
        {
            if(occ[i] >= 0)
            {
                if(minIndex == -1 || minIndex > occ[i])
                    minIndex = occ[i], ch = i;
            }
        }
        if(minIndex == -1) return '#';
        return ch;
    }
};

5 . 左旋转字符

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

思路:逆置三次即可。逆置可采用交换方法。

代码

class Solution {
public:
    string LeftRotateString(string str, int n) {
        if(str.length() <= 0) return "";
        reverse(str, 0, n-1);
        reverse(str, n, str.length()-1);
        reverse(str, 0, str.length()-1);
        return str;
    }
    //采用swap方法进行逆置
    void reverse(string &str, int begin, int end)
    {
        char tmp;
        while(begin < end)
        {
            tmp = str[begin];
            str[begin] = str[end];
            str[end] = tmp;
            begin ++;
            end --;
        }
    }
};

附加题:反转数字

将给出的整数x翻转。
例1:x=123,返回321
例2:x=-123,返回-321

你有思考过下面的这些问题么?
如果整数的最后一位是0,那么输出应该是什么?比如10,100
你注意到翻转后的整数可能溢出吗?假设输入是32位整数,则将翻转10000000003就会溢出,你该怎么处理这样的样例?抛出异常?这样做很好,但是如果不允许抛出异常呢?这样的话你必须重新设计函数(比如添加一个额外的参数)。
示例1
输入

-123

输出

-321

代码

class Solution {
public:
    /**
     * 
     * @param x int整型 
     * @return int整型
     */
    int reverse(int x) {
        // write code here
        long long res = 0;
        while(x)
        {
            res = res* 10 + (x % 10);
            x = x/10;
        }
        //处理异常
        return (res < INT_MIN || res > INT_MAX) ? 0 : res;
    }
};

6. 翻转单词顺序

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

思路:和上题一样,先整体翻转,然后在对每一个单词进行翻转(以空格为标识)

代码

class Solution {
public:
    string ReverseSentence(string str) {
        reverse(str, 0, str.length()-1);
        int begin = 0;
        //去除前置空格,注意for语句中的判断语句,两个条件不能拆开
        for(int i = 0; i < str.length() && str[i] == ' '; i++) 
            begin ++;
        for(int i = 0; i < str.length(); i ++)
        {
            if(str[i] == ' ')
            {
                reverse(str, begin, i-1);
                begin = i + 1;
                // 去除多个空格情况
                while(begin < str.length() && str[begin] == ' ') 
                    begin ++;
            }
        }
        int end = str.length() - 1;
        // 去除末尾多余空格
        while(end < str.length() && str[end] == ' ') 
            end --;
        if(begin < end) 
            reverse(str, begin, end);
        return str;
    }
    void reverse(string &str, int begin, int end)
    {
        char tmp;
        while(begin < end)
        {
            tmp = str[begin];
            str[begin] = str[end];
            str[end] = tmp;
            begin ++;
            end --;
        }
    }
};

7. 把字符串转换成数字

将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0

思路:如果字符串能转化成符合题意的整数,那么字符串中的字符肯定实在’0’ ~'9’之间的,符号 + 或者 - 如果出现的话,那么肯定是出现的字符串的第一个元素的位置,如果出现在字符串的中间的某个位置,这个字符串就不可以转化为数字。

再有就是:res = (res << 1) + (res << 3) + (str[i] & 0xf);
对这句话的解释: (res << 1) + (res << 3),左移一位是乘以2,左移3位是乘以8,加起来相当于res*10;
(str[i] & 0xf) 相当于 str[i] - ‘0’,为什么呢?因为 字符’0’ ~ ‘9’ 的SACII码值的低四个二进制位就是 0 ~ 9, 0xf 是数字15,二进制位1111,这样操作之后就可以将 ‘0’ 转化为0,…,将‘9’转化为‘9’。

整体看这句话:res = res * 10 + str[i] - ‘0’。例如字符串为‘123’;
那么单步运行:res = 1; res = 1 * 10 + 2 = 12; res = 12 * 10 + 3 = 12。这句话就是这个意思。

代码

class Solution {
public:
    int StrToInt(string str) {
       int len = str.size();
        if(len <= 0)
            return 0;
        int symbol = 1;
        long long res = 0;
        if(str[0] == '-')
            symbol = -1;
        for(int i = (str[0] == '+' || str[0] == '-')? 1 : 0; i < len; ++ i)
        {
            if(str[i] >= '0' && str[i] <= '9')
                res = (res << 1) + (res << 3) + (str[i] & 0xf);
            else
                return 0;
        }
        return res * symbol;
    }
};

8 . 扑克牌顺子

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

思路
先不去考虑能否构成顺子,有一些先决条件必须满足,否则判断顺子无从谈起:
(1)一共只有5张牌 <=>数组大小为5
(2)为了方便起见,牌的面值从0到13,在这之外的,就是不对的 <=>元素值取值[0,13]
(3)大小王充当癞子使用,不允许出现对子 <=>满足前两条后,除了0以外,不允许出现重复的,而且0不得出现5次

代码

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        //数组大小为5
        if(numbers.size() != 5)
            return false;
        sort(numbers.begin(), numbers.end());
        int numOfZero = 0, numOfInterval = 0;
        for(int i = 0; i < numbers.size() - 1; i ++)
        {
            //牌的面值范围在[0,13]
            if(numbers[i] < 0 || numbers[i] > 13)
                return false;
            //统计大王小王(即牌为0)的个数
            if(numbers[i] == 0)
            {
                numOfZero ++;
                continue;
            }
            //出现重复非0牌值,则不能构成顺子
            if(numbers[i] == numbers[i+1]) 
                return false;
            //最大值和最小值之间差多少(由于排好序的,注意for循环中的i条件是小于该数组长度-1,即循环体只需要执行numbers.size()-1次[0,number.size()-1))
            numOfInterval += numbers[i+1] - numbers[i] - 1;
        }
        return numbers[4] !=0 && (numOfZero >= numOfInterval ? true : false);
    }
};

9. 正则表达式

请实现一个函数用来匹配包括’.‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配

思路
考虑号的情况。即 当(pattern+1) == ''的时候,由于可以匹配0次,故要么匹配,要么不匹配。如果不匹配的话,直接返回str,pattern+2,如果匹配,则一直匹配(str+1,pattern)或者只匹配这一次(str+1,pattern+2)即可
其他情况,正常来就行,当
str==*pattern就放行,或者为点的时候,但是点不能匹配空,所以要求点必须要匹配一个字符。

代码

class Solution {
public:
    bool match(char* str, char* pattern)
    {
        if(str == nullptr || pattern == nullptr)
            return false;
        if(*pattern == '\0') 
            return *str == '\0' ? true : false;
        if(*(pattern + 1) == '*')
        {
            if(*str == *pattern || *pattern == '.' && *str != '\0')
                //(1) 当前匹配为0;(2)当前匹配上了继续匹配;(3)当前匹配上了不继续匹配
                return  match(str, pattern+2) || match(str+1, pattern) || match(str+1, pattern+2);
            else
                //不满足if条件,肯定是不匹配的结果,则*前的字符出现的次数为0
                return match(str, pattern+2);
        }
        //*(pattern+1) != '*'的情况下,正常匹配即可,当*str==*pattern就放行,或者为点的时候,但是点不能匹配空,所以要求点必须要匹配一个字符。
        if(*str == *pattern || *pattern == '.' && *str != '\0')
            return match(str+1, pattern+1);
        return false;
    }
};

10. 表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。

思路
首先可以推出格式(±)(num1)(.num2)(Ee(num3)),其中num123均为1-9的数字串。当Ee存在时,后面的数字才能存在。

1、使用正则表达式处理
2、一步一步按格式处理

代码1

//使用c++中的正则表达式不要忘记其头文件(regex = regular experession)
#include<regex>
class Solution {
public:
    bool isNumeric(char* string)
    {
        regex pattern("[\\+\\-]?\\d*(\\.\\d+)?([eE][\\+\\-]?\\d+)?");
        return regex_match(string, pattern);
    }
};

[\+\-]? 正或负符号出现与否
* \d* 整数部分是否出现,如-.34 或 +3.34均符合
* (\.\d+)? 如果出现小数点,那么小数点后面必须有数字(*和+的区别,*可表示零次,+表示必须出现一次);否则一起不出现
* ([eE][\+\-]?\d+)? 如果存在指数部分,那么e或E肯定出现,+或-可以不出现,紧接着必须跟着整数;或者整个部分都不出现

代码2

class Solution {
public:
    bool isNumeric(char* str)
    {
        if(str == nullptr) return false;
        // (+-)(num1)(.num2)[Ee](+-)(num3)
        // 1 最开始的符号位
        if(*str == '+' || *str == '-') str++;
        // 2 num1,-.34 或 +3.34均符合
        while(*str != '\0')
        {
            if(*str < '0' || *str > '9') break;
            str++;
        }
        // 3 .num2
        if(*str == '.')
        {
            str++;
            // 类似12.是符合要求的
            while(*str != '\0')
            {
                if(*str < '0' || *str > '9') break;
                str++;
            }
        }
        // 4 Ee
        if(*str == 'E' || *str == 'e')
        {
            str++;
            // 类似12e不符合要求
            if(*str == '\0') return false;
            if(*str == '+' || *str == '-') str++;
            while(*str != '\0')
            {
                if(*str < '0' || *str > '9') break;
                str++;
            }
        }
        return *str == '\0';
    }
};

11. 字节跳动—编程2

有一个仅包含’a’和’b’两种字符的字符串s,长度为n,每次操作可以把一个字符做一次转换(把一个’a’设置为’b’,或者把一个’b’置成’a’);但是操作的次数有上限m,问在有限的操作数范围内,能够得到最大连续的相同字符的子串的长度是多少。

输入描述:

第一行两个整数 n , m (1<=m<=n<=50000),第二行为长度为n且只包含’a’和’b’
的字符串s。

输出描述:

输出在操作次数不超过 m 的情况下,能够得到的 最大连续 全’a’子串或全’b’子串的长度。

输入例子1:
8 1
aabaabaa

输出例子1:
5

例子说明1:
把第一个 'b' 或者第二个 'b' 置成 'a',可得到长度为 5 的全 'a' 子串。

思路: 该字符串非 a 即 b 也就是说在区间 l~r之间把所有字符变为 a 所需的步骤数是 该区间内 字符b 的数量。反之亦然.
用数组 count[i] 表示 字符串中位置区间 0~i 包含的 a 的个数,则 区间 l~r 的 a 的个数为 count[r] - count[l - 1]
b 的个数用 a 的个数算出 即 区间 l~r 的 b 的个数为 r + 1 - count[r] - (l + 1 - 1 - count[l - 1]) = r + 1 - l - count[r] + count[l - 1]
在区间 l~r 的 a 和 b 的个数已知的情况下
若 区间长度step内的 a 的个数 <= m 则 可以通过 m 个步骤 产生 长度为step的连续字符串 b
若 区间长度step内的 b 的个数 <= m 则 可以通过 m 个步骤 产生 长度为step的连续字符串 a

归纳为 :若 区间长度step内的字符 b 或字符 a 的个数 <= m 则 可以通过 m 个步骤 产生 长度为step的字符串(不管是全a还是全b)
这样 就可以直接计算出一个字符串长度(区间长度step)是否可行,因此不需要进行递推,可以直接进行二分搜索,得到最大长度。
检查一个长度step是否可行的时间复杂度为O(n),二分搜索的时间复杂度为O(log n)。
因此,该方法总的时间复杂度为 O(n*log n)
代码

#include<iostream>
#include<string>
#include<cstring>
using namespace std;
//数组区间
int count[50005];
int n,m; 
//检查当前区间长度(step)是否能在 m 个步骤内实现全 a 或 全 b 
bool func(int step)
{
    for(int i = 0;i + step < n;i++)
    {
        if(m >= step + 1 - (count[i + step] - count[i - 1]))
            return true;	//检查 a--》  公式 m>=区间内 b 的个数 
		if(m >= count[i + step] - count[i - 1])
            return true;	//检查 b--》  公式: m>=区间内 a 的个数 
    }
    return false;
}
 
int main()
{
    cin>>n>>m;
    string str;
    cin>>str;
    //输入并计算出 count 数组 
    int sum = 0;
    //统计0~i区间内的a的数量分别保存在每个count[i]位置
    for(int i = 0;i < str.size();i++)
    {
        if(str[i] == 'a')
            count[i] = ++sum;
        else 
            count[i] = sum;
    }
    //二分搜索最大区间值 
	int l = 0,r = n-1,mid; 
    while(l < r)
    {
    	//取中点,确定区间的大小
        mid = l + (r - l) / 2;
        //如果当前区间长度满足情况,需要睁增大区间长度
        if(func(mid))
        {
            l = mid + 1;
        }
        //反之缩小区间长度
        else
        {
            r = mid;
        }
    }
    cout<<l<<endl;
    return 0;    
}

12. 万万没想到之聪明的编辑

我叫王大锤,是一家出版社的编辑。我负责校对投稿来的英文稿件,这份工作非常烦人,因为每天都要去修正无数的拼写错误。但是,优秀的人总能在平凡的工作中发现真理。我发现一个发现拼写错误的捷径:

  1. 三个同样的字母连在一起,一定是拼写错误,去掉一个的就好啦:比如 helllo -> hello
  2. 两对一样的字母(AABB型)连在一起,一定是拼写错误,去掉第二对的一个字母就好啦:比如 helloo -> hello
  3. 上面的规则优先“从左到右”匹配,即如果是AABBCC,虽然AABB和BBCC都是错误拼写,应该优先考虑修复AABB,结果为AABCC

我特喵是个天才!我在蓝翔学过挖掘机和程序设计,按照这个原理写了一个自动校对器,工作效率从此起飞。用不了多久,我就会出任CEO,当上董事长,迎娶白富美,走上人生巅峰,想想都有点小激动呢!
……
万万没想到,我被开除了,临走时老板对我说: “做人做事要兢兢业业、勤勤恳恳、本本分分,人要是行,干一行行一行。一行行行行行;要是不行,干一行不行一行,一行不行行行不行。” 我现在整个人红红火火恍恍惚惚的……

请听题:请实现大锤的自动校对程序

输入描述:
第一行包括一个数字N,表示本次用例包括多少个待校验的字符串。
后面跟随N行,每行为一个待校验的字符串。

输出描述:
N行,每行包括一个被修复后的字符串。

代码

#include<iostream>
#include<regex>
using namespace std;
string replace(string input, string regStr, string rep){
     regex reg(regStr);
     return regex_replace(input, reg, rep);
 }
 int main(){
     int n;
     cin>>n;
     while(n--){
         string input;
         cin >> input;
         cout << replace(replace(input, "(.)\\1+", "$1$1"), "(.)\\1(.)\\2", "$1$1$2") << endl;
     }
     return 0;
 }

注意
(.)\1+ 表示 表示任意一个字符重复两次或两次以上(括号里的点表示任意字符,后面的\1表示取第一个括号匹配的内容,后面的加号表示匹配1次或1次以上。二者加在一起就是某个字符重复两次或两次以上)
$1是第一个小括号里的内容,$2是第二个小括号里面的内容,

13. 字符串中最后一个单词长度

计算字符串最后一个单词的长度,单词以空格隔开。
输入描述:
一行字符串,非空,长度小于5000。

输出描述:
整数N,最后一个单词的长度。

代码:

#include<iostream>
#include<string>
using namespace std;
int main()
{
    string str;
    getline(cin,str);//cin是遇到空格或者换行就结束了;而getline是遇到换行才结束;其中 cin 是正在读取的输入流,而 str是接收输入字符串的 string 变量的名称
    int count = 0;
    for(int i = str.length()-1; i >= 0; i --)
    {
        if(str[i] != ' ')
            count ++;
        else
            break;
    }
    cout << count<< endl;
    return 0;
}

14. 字符串分隔

•连续输入字符串,请按长度为8拆分每个字符串后输出到新的字符串数组;
•长度不是8整数倍的字符串请在后面补数字0,空字符串不处理。
输入描述:
连续输入字符串(输入2次,每个字符串长度小于100)

输出描述:
输出到长度为8的新字符串数组

示例1
输入

abc
123456789

输出

abc00000
12345678
90000000

代码:

#include<iostream>
#include<string>
using namespace std;
void Divide(string &str)
{
    int n = str.length();
    string s0;
    if(n <= 8)
    {
        for(int i = 0; i <(8-n); i ++)
            str += '0';
        cout << str << endl;
        return ;
    }
   if(n > 8)
   {
       string s0(str, 0, 8);//将str内,开始于0且长度顶多为8的部分作为s0的初值
       cout << s0 << endl;
   }
    string s1(str, 8);//将str内,开始于位置8的部分当作s1的初值
    Divide(s1);
}
int main()
{
    string s0, s1;
    getline(cin,s0);
    getline(cin,s1);
    Divide(s0);
    Divide(s1);
    return 0;
}

15. 进制转换

写出一个程序,接受一个十六进制的数,输出该数值的十进制表示。(多组同时输入 )

输入描述:
输入一个十六进制的数值字符串。

输出描述:
输出该数值的十进制字符串。

示例1
输入

0xA

输出

10

思路
输入为字符串,且格式为0x为前缀,可以以此为切入点。
1、将输入作为字符串读取
2、倒序遍历字符串(从尾到头)
3、16进制转换10进制运算,利用乘方加以解决高位的换算。
4、以0xAA为例,从尾到头遍历到0xAA的 ‘x’ 处停止,得到最终结果输出。

代码

#include<iostream>
#include<string>
#include<cmath>
using namespace std;
int main()
{
    string str;
    while(cin >> str)//保证同时输入
    {
        int n = str.length();
        int res = 0;
        for(int i = n-1; i >= 2 ; i --)
        {
            if(str[i] >= '0' && str[i] <= '9' || str[i] >= 'A' && str[i] <= 'F')
            {
                switch(str[i])
                {
                    case 'A':
                        {res += 10 * pow(16, n-1-i); break;}
                    case 'B':
                        {res += 11 * pow(16, n-1-i); break;}
                    case 'C':
                        {res += 12 * pow(16, n-1-i); break;}
                    case 'D':
                        {res += 13 * pow(16, n-1-i); break;}
                    case 'E':
                        {res += 14 * pow(16, n-1-i); break;}
                    case 'F':
                        {res += 15 * pow(16, n-1-i); break;}
                    default:
                        {res += (str[i]-'0') * pow(16, n-1-i); break;}
                 }
            }
        }
        cout << res << endl;
    }
    return 0;
}

16. 坐标移动

开发一个坐标计算工具, A表示向左移动,D表示向右移动,W表示向上移动,S表示向下移动。从(0,0)点开始移动,从输入字符串里面读取一些坐标,并将最终输入结果输出到输出文件里面。

输入:

合法坐标为A(或者D或者W或者S) + 数字(两位以内)

坐标之间以;分隔。

非法坐标点需要进行丢弃。如AA10; A1A; % ; YAD; 等。

下面是一个简单的例子 如:

A10;S20;W10;D30;X;A1A;B10A11;;A10;

处理过程:

起点(0,0)

  • A10 = (-10,0)

  • S20 = (-10,-20)

  • W10 = (-10,-10)

  • D30 = (20,-10)

  • x = 无效

  • A1A = 无效

  • B10A11 = 无效

  • 一个空 不影响

  • A10 = (10,-10)

结果 (10, -10)

注意请处理多组输入输出

输入描述:
一行字符串

输出描述:
最终坐标,以,分隔

示例1
输入

A10;S20;W10;D30;X;A1A;B10A11;;A10;

输出

10,-10

思路
(1)分隔字符串;
(2)判断子串是否符合(长度在2-3,从第二个字符开始必须是数字字符);
(3)坐标移动。

代码

#include<iostream>
#include<string>
#include<cstddef>   //std::size_t
using namespace std;
int main(){
    string str;
    while(cin>>str){
        pair<int,int> point(0,0);               //point.first point.second
        size_t found = str.find_first_of(';');  //找到第一个';'的位置
        int start = 0;
        while(found!=string::npos){
            string s1 = str.substr(start,found-start);//从start开始长度为found-start的子串
            //cout << s1 << endl;
            start = found+1;
            found = str.find_first_of(';',found+1);
            if(s1.size()>1 && s1.size()<=3){    //合法的字符个数:2或3
                char c = s1[0];
                int n = 0;
                int invalid = 0;    //数字为是否非法
                for(int i=1; i<s1.size(); ++i){ //数字位判断与提取,A1A
                    if(s1[i]>='0'&&s1[i]<='9')
                        n = n*10 + (s1[i]-'0');
                    else{
                        invalid=1;
                        break;
                    }
                }
                if(invalid==0){
                    switch(c)
                    {
                        case 'A': {point.first-=n;break;}
                        case 'D': {point.first+=n;break;}
                        case 'W': {point.second+=n;break;}
                        case 'S': {point.second-=n;break;}
                    }
                }
 
            }
        }
        cout << point.first << ',' << point.second <<endl;
    }
    return 0;
}

注意
(1)size_t find_first_of (const string& str, size_t pos = 0)
首先find_first_of该函数的返回类型是size_t类型,其次该函数表示的是从pos(默认是是0,即从头开始查找)开始查找,找到第一个和str1相匹配的子串,返回该子串的起始索引位置。

(2) pair 是 一种模版类型。每个pair 可以存储两个值。这两种值无限制。也可以将自己写的struct的对象放进去

(3)头文件cstddef与其C对应版本兼容,它是C头文件<stddef.h>较新版本,定义了常用的常量、宏、类型和函数
内的各项定义
NULL : 指针值用来表示未定义或无值
nullptr_t : nullptr的类型
size_t :一种无正负号类型,用来表示大小(比如元素的个数)
ptrdiff_t :一种带正负号类型,用来表示指针之间的距离
max_align_t:所有环境之最大齐位所对应的类型
offsetof(type,mem):表示成员mem在某个struct或union中的偏移量

17. 识别有效的IP地址和掩码并进行分类

请解析IP地址和对应的掩码,进行分类识别。要求按照A/B/C/D/E类地址归类,不合法的地址和掩码单独归类。

所有的IP地址划分为 A,B,C,D,E五类

A类地址1.0.0.0~126.255.255.255;

B类地址128.0.0.0~191.255.255.255;

C类地址192.0.0.0~223.255.255.255;

D类地址224.0.0.0~239.255.255.255;

E类地址240.0.0.0~255.255.255.255

私网IP范围是:

10.0.0.0~10.255.255.255

172.16.0.0~172.31.255.255

192.168.0.0~192.168.255.255

子网掩码为二进制下前面是连续的1,然后全是0。(例如:255.255.255.32就是一个非法的掩码)
注意二进制下全是1或者全是0均为非法

注意:

  1. 类似于【0...】和【127...】的IP地址不属于上述输入的任意一类,也不属于不合法ip地址,计数时可以忽略
  2. 私有IP地址和A,B,C,D,E类地址是不冲突的

输入描述:
多行字符串。每行一个IP地址和掩码,用~隔开。

输出描述:
统计A、B、C、D、E、错误IP地址或错误掩码、私有IP的个数,之间以空格隔开。

示例1
输入

10.70.44.68~255.254.255.0
1.0.0.1~255.0.0.0
192.168.0.2~255.255.255.0
19..0.~255.255.255.0

输出

1 0 1 0 0 2 1

思路:这道题只要理清了几类IP地址及其子网掩码的特点,就不难
1、A类地址(适用于大型网络)
IP范围:1.0.0.0~126.255.255.255
子网掩码:255.0.0.0
2、B类地址(适用于中型网络)
IP范围:128.0.0.0~191.255.255.255
子网掩码:255.255.0.0
3、C类地址(适用于小型网络)
IP范围:192.0.0.0~223.255.255.255
子网掩码:255.255.255.0
4、D类地址(组播地址,没有子网掩码)
IP范围:224.0.0.0~239.255.255.255
5、E类地址(保留地址,没有子网掩码)
IP范围:240.0.0.0~255.255.255.255
6、私网IP范围(与A、B、C类地址不冲突,即两边都可以统计)
10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0~192.168.255.255
7、错误IP地址或错误掩码
可以看出,A/B/C/D/E类IP地址包含了所有合法类型的IP地址,且有对应的子网掩码,其他的都是非法的IP地址或掩码

#include<iostream>
#include<string>
#include<vector>
 
using namespace std;
 
void splitString(const string& str, const string separator, vector<string>& res)
{
    int pos1, pos2;
    pos1 = 0;
    pos2 = str.find(separator, pos1);  //找到返回位置,找不到返回-1
    string temp;
    while (pos1 != pos2)
    {
        temp = str.substr(pos1, pos2 - pos1);  //第二次循环这里,pos1等于“~”后面一个位置,而pos1=-1,pos2-pos1<0
        res.push_back(temp);                   //所以默认截取pos1,到字符串末尾
        pos1 = pos2 + 1;
        if (pos1 == 0)break;
        pos2 = str.find(separator, pos1);
        
    }
}
 
bool checkIpNums(const vector<int>& ips)
{
    if (ips.size() == 4)
    {
        if (ips[0] >=0 && ips[0] <= 255 && ips[1] >=0 && ips[1] <= 255
            && ips[2] >=0 && ips[2] <= 255 && ips[3] >= 0 && ips[3] <= 255)
            return true;
        else
            return false;
    }
    else
        return false;
}
 
bool validMask(const vector<int>& mask)
{
    if (checkIpNums(mask))
    {
        int pos=3;
        for (int i = 3; i >= 0; i--)
        {
            if (mask[i] != 0)
            {
                pos = i;
                break;
            }
        }
         
        if (pos == 3&&mask[pos]==255)return false;
         
        if (mask[pos] == 255 || mask[pos] == 254 || mask[pos] == 252 || mask[pos] == 248 ||
            mask[pos] == 240 || mask[pos] == 224 || mask[pos] == 192 || mask[pos] == 128)
        {
            int tag = 0;
            while (pos >0)
            {
                pos--;
                if (mask[pos] != 255)
                {
                    tag = 1;
                    break;
                }
            }
            if (tag == 0)return true;
            else return false;
        }
        else return false;
    }
    else
        return false;
}
 
int main()
{
    string str;
    int A, B, C, D, E, errIpMask, privateIP;
    A = B = C = D = E = errIpMask = privateIP = 0;
    while(getline(cin, str))
    {
        vector<string> tempSepStr, ipStr, maskStr;
        vector<int> ip, mask;
        splitString(str, "~", tempSepStr);
        splitString(tempSepStr[0], ".", ipStr);
        splitString(tempSepStr[1], ".", maskStr);
        for (int i = 0; i < ipStr.size(); i++)
        {
            ip.push_back(stoi(ipStr[i]));
        }
        for (int i = 0; i < maskStr.size(); i++)
        {
            mask.push_back(stoi(maskStr[i]));
        }
 
        if (validMask(mask)&&checkIpNums(ip))
        {
                if (ip[0] >= 1 && ip[0] <= 126)
                {
                    if (ip[0] == 10)privateIP++;
                    A++;
                }
 
                if (ip[0] == 172 && ip[1] >= 16 && ip[1] <= 31)privateIP++;
                if (ip[0] == 192 && ip[1] == 168)privateIP++;
 
                if (ip[0] >= 128 && ip[0] <= 191) B++;
 
                if (ip[0] >= 192 && ip[0] <= 223) C++;
 
                if (ip[0] >= 224 && ip[0] <= 239) D++;
 
                if (ip[0] >= 240 && ip[0] <= 255) E++;
        }
        else
            errIpMask++;
    }
     
    cout << A << " " << B << " " << C << " " << D << " " << E
        << " " << errIpMask << " " << privateIP << endl;
    system("pause");
    return 0;
}

18. 字符串匹配(华为机试)

判断短字符串中的所有字符是否在长字符串中全部出现

详细描述:

接口说明

原型:

boolIsAllCharExist(char* pShortString,char* pLongString);

输入参数:

char* pShortString:短字符串

char* pLongString:长字符串

输入描述:
输入两个字符串。第一个为短字符,第二个为长字符。

输出描述:
返回值:

示例1
输入

bc
abc

输出

true
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main(){
    string s1,s2,res;
    while(cin >> s1 >> s2){
        res="true";//注意此语句放在while语句内,因为每次循环都要重置res为true
        int n1=s1.size(),n2=s2.size();
        map<char,int> m;
        for(int i=0;i<n2;i++){
            m[s2[i]]=1;
        }
        for(int i=0;i<n1;i++){
            if(m[s1[i]]!=1){
                res="false";
                break;
            }
        }
        cout << res <<endl;
    }
    return 0;
}

19. 字符统计(华为机试)

如果统计的个数相同,则按照ASCII码由小到大排序输出 。如果有其他字符,则对这些字符不用进行统计。

实现以下接口:
输入一个字符串,对字符中的各个英文字符,数字,空格进行统计(可反复调用)
按照统计个数由多到少输出统计结果,如果统计的个数相同,则按照ASCII码由小到大排序输出
清空目前的统计结果,重新统计
调用者会保证:
输入的字符串以‘\0’结尾。

输入描述:
输入一串字符。

输出描述:
对字符中的
各个英文字符(大小写分开统计),数字,空格进行统计,并按照统计个数由多到少输出,如果统计的个数相同,则按照ASII码由小到大排序输出 。如果有其他字符,则对这些字符不用进行统计。

示例1
输入

aadddccddc

输出

dca
#include <iostream>
#include <unordered_map>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

bool comp(pair<char,int> a, pair<char,int> b)  // 重载sort函数的自定义比较函数comp
{
    if(a.second > b.second)
        return true;
    else if(a.second == b.second && a.first < b.first)
        return true;
    else return false;
}

int main()
{
    string str;
    unordered_map<char, int> countMap;
    //unordered_map 主键是无序的,而map是按照主键的大小有序存储的。

    while(getline(cin,str))
    {
    for(int i=0; i <= str.length()-1; i++)
        {
              //注意这种写法
             if(countMap.find(str[i]) == countMap.end())
            {
                countMap[str[i]] = 1;
            }
            else
            {
                countMap[str[i]]++;
            }        
        }

    vector<pair<char, int>> elems(countMap.begin(), countMap.end());
    sort(elems.begin(), elems.end(), comp);  // 使用sort对hash表进行排序
     //利用迭代器,不同的容器类定义了自己的iterator类型,用于访问容器内的元素。换句话说,每个容器定义了一种名为iterator的类型,而这种类型支持(概念上的)迭代器的各种行为。
    for(vector<pair<char, int>>::iterator it=elems.begin(); it != elems.end(); it++)
    {
        cout<<it->first;
    }
    cout<<endl;
    countMap.clear();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值