【王道机试指南学习笔记】第四章 字符串

前言与提示

字符串处理及字符串匹配 是很基础的内容!
这里同样放几篇写得很好的博文,供自己学习参考:
ACM中常用算法----字符串:
https://blog.csdn.net/ck_boss/article/details/47066727

4.1 字符串

重点提醒

C语音不提供字符串基本类型,需用char a[]字符数组来代替字符串。
C++提供string基本数据类型,需要#include < string >,要与c语言里面的#include <string.h>区分
在做题时,我们学会使用string即可。

基本操作

1) 字符串定义&初始化

//定义
string str;
//初始化
string str = "hello";

2) 字符串长度

涉及两个函数:size()和length() ,功能基本相同

string str = "hello";
int n = str.size();
int m = str.length();

3) string元素访问

①数组方式:通过元素下标访问,0到size()-1

for(int i = 0; i<str.size();i++){
	printf("%c ",str[i]);//不能写%s
	cout<<str[i]<<" ";
}

②迭代器方式:迭代器类似于指针
string::iterator :是类模板
1.迭代器表现的像指针。他模拟了指针的一些功能,是一个“可遍历 STL 容器内全部或部分元素”的对象, 本质是封装了原生指针,提供了比指针更高级的行为;
2.迭代器返回的是对象引用而不是对象的值,所以cout只能输出迭代器使用取值后的值而不能直接输出其自身;
3.STL的关键所在,通过迭代器,容器和算法可以有机的粘合在一起;
4.迭代器在使用后就会被系统释放,不能再继续使用,但指针可继续使用。

for(string::iterator it = str.begin(); it!=str.end();it++){
     printf("%c ",*it);
     cout<<*it<<" ";
}

4) string元素操作

通常包括:任意位置插入元素的insert()、任意位置删除元素的erase()和将字符串清空的clear()。

//insert
//前面是位置,后面是要插入的元素
str.insert(it,x);
str.insert(str.end(),"abc");
//erase
//1)迭代器方法
//从c中删除迭代器p指定的元素,p必须指向c中一个真实元素,不能=c.end()
c.erase(p);
//从c中删除迭代器对b和e所表示的范围中的元素,返回e
c.erase(b,e)
//2)下标方法
//删除下标10开始及以后的字符
str.erase(10);
//删除下标‘10’开始的连续8个字符 
str.erase(10,8); 

5) string运算符

string也可像数字一样运算。
①进行拼接运算,运算符有“+”和“+=”。

string str1 = "a";
string str2;
str2 = str+str1+'b'+"c";

②进行大小比较运算,运算符有“<”、“>”、“<=”、“>=”、“==”、“!=”。

str1 = "abc";
str2 = "abcd";
if(str1 <= str2) / if(str1 != str2) / if(str1 == str2)..

6) string常用函数

①在字符串中寻找特定字符或字符串的函数是find(),若找到则返回对应下标,找不到则返回string::npos.

    string str = "hello world";
    int rec = str.find("world");
    //也可写成rec!=string::npos
    if(rec) cout<<rec<<endl;
    rec = str.find("aaaa");
    //不可写成(!rec)
    if(rec==string::npos) cout<<"not found."<<endl;

②字符串 子串:substr()

string s = "0123456789"
//表示从下标为5开始一直到结尾:sub1 = "56789"
string sub1 = s.substr(5);
//从下标为5开始截取长度为3位:sub2 = "567"
string sub2 = s.substr(5,3); 

4.2 字符串处理

重点提醒

字符串处理常作为简单题目出现,考察输入/输出格式、思维逻辑,往往涉及边界等问题,要小心对待!

题目练习

例题4.1 特殊乘法(清华复试题)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/a5edebf0622045468436c74c3a34240f
特殊乘法,逐个位对应乘,转为字符串比整数取余求更简单!

cin的 >> 是会过滤掉不可见字符(如 空格 回车 TAB 等)

#include <iostream>
#include <string>
using namespace std;

int main(){
    string s1,s2;
    while(cin>>s1>>s2){
        int sum = 0;
        for(int i = 0;i<s1.size();i++){
            for(int j = 0;j<s2.size();j++){
                sum += (s1[i]-'0')*(s2[j]-'0');
            }
        }
        cout<<sum<<endl;
    }
}

例题4.2 密码翻译(北大复试题)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/136de4a719954361a8e9e41c8c4ad855

接收一行字符的写法:

//1.字符串:接受一个字符串,可以接收空格并输出
string str;
getline(cin,str)
//2.字符数组:接收固定长度的字符,可以接收空格并输出
char name[15];
cin.get(name, 15);
cin.getline(name, 15);

本题AC代码:

#include <iostream>
#include <string>
using namespace std;

