代码随想录算法训练营第九天| leetcode 151、28、459、卡码网55、KMP算法

翻转字符串中的单词 leetcode 151

class Solution {
public:
    void reverse(string &s,int start,int end){
        while(start<end){
            swap(s[start++],s[end--]);
        }
    }
    void removeExtraSpaces(string& s){
        int slow=0;
        for(int fast=0;fast<s.size();fast++){
            if(s[fast]!=' '){
                if(slow!=0) s[slow++]=' ';
                while(fast<s.size()&&s[fast]!=' '){
                    s[slow++]=s[fast++];
                }
            }
        }
        s.resize(slow);
    }
   string reverseWords(string s) {
    removeExtraSpaces(s);
    reverse(s,0,s.size()-1);
    int start=0;
    for(int i=0;i<=s.size();i++){
        if(i==s.size()||s[i]==' '){
            reverse(s,start,i-1);
            start=i+1;
        }
    }
    return s;
   }
};

总结

1.实现了一个左闭右闭翻转字符串的reverse函数。

2.基于双指针的思想实现了一个删除字符串多余空格的removeExtraSpaces函数。快指针用于查找更新数组的元素,慢指针负责指向需要更新的位置和在除了第一个单词之外的每个单词前加空格。fast快指针遍历整个字符串,直到找到不为空的位置,每找到不为空的位置就说明找到了第一个单词或者一个新的单词,除了第一个单词之外剩余的单词前使用慢指针加空格,之后使用while循环将每一个单词更新到数组中新的位置。

3.revreseWords函数中,start用于更新每个单词的起始位置。

出现的错误

自己定义函数时使用值传递的方式,void reverse(string s,int start,int end),在函数reverse内部对字符串s的修改实际上是对s的一个副本的修改,不会影响到原始的字符串,应该使用传引用的方式定义函数,void reverse(string &s,int start,int end)。

右旋转字符串 卡码网 55

#include<iostream>
#include<algorithm>
using namespace std;

int main()
{
    int k=0;
    string s;
    cin>>k;
    cin>>s;
    int len=s.size();
    reverse(s.begin(),s.end());
    reverse(s.begin(),s.begin()+k);
    reverse(s.begin()+k,s.end());
    cout<<s<<endl;
}

总结

1.先反转整个字符串,然后反转前k个字符串,再反转剩余字符串达到题目要求。

2.#include <algorithm>包含algorithm以使用reverse函数。

3.C++库中的reverse函数是左闭右开的,s.begin()获取字符串开始的迭代器,指向字符串中第一个字符,s.end()获取字符串结束的迭代器,指向字符串中最后一个字符的下一个位置。

KMP算法

1.KMP:应用于字符串的匹配上,当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配。时间复杂度O(m+n)。

2.前缀:不包含最后一个字符的所有以第一个字符开头的连续子串

   后缀:不包含第一个字符的所有以最后一个字符结尾的连续子串

例如:字符串aabaaf,的前缀有:a,aa,aab,aaba,aabaa

                                        后缀有:f,af,aaf,baaf,abaaf。

3.最长相等前后缀长度:包含第一个字符的所有连续子串前缀和后缀相同时,前缀(后缀)的长度

例如:字符串aabaaf

a:0,aa:1,aab:0,aaba:1,aabaa:2,aabaaf:0。

4.前缀表:存放最长相等前后缀长度的表。前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

对于字符串aabaaf来说,前缀表为:[0,1,0,1,2,0],对应关系如下图(代码随想录):

这样我们在文本串中找模式串遍历到不匹配的位置i时,那么前一个位置i-1对应字符的前缀表中最长相等前后缀长度为L,那么模式串中对应L的下标位置就是我们要重新开始遍历i指向的位置。这样可以避免从头开始判断。

找出字符串中第一个匹配的下标 leetcode 28

class Solution {
public:
    void getNext(int *next,const 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;
            }
        }
    int strStr(string haystack, string needle) {
        if(needle.size()==0) return 0;
        vector<int> next(needle.size());
        getNext(&next[0],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++;
            }
            if(j==needle.size()){
                return i-j+1;
            }
        }
        return -1;
    }
};

总结

1.getNext函数中,i遍历字符串且代表后缀末尾,j代表前缀末尾同时还代表i之前包括i子串的最长相等前后缀的长度。不同的next数组(存放前缀表)有不同的实现方式,上述代码表示不减一的前缀表。next数组实现:初始化,处理前后缀不同的情况,处理前后缀相同的情况,更新next数组。

前缀表减一的getNext函数实现:

class Solution {
public:
    void getNext(int *next,string s){
        int j=-1;
        next[0]=j;
        for(int i=1;i<s.size();i++){
            while(j>=0&&s[i]!=s[j+1]){
                j=next[j];
            }
            if(s[i]==s[j+1]){
                j++;
            }
            next[i]=j;
        }
    }
    int strStr(string haystack, string needle) {
        if(needle.size()==0) return 0;
        vector<int> next(needle.size());
        int j=-1;
        getNext(&next[0],needle);
        for(int i=0;i<haystack.size();i++){
            while(j>=0&&haystack[i]!=needle[j+1]){
                j=next[j];
            }
            if(haystack[i]==needle[j+1]){
                j++;
            }
            if(j==needle.size()-1){
                return i-needle.size()+1;
            }
        }
        return -1;
    }
};

出现的错误

1.getNext函数传入数组next时前面应该加“*”号,使用指针来传递数组。

2.getNext(&next,needle),这样写是错误的,getNext函数第一个形参是int类型的指针,而这样写传入的是一个vector容器的引用。还可以这样改:

//vector<int> next(needle.size());
int next[needle.size()];
getNext(next,needle);

这样就不能在next前加&了,因为直接写next就相当于传入指向next数组第一个元素的指针,加了引用就相当于传入整个数组与形参类型不对应。

重复的字符串 leetcode 459

解法一:移动匹配

class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        string ss=s+s;
        ss.erase(ss.begin());
        ss.erase(ss.end()-1);
        if(ss.find(s)!=std::string::npos) return true;
        return false;
    }
};

总结

1.将字符串相加组成一个新的字符串,去头去尾之后看还能不能再新的字符串中找到原来的字符串,如果可以就说明字符串可以由它的某个子串重复多次构成。

2.ss.find(s)库函数,用于查找s在ss中第一次出现的位置,如果找到了就返回一个指向s第一次出现起始位置的迭代器,如果ss中没有s,就返回std::string::npos。

3.ss.begin()指向字符串ss的第一个字符,ss.end()指向字符串ss最后一个字符后一位的位置。

解法二:KMP算法实现

如果一个字符串可以由它的某个子串重复构成的话,那么它的最小重复子串就是它的最长相等前后缀不包含的那一部分。如下图(源自代码随想录):

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;
        }
    }
    bool repeatedSubstringPattern(string s) {
        if(s.size()==0) return false;
        int next[s.size()];
        getNext(next,s);
        int len=s.size();
        if(next[len-1]!=0&&len%(len-next[len-1])==0) return true;
        else return false;
    }
};

总结

1.数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。

2.next[len-1]!=0,确保存在一个非空的子串,因为next数组的最后一个元素对应的是字符串的最后一个字符,如果这个值为0,意味着没有非空子串与其匹配。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值