在CSP里面好多道“水题“基本都离不开字符串/数组的模拟
滚动哈希,字典树,DP几个强强联合基本可以横扫所有难度的字符串算法了,所以在这个task里会好好消化其中前二
字符串和数组有很多相似之处,比如同样使用下标的方式来访问单个字符。根据字符串的特点,将字符串问题分为以下几种:
- 字符串匹配问题
- 子串相关问题
- 前缀 / 后缀相关问题
- 回文串相关问题
- 子序列相关问题
关于字符串的字符编码:
-
ASCII(英语为主的语言)
-
Unicode(多语言,其中最常用的就是UTF-8编码)。UTF-8:将Unicode字符根据大小编成1-6个字节,英文1个字节,汉字通常是3个字节。
字符串匹配问题(模式匹配,,Pattern Matching) :
-
单模式串匹配:从文本串中找到特定串的所有出现位置。这次主要训练了KMP算法(基于前缀搜索方法)。
-
多模式串匹配:文本串中找模式串P,其中P是模式串p的集合,找到集合P中p的所有出现位置。
基操
125.验证回文串
测试用例:
a man,a plAn,a canal:Panama
< cctype >
isalnum( ):bool型,判断是否为有效字符(十进制数字/字母
tolower():char型,将有效字符转为小写字母(toupper
AC:对撞指针--left&right
#include<iostream>
#include<string>
using namespace std;
bool ValidPalindrome(string &s){
string sgood;
for(char c:s){
if(isalnum(c)){
sgood+=tolower(c);
}
}
int n=sgood.size();
for(int left=0,right=n-1;left<right;){
if(sgood[left]!=sgood[right])
return false;//不是回文串
++left;
--right;
}
return true;
}
int main(){
string s;
cin>>s;
if(ValidPalindrome(s))
cout<<"true"<<endl;
else cout<<"false"<<endl;
return 0;
}
344.反转字符串
UNAC:翻转字符串角标的关系 i&n-1-i ( ≠ i&n-i )
string ReverseString(string &s){
int n=s.size();
for(int i=0;i<=n/2;++i){
swap(s[i],s[n-i]);//s[n-1-i]
}
return s;
}
AC:双指针--left&right
n&n-1-i 无法替代lr指针(for循环判断不会出错
#include<iostream>
#include<string>
using namespace std;
string reverse(string &s){
int left=0;
int right=s.size()-1-left;
for(;left<right;++left,--right){
swap(s[left],s[right]);
}
cout<<s<<endl;
return s;
}
int main(){
string s;
cin>>s;
reverse(s);
cout<<"[\"";
for(int i=0;i<s.size()-1;i++){
cout<<s[i]<<"\",\"";
}
cout<<s[s.size()-1]<<"\"]";
}
557.翻转字符串中的单词III
AC:使用额外空间
#include<iostream>
#include<string>
using namespace std;
string reverse(string &s){
string word;//开新串存翻转后字符串
int n=s.size();
int i=0;
while(i<n){
int start=i;//单词的起始下标
while(i<n&&s[i]!=' '){//始
i++;
}
for(int p=start;p<i;p++){
word.push_back(s[start+i-1-p]);//翻转后存入
}
while(i<n&&s[i]==' '){//终
i++;
word.push_back(' ');
}
return word;
}
}
int main(){
string s,rev;
cin>>s;
rev=reverse(s);
cout<<rev;
return 0;
}
字符串单模式匹配
BruteForce算法
BF暴力匹配:双层for循环
-
成功:i++; j++;
-
失败:i回溯;j置0;
RabinKarp算法(滚动哈希算法
RK,使用哈希函数在文本中搜索
。。。看官方讲解的时候只能说什么依托答辩,一堆高级名词直接劝退,后来找到一篇CSDN直呼牛蛙
(参考:滚动哈希——干脆叫前缀和得了——CSDN)
KMP算法
KMP算法:
-
成功:i++; j++;
-
失败:i不变;j = next[ j ] ;
-
核心:前缀函数--前缀表
区分一下前后缀和真前后缀(刚开始学的时候很容易迷糊:
-
真前缀:去尾,从前往后逐字增一
-
真后缀:去首,从后往前逐字增一,字符之间相对顺序不变;亦即,不包含第一个字符的所有以最后一个字符结尾的连续子串
-
举个栗子:字符串"initialize"
所有真前缀:i in ini init initi initia initial initaili initializ
所有真后缀:e ze ize lize alize ialize tialize itialize nitialize
单个字符:存在前后缀,就是其本身,但不存在真前后缀,规定为0
会用到三个数组:
-
文本串T
-
模式串p
-
前缀表next:在不p包含尾字符的前缀字符全排列中,每种排列的最长相等前后缀数组成的表。
-
因取失配字符上一个字符的前缀表值用:指示回退下标,所以叫做next。(与prefix用法相同,都是前缀表)
算法实现:
//在背模板了,别急
28.找出字符串中第一个匹配项的下标
AC1:BF
int STRBF(string &haystack,string &needle){
int n=haystack.size(),m=needle.size();
for(int i=0;i+m<=n;i++){ //i<n, nonono
bool flag=true;
for(int j=0;j<m;j++){
if(haystack[i+j]!=needle[j]){
flag=false;
break;
}
}
//每一次p串匹配完都判断一次flag
if(flag){
return i;
}
}
return -1;
}
AC2:KMP
#include<iostream>
#include<string>
#include<vector>
using namespace std;
//KMP:next array
int STRKMP(string &haystack,string &needle){
int m=haystack.size(),n=needle.size();
if(n==0)
return 0;
vector<int> next(n);
//l找前缀, r找后缀
for(int l=0,r=1;r<n;r++){
while (l>0 && needle[l]!=needle[r])
l=next[l-1];
if(needle[l]==needle[r])
l++;
next[r]=l;
}
for(int i=0,j=0;i<m;i++){
while(j>0 && haystack[i]!=needle[j])
j=next[j-1];
if(haystack[i]==needle[j])
j++;
if(j==n)
return i-n+1;
}
return -1;
}
int main(){
string haystack,needle;
cin>>haystack;
cin>>needle;
int ans=0;
ans=STRKMP(haystack,needle);
cout<<ans;
return 0;
}
796.旋转字符串
< string > -- substr
字符串截取函数
(日常调库的一天
string s,a,b,c;
s="123a bc";
a=s.substr(0,3);//123,从第一位开始往后截取3个字符
b=s.substr(1,4);//123a,从第一位开始往后截取4个字符
c=s.substr(4,3);//a b,从第四位开始往后截取3个字符
AC1:调库
bool rotateSubStr(string &s,string &goal){
int n = s.size();
string str;
for(int i = 1; i < n; ++i) {
if(s[i] == goal[0]) {
str = s.substr(i);
str += s.substr(0, i);
if(str == goal) {
return true;
}
}
}
return false;
}
AC2:暴力模拟
bool rotateStrSmlt(string &s,string &goal){
int m=s.size(),n=goal.size();
//长度不匹配,直接判否
if(m!=n)
return false;
for(int i=0;i<n;i++){
bool flag=true;
for(int j=0;j<n;j++){
if(s[(i+j)%n]!=goal[j]){
flag=false;
break;
}
}
if ((flag))
return true;
}
return false;
}
AC3:KMP
bool rotateStrKMP(string s, string goal) {
if(s.size() != goal.size()) return false;
string res = s + s;
//next array
vector<int> ne(goal.size());
ne[0] = -1;
for(int i = 1, j = -1; i < goal.size(); i ++)
{
while(j != -1 && goal[j + 1] != goal[i]) j = ne[j];
if(goal[j + 1] == goal[i]) j++;
ne[i] = j;
}
for(int i = 1, j = -1; i < res.size(); i ++)
{
while(j != -1 && goal[j + 1] != res[i]) j = ne[j];
if(goal[j + 1] == res[i]) j ++;
if(j == goal.size() - 1) return true;
}
return false;
}