代码随想录——字符串

(一)基础理论

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
字符串最长相等前后缀长度
a0
aa1
aab0
aaba1
aabaa2
aabaaf0

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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值