Studying-代码随想录训练营day9| 151.反转字符串里的单词、卡码网:55.右旋转字符串、28.实现strStr()、459.重复的子字符串、字符串总结、双指针回顾

第九天,💪💪(ง •_•)ง,编程语言:C++

目录

151.反转字符串里的单词

卡码网:55.右旋转字符串

28.实现strStr()

459.重复的子字符串

字符串总结

双指针回顾


151.反转字符串里的单词

文档讲解:代码随想录反转字符串里的单词

视频讲解:手撕反转字符串里的单词

题目:

学习:本题可以不使用辅助空间,空间复杂度为O(1)。难点主要在于:1.去除多余的空格;2.当每个单词交换位置。首先可以采取双指针的方法,将多余的空格取消,类似于之前数组中采用双指针赋值的方式,slow指针指向当前正确的赋值下标,fast指针指向原字符串需要取出的下标;每个单词交换位置,可以采取先将整个单词反转,然后逐个单词再次反转的方式(这个思路很关键,后面还会用到),这样就可以实现将每个单词交换位置。

代码: 

1.移除多余空格;

2.将整个字符串反转;

3.将每个单词反转;

//时间复杂度O(n)
//空间复杂度O(1)
class Solution {
public:
    void removespace(string& s) {
        //快慢指针
        int slow = 0;
        int fast = 0;
        
        for (; 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) {
        //去掉多余空格
        removespace(s);
        reverse(s.begin(), s.end());
        int i = 0;
        for(int j = 0; j < s.size(); j++) {
            if(s[j] == ' ') {
                reverse(s.begin() + i, s.begin() + j);
                i = j + 1;
            }
        }
        //处理最后一个单词
        reverse(s.begin() + i, s.end());
        return s;
    }
};

收获:

  1. 这里要注意移除空格,不要采用erase的方式,因为erase本身就是O(n)的操作,如果在erase操作上面还套了一个for循环遍历,那整体的复杂度就来到了O(n^2)。 
  2. 在用双指针法移除空格后,还需要注意使用resize重新设置一下字符串的大小。

卡码网:55.右旋转字符串

 文档讲解:代码随想录右旋转字符串

题目: 

学习:本题仍可以不申请额外空间,进行操作。类似与上题,本题要求把后面几个字母反转到前面来同样可以采取,先整体反转,再局部反转的方式进行。

代码:

//时间复杂度O(n)
//空间复杂度O(1)
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;

int main( ) {
    int k = 0;
    cin >> k;
    string s;
    cin >> s;
    
    reverse(s.begin(), s.end());
    reverse(s.begin(), s.begin() + k);
    reverse(s.begin() + k, s.end());
    cout << s << endl;
    
    system("pause");
    return 0;
}

28.实现strStr()

文档讲解:代码随想录实现strStr()

视频讲解:KMP理论篇KMP代码篇

题目:

学习:本题最简单的方式当然是两个循环暴力破解,时间复杂度为O(n*m)。但显然本题是数据结构算法中最经典的一道题,字符串匹配问题,在本题中最重要的就是使用KMP算法。

KMP算法:KMP算法的本质是待匹配字符串自身存在一定的匹配关联性,例如aadaaf字符串,当最后一个' f ' 字符匹配失败的时候,我们无需回到最开头匹配,而是可以直接匹配第三个字符' d '是否相当,因为我们此时已经知道前两个一定是'aa'了。

next数组:我们如何知道当前字符匹配不对的时候,该跳到哪个位置,就是依靠得到的next数组。next数组只与需要匹配的字符串有关和长的文本串无关。next数组得到的方式,主要在于公共前后缀的匹配长度,建议看视频加上手动操作学习。

代码:

//时间复杂度O(n+m)
//空间复杂度O(m)
class Solution {
public:
    void getNextval(int* next, const string& s) {
        int i = 0;
        next[0] = -1;
        next[1] = 0;
        int j = 2;
        for(; j < s.size(); j++) {
            while(i > 0 && s[i] != s[j - 1]) {
                i = next[i];
            }
            if (s[i] == s[j - 1]) {
                i++;
            }
            next[j] = i;
        }
        //优化next数组
        for (int z = 1; z < s.size(); z++) {
            //如果next数组的下标对应的字符串的数,和我当前下标的字符串的数一样,修改next数组,到对应下标。
            if (s[next[z]] == s[z]) {
                next[z] = next[next[z]];
            }
        }


    }
    int strStr(string haystack, string needle) {
        int n = needle.size();
        int m = haystack.size();
        if(n == 0) return 0;
        if(n > m) return -1;
        int next[n];
        if (n == 1) {
            next[0] = -1;
        }
        else{
            getNextval(next, needle);
        }

        int j = 0;
        for (int i = 0; i < m; i++) {

            while (j >= 0 && haystack[i] != needle[j]) {
                j = next[j];
            }
            if (j < 0) {
                j = 0;
                continue;
            }
            if (haystack[i] == needle[j]) {
                j++;
            }
            if(j == n) {
                return (i - j + 1);
            }
        }
        return -1;
    }
};

收获:本题还可以对next数组进行优化,关键在于当跳转到的位置,和原先字符相同,那其实也意味着下次匹配我们已经知道是不对的了,就可以优化next数组。


459.重复的子字符串

文档讲解: 代码随想录重复的子字符串

视频讲解:手撕重复的子字符串

题目:

学习:本题同样可以采用暴力循环的方式,一个寻找子串,一个遍历主串,这种方式时间复杂度为O(n^2)。但本题主要还有两个简化的解决办法:1.移动匹配;2.KMP算法。

1.移动匹配:

当字符串内部由重复的子串组成的时候,它前后一定拥有着相同的子串,因此可以采用s+s的方式,这样组成的字符串,内部一定还会有个主串。

代码:

//时间复杂度O(n)
//空间复杂度O(1)
class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        string t = s + s;
        t.erase(t.begin()); t.erase(t.end() - 1); // 掐头去尾
        if (t.find(s) != string::npos) return true; // 当字符串没有找到时返回一个npos位置
        return false;
    }
};

2.KMP算法:

在一个串中查找是否出现过另一个串,这是KMP的看家本领。当一个字符串由重复子串组成的,最长相等前后缀不包含的子串就是最小重复子串。

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

代码:

//时间复杂度O(n)
//空间复杂度O(1)
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;
    }
};

字符串总结

文档讲解:代码随想录字符串总结

字符串的定义:字符串是若干字符组成的有限序列,也可以理解为是一个字符数组。但要注意很多语言对字符串做了特殊的规定。

字符串包含的库函数众多,但是否要使用库函数,重点在于你所使用的库函数是不是你方法中的重点,以及它的时间复杂度是否合适。

字符串类类型的题目,往往想法比较简单,但是实现起来并不容易,复杂的字符串题目非常考验对代码的掌控能力。其中:双指针法是字符串处理的常客;KMP算法是字符串查找最重要的算法,要十分注意next数组的求取。


双指针回顾

文档讲解:代码随想录双指针回顾

数组:数组中我们可以通过两个指针在一个for循环下完成两个for循环的工作。使得时间复杂度从O(n^2)降到O(n)。

字符串:字符串中,双指针法可用于反转字符串、替换空格、删除冗余空格。也同样有助于降低时间复杂度。

链表:链表中,双指针法,是我们反转链表,寻找链表中的环的必要方法。

N数之和:N数之和中,重点还是在于,通过前后两个指针不算向中间逼近,在一个for循环下完成两个for循环的工作。

  • 28
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值