代码随想录-训练营-day8

151. 反转字符串中的单词 - 力扣(LeetCode)

这个题相对来说会更难处理一点:主要的难度在于我们对于空格的处理,尤其是最开始的空格和最后的空格。首先我们需要根据原字符串中的空格来区分单词,然后我们还需要去除掉多余的空格。

关于如何反转字符串,我们有很多种做法:就像我们昨天做的:我们可以swap,reverse,或者自己创建一个char类型的temp变量来短暂存储一个值。但这里我们还提供了另一种思路:我们利用栈的特性:先入后出。

我相信大家应该已经反应过来了,最简单的情况:我正着遍历一遍字符串并全部丢进栈里,然后再重新从栈顶读取一遍得到的字符串就是自带反转效果的了。当然这个题用栈的原因不止是这个,更重要的是对于空格:我们需要一个容器来短暂的存储字符串里被分割的单词,且这个容器最后能顺便帮我把反转的任务完成了(准确的说是反转再反转),于是我们就想到了栈。

脱离for循环后之所以还要最后进行一次操作是因为最后一个单词后没有空格,我们没法检测空格来触发,所以添加最后一次操作把最后的单词也塞进栈里去,然后最后检查单词后面有没有空格即可。

class Solution {
public:
    string reverseWords(string s) {
        stack<char> stk;
        string res;
        for(int i=s.size()-1;i>=0;--i){
            if(s[i]==' '){
                while(!stk.empty()){
                    res+=stk.top();
                    stk.pop();
                }
                if(res!=""&&res.back()!=' '){
                    res+=' ';
                }
            }
            else stk.push(s[i]);
        }
        while(!stk.empty()){
            res+=stk.top();
            stk.pop();
        }
        if(res!=""&&res.back()==' '){
            res.pop_back();
        }
        return res;
    }
};

55. 右旋字符串(第八期模拟笔试) (kamacoder.com)

其实这种题属于经验题,怎么说呢,做过一遍就都会做,但要是没做过就大概率写不出来。

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main(){
    int k;
    string s;
    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;
}

28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)

这下迎来我们字符串中最麻烦的部分:KMP算法了。

首先,我们的KMP算法主要的作用就是优化字符串匹配问题:当我们有一个模式串要去尝试匹配我们的文本串时,一般的匹配过程会有一些重复的过程,而我们的KMP算法可以免去这个过程。

简单地说,所谓的KMP算法就是去找我们的模式串每个字符的最长相等前后缀长度。前缀就是不包含最后一个字符的所有连续子串,后缀就是不包含第一个字符的所有连续子串。每当我们发现模式串中的某一个字符与文本串不匹配时,我们就去查询这个字符前一个字符的最长相等前后缀长度,然后从模式串的这个长度对应下标开始查询即可。

我想先解释一下为什么这样做是对的,我们用一个样例来解释会方便很多。

我们可以看到:当文本串和模式串在f处发生不匹配时,如果按照一般的思路,我们的首字母移位到下一个字符,又从头开始匹配。

但是我们通过KMP算法,我们得知f的上一个字符a的最长相等前后缀的长度为2,所以我们直接从b开始查找:其实这是非常直观的:我们可以看到f前有两个a,而我们一直遍历到f才不匹配说明此时在文本串中前面的部分都是一样的,那么我们通过查找模式串本身的最长的前缀后后缀的长度,我们知道模式串中b前面有两个a,后面也有两个a,然后我们又知道文本串中f前面其实也有两个a,那我们就不用再检查这两个a了,我们直接从b开始检查不就行了?

是的,其实本质上就是这样一个过程,现在让我们去看看具体的代码如何实现,这也是比较麻烦的一步。

class Solution {
public:
    void getNext(int *next,const 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;
        int next[needle.size()];
        getNext(next,needle);
        int j=-1;
        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;
    }
};

首先解释各个变量的含义:首先是我们的next数组其实就是前缀表(这里做了统一减一处理),也就是记录模式串中每个字符处的最长相等前后缀长度的数组。然后我们可以看到我们首先需要获得前缀表,也就是调用getNext函数:这个函数中有两个变量i和j,其中i代表的是我们后缀的末尾,而j代表的是我们前缀的末尾,除此之外j的含义里还有包括i在内的字串的最长相等前后缀的长度(也就是我们的next[i]),next[i]代表的是到i为止(包括i)的最长相等前后缀长度。为什么初始化的时候要把next[0]设定为-1?因为我们希望我们前缀表的下标与字符的长度对齐,而前缀表0代表空字符,我们就设置为-1。当前缀和后缀不相等时,我们需要去更新我们的前缀的末尾(KMP算法思想):我们将j换成我们记录的j处的最长相等前后缀长度(因为j实际上是从-1开始,我们需要保证j>=0才行,不然数组会越界),这样我们的j就可以来到之前记录过的前缀的位置。而如果是相等时,我们就可以增加j,同时更新了前缀的末尾元素与包含i的字串的最长相等前后缀长度。

然后就是我们的main函数部分,本质上也是和前缀表差不多,不过显然这里我们要从文本串的第一个字符开始比较,当我们发现j作为子串的最长相等前后缀长度与我们的模式串长度相等时,我们就可以去return了。

459. 重复的子字符串 - 力扣(LeetCode)

这个题可以理解为我们KMP算法的一个小应用,我们依然需要去获得前缀表,不过我们在main函数中具体的判定需要的是当我们获得最长前后缀的长度后我们将整个字符串的长度减去前缀(或者后缀)的长度后能否整除剩下的长度,可以的话说明我们剩下的字符串的内容也是由这个相等的前后缀组成的。

class Solution {
public:
    void getNext (int* next, const string& s){
        next[0] = -1;
        int j = -1;
        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;
        }
    }
    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] != -1 && len % (len - (next[len - 1] + 1)) == 0) {
            return true;
        }
        return false;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值