int main(){
    string str;
    while(getline(cin,str)){
        for(int i = 0;i<str.size();i++){
            if(str[i] == 'z'||str[i]=='Z') str[i] -= 25;
            else if((str[i]>= 'a'&&str[i]<='y')||(str[i]>= 'A'&&str[i]<='Y'))
                str[i]++;
        }
        cout<<str<<endl;
    }
    return 0;
}

例题4.3 简单密码(北大复试题)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/ff99c43dd07f4e95a8f2f5448da3772a

#include <iostream>
#include <string>
using namespace std;

int main(){
    string str;
    while(getline(cin,str)){
        if(str=="ENDOFINPUT") break;
        getline(cin,str);//密文获取
        for(int i = 0;i<str.size();i++){
        //循环平移问题解法!!
        //if(str[i]>= 'A'&&str[i]<='Z') str[i] = (str[i]+26-5-'A')%26+'A';
        //我的解法
            if(str[i]>= 'F'&&str[i]<='Z') str[i] -=5;
            else if(str[i]>= 'A'&&str[i]<='E') str[i] += 26-5;
        }
        cout<<str<<endl;
        getline(cin,str);
    }
    return 0;
}

例题4.4 统计字符(浙大复试题)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/4ec4325634634193a7cd6798037697a8
①书上的求解方法:
设立一个int数组rec 扫描line字符串并rec[line[i]]++;记录次数
创建一个ascii整型数组,收集第二行字符串中每个字符出现的次数
②AC代码:用count求解了次数

#include <iostream>
#include <string>
#include<algorithm>
using namespace std;

int main(){
    string str;
    while(getline(cin,str)){
        if(str=="#") break;
        string line;
        getline(cin,line);//密文
        for(int i=0;i<str.size();i++){
        //超简单的高级解法 count!!!
            cout<<str[i]<<" "<<count(line.begin(),line.end(),str[i])<<endl;
        }
    }
    return 0;
}

例题4.5 字母统计(上交复试题)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/de7bf0945c1c4bd1aa9d49573b831f3c
因为要按A-Z顺序输出,所以用count求解次数的方法不再好用了!
AC方法:设立一个int数组number 扫描字符串并rec[line[i]]++;记录次数
注:memset是常用的数组清零的函数,使用它需要引入string.h,在c++中相当于 < cstring >

memset(number,0,sizeof(number));//清0
#include <iostream>
#include <string>
//cstring是把string.h放到std中
#include <cstring>
using namespace std;

int number[26];
int main(){
    string str;
    while(getline(cin,str)){
        memset(number,0,sizeof(number));//清0
        for(int i=0;i<str.size();i++){
            if(str[i]>= 'A'&&str[i]<='Z')
                number[str[i]]++;
        }
        for(int i=0;i<26;i++){
            char a = 'A'+i;
            cout<<a<<":"<<number[a]<<endl;
            //上面++时若str[i]-'A',则在此cout number[i]
        }
    }
    return 0;
}

习题4.1 skew数(北大复试题)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/7b6586ac8f614aafbe2e0896e82ac0c1
类似逐位二进制转十进制,简单。
pow幂乘函数要引入头文件 < cmath >

#include <iostream>
#include <string>
#include <cmath>
using namespace std;

int main(){
    string str;
    int sum = 0;
    while(getline(cin,str)){
        if(str=="0") break;
        for(int i=0;i<str.size();i++){
            sum += (str[i]-'0')*(pow(2,str.size()-i)-1);
        }
        cout<<sum<<endl;
    }
    return 0;
}

习题4.2 单词替换(北大复试题)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/5b58a04679d5419caf62c2b238e5c9c7
这题废了一定时间 因为它要求单词替换,比如CCCCC单词,如果我们要找的是CCC,也是不能替换CCCCC的!这是我开始写错的点。
AC代码中先在首尾插入“ ”,便于判断!最后打印前删去空格

#include <iostream>
#include <string>
using namespace std;

int main(){
    string s,a,b;
    while(getline(cin,s)){
        getline(cin,a);
        getline(cin,b);
        //首尾插入" "方便判断
        s.insert(0," ");
        s=s+" ";
        while(1){
            int start=s.find(" "+a+" ");
            if(start==-1) break;
            else{
                s.erase(start,a.size()+2);
                s.insert(start," "+b+" ");
            }
        }
        s.erase(0,1);
        s.erase(s.size()-1);
        cout<<s<<endl;
    }
    return 0;
}

习题4.3 首字母大写(北大复试题)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/91f9c70e7b6f4c0ab23744055632467a

#include <iostream>
#include <string>
using namespace std;

int main(){
    string s;
    while(getline(cin,s)){
        for(int i=0;i<s.size();i++){
            if(i==0) s[i]=toupper(s[i]);
            else{
                if(s[i-1]==' '||s[i-1]=='\t'||s[i-1]=='\r'||s[i-1]=='\n')
                    s[i]=toupper(s[i]);
            }
        }
        cout<<s<<endl;
    }
    return 0;
}

