算法基础——字符串相关
目录:
- 基础知识
- STL-string常用函数
- KMP算法
- 应用实例
- 特殊乘法【清华大学】
- 密码翻译【北京大学】
- 简单密码【北京大学】
- 统计字符【浙江大学】
- 字母统计【上海交通大学】
- 统计字符【2017校招真题在线编程】
- skew数【北京大学】
- 单词替换【北京大学】
- 首字母大写【北京大学】
- 浮点数加法【北京大学】
- 后缀子串排序【上海交通大学】
- 字符串匹配【北京航天航空大学】
- String Matching【上海交通大学】
一、基础知识
1、STL-string常用函数:
- string的长度:
- length():返回当前字符串长度。
- size():返回当前字符串长度。
- string的元素访问:
- 可以像数组那样通过元素下标进行访问,下标从0到size() - 1。
- for(int i = 0; i < str.size(); i++){
- cout << str[i];
- }
- cout << endl;
- 可以通过迭代器进行访问,迭代器类似于指针。
- for(string::iterator iter = str.begin(); iter != str.end(); iter++){
- cout << &iter;
- }
- cout << endl;
- 可以像数组那样通过元素下标进行访问,下标从0到size() - 1。
- string的元素操作:
- insert(loc, "abcd"):将abcd从第loc位置(最小loc为0)开始插入。
- erase(loc):将str串从第loc位置(最小loc为0)开始删除,一直删到str结尾。
- erase(loc, 4):将str串从第loc位置(最小loc为0)开始删除,删除四个字符。
- clear():将字符串清空。
- 寻找特定字符或字符串:
- find(字符或字符串):找到相应的字符或字符串则返回对应的下标,若找不到则返回string::npos。
- 返回字符串的子串:
- substr(3, 5):从第3位置(最小为0)开始,返回长度为5的字符串。
2、KMP算法(具体代码博主看了好几遍也还没看懂,抄的王道o(╥﹏╥)o,见应用实例中题13):
- 核心思想:模式串失配后,并不是从下一个字符开始重新匹配,而是利用已有的信息,跳过一些不可能成功的匹配,以便尽量减少模式串与主串的匹配次数,进而达到快速匹配的目的。
- 名词定义:
- next数组(前缀数组):记录字符串匹配过程中失配可以向前多跳的字符个数。
- 前缀:除最后一个字符外,字符串的所有头部子串。
- 后缀:除第一个字符外,字符串的所有尾部子串。
- 部分匹配值:字符串前缀和后缀最长相等前后缀的长度。
- 移动位数 = 已匹配的字符数 - 对应的部分匹配值
- 时间复杂度:O(n + m)
- 遇到第一个匹配的地方后,停止搜索,输出位置,代码如下:
#include <iostream>
#include <string>
using namespace std;
const int MAX_N = 10000;
int nextTable[MAX_N];
void GetNext(string pattern){
int j = 0;
nextTable[j] = -1;
int i = nextTable[j];
while(j < pattern.size()){
if(i == -1 || pattern[j] == pattern[i]){
i++;
j++;
nextTable[j] = i;
}else{
i = nextTable[i];
}
}
}
int KMP(string text, string pattern){
GetNext(pattern);
int i = 0, j = 0;
while(i < text.size() && j < pattern.size()){
if(j == -1 || text[i] == pattern[j]){
i++;
j++;
}else{
j = nextTable[j];
}
}
if(j == pattern.size()){
return i - j + 1;
}else{
return -1;
}
}
int main(){
int nextTable[MAX_N];
memset(nextTable, 0, sizeof(nextTable));
string s1, s2;
while(cin >> s1 >> s2){
cout << KMP(s1, s2) << endl;
}
return 0;
}
二、应用实例
1、题目描述:写个算法,对2个小于1000000000的输入,求结果。 特殊乘法举例:123 * 45 = 1*4 +1*5 +2*4 +2*5 +3*4+3*5【清华大学】
- 输入格式:两个小于1000000000的数
- 输出格式:输入可能有多组数据,对于每一组数据,输出Input中的两个数按照题目要求的方法进行运算后得到的结果。
- 样例输入:
- 123 45
- 样例输出:
- 54
示例代码:
#include <iostream>
#include <string>
using namespace std;
int main(){
string str1, str2;
while(cin >> str1 >> str2){
int count = 0;
for(int i = 0; i < str1.size(); i++){
for(int j = 0; j < str2.size(); j++){
count += (str1[i] - '0') * (str2[j] - '0');
}
}
cout << count << endl;
}
return 0;
}
2、题目描述:在情报传递过程中,为了防止情报被截获,往往需要对情报用一定的方式加密,简单的加密算法虽然不足以完全避免情报被破译,但仍然能防止情报被轻易的识别。我们给出一种最简的的加密方法,对给定的一个字符串,把其中从a-y,A-Y的字母用其后继字母替代,把z和Z用a和A替代,则可得到一个简单的加密字符串。【北京大学】
- 输入格式:读取这一行字符串,每个字符串长度小于80个字符
- 输出格式:对于每组数据,输出每行字符串的加密字符串。
- 样例输入:
- Hello! How are you!
- 样例输出:
- Ifmmp! Ipx bsf zpv!
示例代码:
#include <iostream>
#include <string>
using namespace std;
void Code(string s){
for(int i = 0; i < s.size(); i++){
if(s[i] == 'z' || s[i] == 'Z'){
s[i] -= 25;
}else if((s[i] >= 'a' && s[i] <= 'y')
|| (s[i] >= 'A' && s[i] <= 'Y')){
s[i] += 1;
}
}
cout << s << endl;
}
int main(){
string s;
while(getline(cin ,s)){
Code(s);
}
return 0;
}
3、题目描述:Julius Caesar曾经使用过一种很简单的密码。 对于明文中的每个字符,将它用它字母表中后5位对应的字符来代替,这样就得到了密文。 比如字符A用F来代替。如下是密文和明文中字符的对应关系。 密文 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 明文 V W X Y Z A B C D E F G H I J K L M N O P Q R S T U 你的任务是对给定的密文进行解密得到明文。 你需要注意的是,密文中出现的字母都是大写字母。密文中也包括非字母的字符,对这些字符不用进行解码。【北京大学】
- 输入格式:输入中的测试数据不超过100组。每组数据都有如下的形式,而且各组测试数据之间没有空白的行。
一组测试数据包括三部分:
1. 起始行 - 一行,包括字符串 "START"
2. 密文 - 一行,给出密文,密文不为空,而且其中的字符数不超过200
3. 结束行 - 一行,包括字符串 "END"
在最后一组测试数据之后有一行,包括字符串 "ENDOFINPUT"。 - 输出格式:对每组数据,都有一行输出,给出密文对应的明文。
- 样例输入:
- START
- NS BFW, JAJSYX TK NRUTWYFSHJ FWJ YMJ WJXZQY TK YWNANFQ HFZXJX
- END
- START
- N BTZQI WFYMJW GJ KNWXY NS F QNYYQJ NGJWNFS ANQQFLJ YMFS XJHTSI NS WTRJ
- END
- START
- IFSLJW PSTBX KZQQ BJQQ YMFY HFJXFW NX RTWJ IFSLJWTZX YMFS MJ
- END
- ENDOFINPUT
- 样例输出:
- IN WAR, EVENTS OF IMPORTANCE ARE THE RESULT OF TRIVIAL CAUSES
- I WOULD RATHER BE FIRST IN A LITTLE IBERIAN VILLAGE THAN SECOND IN ROME
- DANGER KNOWS FULL WELL THAT CAESAR IS MORE DANGEROUS THAN HE
示例代码:
#include <iostream>
#include <string>
using namespace std;
int main(){
string s;
while(getline(cin, s) && s != "ENDOFINPUT"){
getline(cin, s);
for(int i = 0; i < s.size(); i++){
if(s[i] >= 'F' && s[i] <= 'Z'){
s[i] -= 5;
}else if(s[i] >= 'A' && s[i] <= 'E'){
s[i] += 21;
}
}
cout << s << endl;
getline(cin, s);
}
return 0;
}
4、题目描述:统计一个给定字符串中指定的字符出现的次数。【浙江大学】
- 输入格式:测试输入包含若干测试用例,每个测试用例包含2行,第1行为一个长度不超过5的字符串,第2行为一个长度不超过80的字符串。注意这里的字符串包含空格,即空格也可能是要求被统计的字符之一。当读到'#'时输入结束,相应的结果不要输出。
- 输出格式:对每个测试用例,统计第1行中字符串的每个字符在第2行字符串中出现的次数,按如下格式输出:
c0 n0
c1 n1
c2 n2
...
其中ci是第1行中第i个字符,ni是ci出现的次数。 - 样例输入:
- I
- THIS IS A TEST
- i ng
- this is a long test string
- #
- 样例输出:
- I 2
- i 3
- 5
- n 2
- g 2
示例代码1:(4ms,504k)
#include <iostream>
#include <string>
using namespace std;
int main(){
string searchStr;
string inputStr;
while(getline(cin, searchStr) && searchStr != "#"){
getline(cin, inputStr);
for(int i = 0; i < searchStr.size(); i++){
int count = 0;
for(int j = 0; j < inputStr.size(); j++){
if(inputStr[j] == searchStr[i]){
count++;
}
}
cout << searchStr[i] << " " << count << endl;
}
}
return 0;
}
示例代码2:(4ms,500k)
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
int a[128];
int main(){
string searchStr;
string inputStr;
while(getline(cin, searchStr) && searchStr != "#"){
memset(a, 0, sizeof(a));
getline(cin, inputStr);
for(int j = 0; j < inputStr.size(); j++){
a[inputStr[j]]++;
}
for(int i = 0; i < searchStr.size(); i++){
cout << searchStr[i] << " " << a[searchStr[i]] << endl;
}
}
return 0;
}
5、题目描述:输入一行字符串,计算其中A-Z大写字母出现的次数【上海交通大学】
- 输入格式:案例可能有多组,每个案例输入为一行字符串。
- 输出格式:对每个案例按A-Z的顺序输出其中大写字母出现的次数。
- 样例输入:
- DFJEIWFNQLEF0395823048+_+JDLSFJDLSJFKK
- 样例输出:
- A:0
B:0
C:0
D:3
E:2
F:5
G:0
H:0
I:1
J:4
K:2
L:3
M:0
N:1
O:0
P:0
Q:1
R:0
S:2
T:0
U:0
V:0
W:1
X:0
Y:0
Z:0
- A:0
示例代码:
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
int a[26];
int main(){
string s;
while(getline(cin, s)){
memset(a, 0, sizeof(a));
for(int i = 0; i < s.size(); i++){
if(s[i] >= 'A' && s[i] <= 'Z'){
a[s[i] - 'A']++;
}
}
for(int i = 0; i < 26; i++){
cout << char('A' + i) << ":" << a[i] << endl;
}
}
return 0;
}
6、题目描述:给定一个英文字符串,请写一段代码找出这个字符串中首先出现三次的那个英文字符(需要区分大小写)。【2017校招真题在线编程】
- 输入格式:输入数据一个字符串,包括字母,数字等。
- 输出格式:输出首先出现三次的那个英文字符
- 样例输入:
- Have you ever gone shopping and
- 样例输出:
- 3
示例代码:
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
int a[128];
int main(){
string s;
while(getline(cin, s)){
memset(a, 0, sizeof(a));
for(int i = 0; i < s.size(); i++){
if((s[i] >= 'A' && s[i] <= 'Z') || (s[i] >= 'a' && s[i] <= 'z')){
a[s[i]]++;
if(a[s[i]] == 3){
cout << s[i] << endl;
break;
}
}
}
}
return 0;
}
7、题目描述:在 skew binary表示中, 第 k 位的值x[k]表示x[k]*(2^(k+1)-1)。 每个位上的可能数字是0 或 1,最后面一个非零位可以是2, 例如, 10120(skew) = 1*(2^5-1) + 0*(2^4-1) + 1*(2^3-1) + 2*(2^2-1) + 0*(2^1-1) = 31 + 0 + 7 + 6 + 0 = 44. 前十个skew数是 0、1、2、10、11、12、20、100、101、以及102。【北京大学】
- 输入格式:输入包含一行或多行,每行包含一个整数n。如果 n = 0 表示输入结束,否则n是一个skew数
- 输出格式:可能有多组测试数据,对于每一个输入,输出它的十进制表示。转换成十进制后, n 不超过 2^31-1 = 2147483647
- 样例输入:
- 10120
- 200000000000000000000000000000
- 10
- 1000000000000000000000000000000
- 11
- 100
- 11111000001110000101101102000
- 0
- 样例输出:
- 44
- 2147483646
- 3
- 2147483647
- 4
- 7
- 1041110737
示例代码:
#include <iostream>
#include <string>
using namespace std;
int main(){
string s;
while(cin >> s && s != "0"){
long long count = 0;
long long tmp = 2;
for(int i = s.size() - 1; i >= 0; i--){
count += (s[i] - '0') * (tmp - 1);
tmp *= 2;
}
cout << count << endl;
}
return 0;
}
8、题目描述:输入一个字符串,以回车结束(字符串长度<=100)。该字符串由若干个单词组成,单词之间用一个空格隔开,所有单词区分大小写。现需要将其中的某个单词替换成另一个单词,并输出替换之后的字符串。【北京大学】
- 输入格式:多组数据。每组数据输入包括3行,第1行是包含多个单词的字符串 s,第2行是待替换的单词a(长度<=100)。第3行是a将被替换的单词b(长度<=100)。s, a, b 最前面和最后面都没有空格.
- 输出格式:每个测试数据输出只有 1 行,将s中所有单词a替换成b之后的字符串。
- 样例输入:
- You want someone to help you
- You
- I
- 样例输出:
- I want someone to help you
示例代码:
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main(){
string inputStr, originWord, replaceWord;
while(getline(cin, inputStr)){
getline(cin, originWord);
getline(cin, replaceWord);
int index = 0;
string word = "";
while(index <= inputStr.size() - 1){
if(index != 0){
cout << " ";
}
word = "";
while(index <= inputStr.size() - 1 && isalpha(inputStr[index])){
word += inputStr[index++];
}
if(word == originWord){
cout << replaceWord;
}else{
cout << word;
}
index++;
}
cout << endl;
}
return 0;
}
9、题目描述:对一个字符串中的所有单词,如果单词的首字母不是大写字母,则把单词的首字母变成大写字母。 在字符串中,单词之间通过空白符分隔,空白符包括:空格(' ')、制表符('\t')、回车符('\r')、换行符('\n')。【北京大学】
- 输入格式:输入一行:待处理的字符串(长度小于100)。
- 输出格式:可能有多组测试数据,对于每组数据,输出一行:转换后的字符串。
- 样例输入:
- if so, you already have a google account. you can sign in on the right.
- 样例输出:
- If So, You Already Have A Google Account. You Can Sign In On The Right.
示例代码:
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main(){
string s;
while(getline(cin, s)){
int index = 0;
string word;
while(index <= s.size() - 1){
if(s[index] == ' ' || s[index] == '\t' || s[index] == '\r' || s[index] == '\n'){
cout << s[index++];
continue;
}
word = "";
while(index <= s.size() - 1 && s[index] != ' '&& s[index] != '\t' && s[index] != '\r' && s[index] != '\n'){
word += s[index++];
}
word[0] = toupper(word[0]);
cout << word;
}
cout << endl;
}
return 0;
}
10、题目描述:求2个浮点数相加的和 题目中输入输出中出现浮点数都有如下的形式: P1P2...Pi.Q1Q2...Qj 对于整数部分,P1P2...Pi是一个非负整数 对于小数部分,Qj不等于0【北京大学】
- 输入格式:对于每组案例,每组测试数据占2行,分别是两个加数。
- 输出格式:每组案例是n行,每组测试数据有一行输出是相应的和。输出保证一定是一个小数部分不为0的浮点数
- 样例输入:
- 0.111111111111111111111111111111
- 0.111111111111111111111111111111
- 样例输出:
- 0.222222222222222222222222222222
示例代码:
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1, s2;
int pointLoc1, pointLoc2;
while(cin >> s1 >> s2){
if(s1.find('.') == s1.npos){
pointLoc1 = s1.size();
}else{
pointLoc1 = s1.find('.');
}
if(s2.find('.') == s2.npos){
pointLoc2 = s2.size();
}else{
pointLoc2 = s2.find('.');
}
int distance = pointLoc1 - pointLoc2;
int distance2 = s1.size() - pointLoc1 - s2.size() + pointLoc2;
string tmp = "", tmp2 = "";
for(int i = 0; i < abs(distance); i++){
tmp += "0";
}
for(int i = 0; i < abs(distance2); i++){
tmp2 += "0";
}
if(pointLoc1 > pointLoc2){
s2 = tmp + s2;
}else{
s1 = tmp + s1;
}
if(distance2 > 0){
s2 += tmp2;
}else{
s1 += tmp2;
}
int carry = 0;
for(int i = s1.size() - 1; i >= 0; i--){
if(s1[i] == '.'){
continue;
}
int current = carry + s1[i] - '0' + s2[i] - '0';
carry = current / 10;
s1[i] = current % 10 + '0';
}
if(carry != 0){
char c = carry + '0';
s1 = c + s1;
}
cout << s1 << endl;
}
return 0;
}
11、题目描述:对于一个字符串,将其后缀子串进行排序,例如grain 其子串有: grain rain ain in n 然后对各子串按字典顺序排序,即: ain,grain,in,n,rain【上海交通大学】
- 输入格式:每个案例为一行字符串。
- 输出格式:将子串排序输出
- 样例输入:
- grain
- 样例输出:
- ain
- grain
- in
- n
- rain
示例代码:
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;
vector<string> myVector;
int main(){
string s;
while(cin >> s){
for(int i = 0; i < s.size(); i++){
myVector.push_back(s.substr(i));
}
sort(myVector.begin(), myVector.end());
for(int i = 0; i < myVector.size(); i++){
cout << myVector[i] << endl;
}
myVector.clear();
}
return 0;
}
12、题目描述:读入数据string[ ],然后读入一个短字符串。要求查找string[ ]中和短字符串的所有匹配,输出行号、匹配字符串。匹配时不区分大小写,并且可以有多个用中括号表示的模式匹配。如“aa[123]bb”,就是说aa1bb、aa2bb、aa3bb都算匹配。【北京航天航空大学】
- 输入格式:输入有多组数据。每组数据第一行输入n(1<=n<=1000),从第二行开始输入n个字符串(不含空格),接下来输入一个匹配字符串。
- 输出格式:输出匹配到的字符串的行号和该字符串(匹配时不区分大小写)。
- 样例输入:
- 4
- Aab
- a2B
- ab
- ABB
- a[a2b]b
- 样例输出:
- 1 Aab
- 2 a2B
- 4 ABB
示例代码:
#include <iostream>
#include <vector>
#include <string>
#include <queue>
using namespace std;
vector<string> myVector;
vector<string> resultVector;
vector<string> resultPattern;
queue<string> tmpPattern;
void Getpattern(){
while(!tmpPattern.empty()){
string s = tmpPattern.front();
tmpPattern.pop();
int begin = s.find('['), end = s.find(']');
if(begin != s.npos){
string patternStr = s.substr(begin + 1, end - begin - 1);
s.erase(begin, end - begin + 1);
for(int i = 0; i < patternStr.size(); i++){
string sCopy = s;
string tmp = "";
tmpPattern.push(sCopy.insert(begin, tmp + patternStr[i]));
}
}else{
resultPattern.push_back(s);
}
}
}
int main(){
int n;
string s;
while(cin >> n){
for(int i = 0; i < n; i++){
cin >> s;
resultVector.push_back(s);
for(int j = 0; j < s.size(); j++){
s[j] = tolower(s[j]);
}
myVector.push_back(s);
}
cin >> s;
for(int j = 0; j < s.size(); j++){
s[j] = tolower(s[j]);
}
tmpPattern.push(s);
Getpattern();
for(int i = 0; i < myVector.size(); i++){
for(int j = 0; j < resultPattern.size(); j++){
if(myVector[i].find(resultPattern[j]) != myVector[i].npos){
cout << i + 1 << " " << resultVector[i] << endl;
break;
}
}
}
myVector.clear();
resultVector.clear();
resultPattern.clear();
}
return 0;
}
13、题目描述: Finding all occurrences of a pattern in a text is a problem that arises frequently in text-editing programs. Typically,the text is a document being edited,and the pattern searched for is a particular word supplied by the user. We assume that the text is an array T[1..n] of length n and that the pattern is an array P[1..m] of length m<=n.We further assume that the elements of P and T are all alphabets(∑={a,b...,z}).The character arrays P and T are often called strings of characters. We say that pattern P occurs with shift s in the text T if 0<=s<=n and T[s+1..s+m] = P[1..m](that is if T[s+j]=P[j],for 1<=j<=m). If P occurs with shift s in T,then we call s a valid shift;otherwise,we calls a invalid shift. Your task is to calculate the number of vald shifts for the given text T and p attern P.【上海交通大学】
- 输入格式:For each case, there are two strings T and P on a line,separated by a single space.You may assume both the length of T and P will not exceed 10^6.
- 输出格式:You should output a number on a separate line,which indicates the number of valid shifts for the given text T and pattern P.
- 样例输入:
- abababab abab
- 样例输出:
- 3
示例代码1:
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1, s2;
while(cin >> s1 >> s2){
int count = 0;
int pos = s1.find(s2);
while(pos != s1.npos){
count++;
s1 = s1.substr(pos + 1);
pos = s1.find(s2);
}
cout << count << endl;
}
return 0;
}
示例代码2:(KMP)
#include <iostream>
#include <string>
using namespace std;
const int MAX_N = 1000010;
int nextTable[MAX_N];
void GetNext(string pattern){
int j = 0;
nextTable[j] = -1;
int i = nextTable[j];
while(j < pattern.size()){
if(i == -1 || pattern[j] == pattern[i]){
i++;
j++;
nextTable[j] = i;
}else{
i = nextTable[i];
}
}
}
int KMP(string text, string pattern){
GetNext(pattern);
int i = 0, j = 0, number = 0;
while(i < text.size()){
if(j == -1 || text[i] == pattern[j]){
i++;
j++;
}else{
j = nextTable[j];
}
if(j == pattern.size()){
number++;
j = nextTable[j];
}
}
return number;
}
int main(){
string s1, s2;
while(cin >> s1 >> s2){
cout << KMP(s1, s2) << endl;
}
return 0;
}
参考文献:
[1]Thomas.H.Cormen Charles E. Leiseron、Ronald L. Rivest Clifford Srein. 算法导论(第3版). [M]北京:机械工业出版社,2013.01;
[2]杨泽邦、赵霖. 计算机考研——机试指南(第2版). [M]北京:电子工业出版社,2019.11;