前言与提示
字符串处理及字符串匹配 是很基础的内容!
这里同样放几篇写得很好的博文,供自己学习参考:
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数组值代表字符串的前缀和后缀相同的最大长度。
具体讲解可看:KMP1、KMP2
**“部分匹配值”**就是"前缀"和"后缀"的最长的共有元素的长度。
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代码基本一样,不重复写了