习题4.4 浮点数加法(北大复试题)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/ddec753f446e4ba4944e35378ba635c8

#include <iostream>
#include <string>
using namespace std;

string fadd(string sa, string sb){
    string result, sc, sd;
    //将两浮点数对齐
    int ia = sa.find(".");
    int ib = sb.find(".");
    sc = (ia>ib)?sa:sb;
    sd = (ia>ib)?sb:sa;
    int n = (ia>ib)?(ia-ib):(ib-ia);
    while(n--){
        sd = "0"+sd;//.前+0
    }
    int lenc = sc.length();
    int lend = sd.length();
    sa = (lenc>lend)?sc:sd;
    sb = (lenc>lend)?sd:sc;
    n = (lenc>lend)?(lenc-lend):(lend-lenc);
    while(n--){
        sb+="0";//.后+0
    }
    //对对齐后的浮点数进行相加
    int carry = 0;
    //从小数点后开始扫描
    for(int i = sa.length()-1; i>=0; i--){
        if(sa[i]=='.'){
            result = "."+result;
            continue;
        }
        char value = sa[i]-'0'+sb[i]-'0'+ carry;
        //若进位则%
        result = char(value%10+'0')+result;
        carry = value/10; //carry存进位个数
    }
    //判断最后一步有没有进位(最高位)
    while(carry!=0){
        result = char(carry%10+'0')+result;
        carry/=10;
    }
    return result;
}
int main(){
    string a,b;
    while(cin>>a>>b){
        cout<<fadd(a,b)<<endl;
    }
    return 0;
}

习题4.5 后缀子串排序(上交复试题)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/f89f96ea3145418b8e6c3eb75773f65a
也用到了之前排序的sort函数~

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int main(){
    string s;
    while(cin>>s){
        string a[s.size()];
        for(int i = 0;i<s.size();i++){
            a[i] = s.substr(i,s.size()-i);
        }
        //利用排序的算法默认升序
        sort(a,a+s.size());
        for(int i = 0;i<s.size();i++){
            cout<<a[i]<<endl;
        }
    }
    return 0;
}

4.3 字符串匹配

重点提醒

字符串匹配也很经典。给出一个模式串pattern(长度m),一个文本串test(长度n),判断模式串是否为文本串的一个子串?
①暴力算法:逐字符比较,时间复杂度O(nm),效率不高
②KMP算法:改进的字符串匹配算法。模板串失配后,不是从下一个字符开始重新匹配,而是跳过一些不可能成功的位置,尽量减少次数,快速匹配,可以让复制度降低到O(n+m)。
用模板串计算出跳过多少个位置,用next数组保存结果
next数组值代表字符串的前缀和后缀相同的最大长度。
具体讲解可看:KMP1KMP2
**“部分匹配值”**就是"前缀"和"后缀"的最长的共有元素的长度。

KMP算法步骤

①寻找前缀后缀最长公共元素长度——“部分匹配值”
以"ABCDABD"为例:

下面的图和讲解,部分引用自上文提到的KMP2链接!
在这里插入图片描述在这里插入图片描述

②求next数组

next 数组相当于“最大长度值” 整体向右移动一位,然后初始值赋为-1在这里插入图片描述
当然也可以直接计算某个字符对应的next值,==看这个字符之前的字符串中有多大长度的相同前缀后缀 ==
next[1]一般肯定为0!!!!!!!
在这里插入图片描述

③根据next数组进行匹配
失配时,按照next表进行跳转,尝试重新匹配。只需遍历一遍pattern串即可获得next表,且遍历一遍文本串即可获得字符串匹配情况。

  • 根据 最大长度表,失配时:
    向右移动的位数 = 已经匹配的字符数 - 失配字符的上一位字符的最大长度值
  • 根据 next 数组,失配时:
    向右移动的位数 = 失配字符的位置 - 失配字符对应的next 值

注:从0开始计数时,失配字符位置 = 已经匹配的字符数(失配字符不计数),而失配字符对应的next 值 = 失配字符的上一位字符的最大长度值,两相比较,结果必然完全一致。

在这里插入图片描述
在这里插入图片描述

题目练习

例题4.6 Number Sequence

题目无链接,截图如下:
在这里插入图片描述
KMP算法求解!!
k = next[k],就是找到p[k]对应的next[k],根据对称性,只需再判断p[next[k]]与p[j]是否相等即可,于是令k = next[k],这里恰好就使用了递归的思路。
在这里插入图片描述
代码里有创建next表的函数,以后写别的也可以套用…

#include <iostream>
#include <string>
using namespace std;

const int MAXM = 10005;
const int MAXN = 1000005;

int nextTable[MAXM];
int pattern[MAXM];
int text[MAXN];

