(一)基础理论
C++ string库函数
初始化:可以通过直接赋值或使用构造函数来初始化一个string对象。
获取长度:使用length()或size()成员函数可以获取字符串的长度。
插入:insert()函数可以在指定位置插入字符或另一个字符串。
替换:replace()函数可以替换字符串中的一部分内容。
添加:append()函数可以在字符串的末尾添加字符或另一个字符串。
赋值:可以使用=运算符或assign()函数来给string对象赋值。
删除:erase()函数可以删除字符串中的一段字符。
剪切:substr()函数可以从字符串中提取子串。
比较:可以使用compare()函数来比较两个字符串。
交换:swap()函数可以交换两个string对象的内容。
反转:reverse()函数可以反转字符串中的字符顺序。
数值转化:可以将字符串转换为数值类型,如stoi()(字符串转整数)。
查找:find()函数可以查找子串在字符串中的位置。
迭代器:string类支持使用迭代器来访问和修改字符串中的字符。
原文链接:https://blog.csdn.net/2301_80045850/article/details/135851809
(二)反转字符串
原地反转字符串
双指针法,头尾各有一个指针,首位指针位置交换
len = nums.size;
for(i = 0; j = len -1; i < j; i++, j--){
swap(nums[i], nums[j]);
}
class Solution {
public:
void reverseString(vector<char>& s) {
int len = s.size();
for(int i = 0, j = len - 1; i < j; i ++, j --){
swap(s[i], s[j]);
}
}
};
(三)反转字符串II
每次将 i 移动 2k,直到不够为止
class Solution {
public:
string reverseStr(string s, int k) {
int len = s.size();
for(int i = 0; i < len; i += (2*k)){
//当剩余字符大于k,只反转前k个字符
if(i + k <= s.size()){
reverse(s.begin() + i, s.begin() + i + k);
}else{
//当剩余字符小于k,将剩下的全部反转
reverse(s.begin() + i, s.end());
}
}
return s;
}
};
class Solution {
public:
//这里使用指针,才能真实改变s
void reverse(string& s, int begin, int end){
for(int i = begin, j = end - 1; i < j; i++, j--){
swap(s[i], s[j]);
}
}
string reverseStr(string s, int k) {
int len = s.size();
for(int i = 0; i < len; i += (2*k)){
//当剩余字符大于k,只反转前k个字符
if(i + k <= s.size()){
//从i反转到i+k,注意不包含i+k
reverse(s, i, i + k);
}else{
//当剩余字符小于k,将剩下的全部反转
reverse(s, i, len);
}
}
return s;
}
};
(四)替换数字
直接用库函数replace
int main(){
string s;
cin >> s;
for(int i = 0; i < s.size(); i++){
if(s[i] >= 'a' && s[i] <= 'z'){
continue;
}else{
s = s.replace(s.begin()+i, s.begin() + i + 1, "number");
}
}
cout << s;
return 0;
}
双指针法:
先预先给数组扩容带填充后的大小,然后从后向前操作
一个指针指向新数组(原数组基础上扩容得到)末位元素
一个指针指向原数组末位元素
从原数组最后向前遍历,如果是字母,就赋值给新数组,如果是数字,就从后到前填充number
直到两指针重合在原数组开头位置,结束
好处:
-
不用申请新的数组
-
从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
#include<iostream>
using namespace std;
#include <string>
int main(){
string s;
cin >> s;
int count = 0;
int oldSize = s.size();
for(int i = 0; i< s.size(); i++){
//注意这里是字符‘0’-‘9’
if(s[i] >= '0' && s[i] <= '9'){
count++;
}
}
s.resize(oldSize + 5*count);
int newSize = s.size();
int oldPointer = oldSize - 1;
int newPointer = newSize - 1;
//这里当i=j的时候,就是最后一个字母,不需要再赋值了,并且如果判定条件是i<=j的话,那么即使i=j=0,再同时减去1条件仍然成立,会导致越界
for(int i = oldPointer, j = newPointer; i < j; i--, j--){
if(s[i] >= 'a' && s[i] <= 'z'){
s[j] = s[i];
}else{
s[j] = 'r';
s[j - 1] = 'e';
s[j - 2] = 'b';
s[j - 3] = 'm';
s[j - 4] = 'u';
s[j - 5] = 'n';
j -= 5;
}
}
cout << s;
return 0;
}
(五)翻转字符串里的单词
第一步:将原字符串进行整体反转(reverse)
第二步:将每个单词内部进行反转(reverse)
(直接用split,然后将单词倒叙输出)
难点:
需要删除空格
开头和截尾的空格,中间多于2个空格,都要删除
移除空格 → 删除某一元素
(erase 的时间复杂度为 O(N),总时间复杂度O(N*N))
使用双指针,一个快指针,一个慢指针(参照移除元素节)
时间复杂度为O(N),空间复杂度为O(1)
slow = 0;
//去除多于空格
for(fast = 0; fast < s.size(); fast++){
if(s[fast] !=' '){
//在每个单词的起始位置前放一个空格,除了第一个单词
if(slow != 0) s[slow++] = ' ';
while(fast < s.size() && s[fast] != ' ') {
s[slow] = slow[fast];
fast ++;
slow ++;
}
}
s.resize(slow); //slow 的大小为新字符串的大小
class Solution {
public:
string reverseWords(string s) {
int slow = 0;
for(int fast = 0; fast < s.size(); fast ++){
if(s[fast] != ' '){
if(slow != 0){
s[slow ++] = ' ';
}
while(s[fast] !=' ' && fast < s.size()){
s[slow ++] = s[fast ++];
}
}
}
s.resize(slow);
reverse(s.begin(),s.end());
int before = 0; //记录单词的开始位置
for(int i = 0; i < s.size(); i++){
if(s[i] == ' '){
reverse(s.begin() + before, s.begin() + i);
before = i+1;
}
}
//反转最后一个单词
reverse(s.begin() + before, s.end());
return s;
}
};
(六)右旋转字符串
第一步:将字符串整体反转(reverse)
第二步:将两段子串分别反转(reverse)
第一段的长度为k,第二段的长度为s.size - k
#include <iostream>
using namespace std;
#include <string>
#include <algorithm>
int main(){
string s;
int k;
cin >> k;
cin >> s;
reverse(s.begin(),s.end());
reverse(s.begin(),s.begin() + k);
reverse(s.begin()+k,s.end());
cout << s << endl;
return 0;
}
(七)实现strStr()
KMP算法
解决字符串匹配问题
eg:
文本串: aabaabaaf
模式串:aabaaf
文本串里面是否有格式串
暴力匹配 时间复杂度 O(MN)
前缀表
前缀:包含首字母,不包含尾字母的所有子字符串
后缀:包含尾字母,不包含首字母的所有子字符串
最长相等前后缀长度
eg: aabaaf的前缀表:
010120
字符串 | 最长相等前后缀长度 |
---|---|
a | 0 |
aa | 1 |
aab | 0 |
aaba | 1 |
aabaa | 2 |
aabaaf | 0 |
KMP算法实现过程:
文本串 aabaabaaf
字符串 aabaaf
010120
当匹配到f时,f和b不匹配了,这时要找到与aabaa的后缀的相等前缀的后面一个字符开始匹配,因为aabaa的最长相等前后缀为2,因此要从下标为2的字符开始匹配
原理:把最后能匹配的后缀作为下一匹配的前缀,减少了比较次数
next数组(profix数组也可以)
统一减去1/统一右移(不涉及原理性的东西,只是实现方法不同)
①next整体后移一位,在遇到冲突后,找冲突字母对应的最长相等前后缀长度,回退
②next不后移,在遇到冲突后,找冲突字母前一个字母,对应的前缀表长度,回退
具体实现方法
//求Next数组
void getNext(next,s){
//初始化
//j -- 前缀末尾 i -- 后缀末尾
//j同时也是最长相等前后缀的长度
j = 0;
next[0] = 0;
for(int i = 1; i < s.size(); i++){
//前后缀不相同
//遇到冲突向前回退是一个连续的过程,不一定一次回退就能一步到位
while(j > 0 && s[i] != s[j]){
//遇到冲突找冲突前缀的前一个next值
j = next[j - 1];
}
//前后缀相同的情况
if(s[i] == s[j]){
j ++;
}
next[i] = j; //更新next数组的值
}
//前后缀相同
//next
class Solution {
public:
void getNext(int* next, string& s){
int j = 0;
next[0] = 0;
for(int i = 1; i < s.size(); i++){
while(j >0 && s[i] != s[j]){
j = next[j - 1];
}
if(s[i] == s[j]){
j ++;
}
next[i] = j;
}
}
int strStr(string haystack, string needle) {
int next[needle.size()];
getNext(next, needle);
int j = 0;
for(int i = 0; i < haystack.size(); i ++){
while(j > 0 && haystack[i] != needle[j]){
j = next[j - 1];
}
if(haystack[i] == needle[j]){
j++;
}
//注意这里因为匹配了最后一个字母后,j已经加了1,因此这时j指向needle末尾的下一个
if(j == needle.size()){
return (i - needle.size() + 1);
}
}
return -1;
}
};
(二)重复的子字符串
(1)移除匹配算法
将字符串s和自己拼接 s+s
例如:
s: abcabc
s + s: abcabc abcabc
s
将拼接后的字符串第一个和最后一个字母去掉(erase),看是否能再找到(find)s
如果能找到,则是s是由重复的子字符串拼接而成。
class Solution {
public:
bool repeatedSubstringPattern(string s) {
string ss = s;
ss.append(s);
ss.erase(ss.begin());
//注意这里不能直接用erase(ss.end()),因为erase导致ss长度变化,应该重新计算末位字母的指针
ss.erase(ss.begin()+ss.size() - 1);
if(ss.find(s) != -1){
return true;
}
return false;
}
};
(2)KMP算法
如果字符串s是由重复子字符串组成,那么s的最长相等前后缀所不包含的字符串即该重复子字符串
eg: 字符串s : abababab
最长相等前后缀:ababab
**abab**ab
计算next[size - 1]的值,即字符串最后一个字母的最长相等前后缀长度。
用s.size - next[size - 1],即计算出来的重复子字符串,
如果s.size可以整除(s.size - next[size - 1]),那么字符串s由重复子字符串构成,该重复子字符串即为s的前(s.size - next[size - 1])个字母。
class Solution {
public:
void getNext(int* next, string s){
int j = 0;
next[0] = 0;
for(int i = 1; i < s.size();i++){
while(j >0 && s[j] != s[i]){
j = next[j - 1];
}
if(s[j] == s[i]){
j++;
}
next[i] = j;
}
}
bool repeatedSubstringPattern(string s) {
int next[s.size()];
getNext(next, s);
int size = s.size() - next[s.size() - 1];
if(next[s.size() -1] > 0 && s.size()%size == 0){
return true;
}
return false;
}
};