//创建next表 计算next 数组的方法可以采用递推
void GetNextTable(int m){
    nextTable[0] = -1;
    int j = 0;
    int i = -1;
    while(j<m){
        //p[i]表示前缀,p[j]表示后缀
        if(i==-1||pattern[j]==pattern[i]){
            i++;
            j++;
            //next[j] = i 代表p[j]之前的模式串子串中,有长度为i的相同前缀和后缀
            nextTable[j]=i;
        }
        else i = nextTable[i];
    }
}
int KMP(int n,int m){
    GetNextTable(m);
    int i = 0;
    int j = 0;
    while(i<n && j<m){
        if(j==-1 || text[i] == pattern[j]){//当前字符匹配成功
            i++;
            j++;
        }
        else j = nextTable[j];//当前字符匹配失败,从next表的位置重新开始匹配
    }
    if(j==m) return i-j+1;//返回匹配成功的起始位置
    else return -1;
}
int main(){
    int num;
    cin>>num;
    while(num--){
        int n,m;
        cin>>n>>m;
        for(int i = 0;i<n;i++){
            cin>>text[i];
        }
        for(int i = 0;i<m;i++){
            cin>>pattern[i];
        }
        cout<<KMP(n,m)<<endl;
    }
    return 0;
}

例题4.7 Oulipo

题目OJ网址(POJ):http://poj.org/problem?id=3461
与上题类似,只不过本题需要完整遍历文本串,记录匹配成功的次数。

#include <iostream>
#include <string>
using namespace std;

const int MAXM = 10005;

int nextTable[MAXM];

//创建next表 计算next 数组的方法可以采用递推
void GetNextTable(string pattern){
    int m = pattern.size();
    nextTable[0] = -1;
    int j = 0;
    int i = -1;
    while(j<m){
        //i比j小1
        if(i==-1||pattern[j]==pattern[i]){//p[i]表示前缀,p[j]表示后缀
            i++;
            j++;
            //next[j] = i 代表p[j]之前的模式串子串中,有长度为i的相同前缀和后缀
            nextTable[j]=i;
        }
        else i = nextTable[i];
    }
}
int KMP(string text,string pattern){
    GetNextTable(pattern);
    int i = 0;
    int j = 0;
    int n = text.size();
    int m = pattern.size();
    int rec = 0;
    while(i<n){
        if(j==-1 || text[i] == pattern[j]){//当前字符匹配成功
            i++;
            j++;
        }
        else j = nextTable[j];//当前字符匹配失败
        if(j==m){//匹配成功一个后,继续当作失败进行匹配(重叠~)
            rec++;
            j = nextTable[j];
        }
    }
    return rec;
}
int main(){
    int num;
    cin>>num;
    string pattern,text;
    while(num--){
        cin>>pattern>>text;
        cout<<KMP(text,pattern)<<endl;
    }
    return 0;
}

习题4.6 字符串匹配(北航复试题)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/f7a070bc72e644d68d28fdacc9cc6792

#include <iostream>
#include <string>
using namespace std;
//判等函数
bool equal(char a, char b){
    if(isalpha(a)&&isalpha(b))//若a、b同为字母,判断两者的小写是否相等
        return tolower(a) == tolower(b);    //实测,tolower在已是小写时不操作
    else return a == b;//否则判其是否严格相等
}

int main(){
    int num;
    string text[num];
    string pattern;
    while(cin>>num){
        for(int i = 0;i<num;i++){
            cin>>text[i];//num个文本串
        }
        cin>>pattern;//模式串
        for (int i = 0; i < num; i++) {   
            bool match = true;
            int j = 0, k = 0;            //双指针指向两者位置
            for (; j < pattern.size()&&k<text[i].size();j++,k++) {
                if (pattern[j] == '[') {    //遇到'['则单独扫描j
                    bool hit = false;
                    while (++j<pattern.size() && pattern[j] != ']') {
                        //j退出循环时已经与k同步
                        if (equal(text[i][k], pattern[j])) hit = true;
                    }
                    if (!hit) {//扫描'[]'内字符后仍无法匹配则跳出
                        match = false;
                        break;
                    }
                }
                else if (!equal(text[i][k], pattern[j])) {//匹配失败跳出
                    match = false;
                    break;
                }
            }
            if (j != pattern.size() || k != text[i].size()) match = false;
            //匹配成功则必然j、k都到上限,这一步是防止包含情况出现,如ab,A[a2b]b
            if(match) cout<<i + 1<< " " << text[i] << endl;
        }
    }
    return 0;
}

习题4.7 String Matching(上交复试题)

题目OJ网址(牛客网):
https://www.nowcoder.com/questionTerminal/00438b0bc9384ceeb65613346b42e88a
跟例题4.7代码基本一样,不重复写了

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值