Day12 右旋转字符串 28KMP算法 459重复的子字符串 字符串总结

右旋转字符串

字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。  

例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。

         看到示例,我想到的是先将整个字符串翻转一下再看看变化,这样先变成了gfedcba,然后再把前两个翻转,剩下的翻转即可完成本题。代码如下:

#include
using namespace std;
#include

int main()
{
    int k;
    string s;
    cin >> k;
    cin >> s;
    reverse(s.begin(),s.end());
    for(int i = 0; i <= s.size(); i++)
    {
        if(i == k)
        {
            reverse(s.begin(),s.begin()+ k);
        }
        else if(i == s.size())
        {
            reverse(s.begin()+k, s.end());
        }
        else
        {
            continue;
        }
    }
    for(auto i : s)
        cout<

        这里代码随想录里面的代码写的更好,我这个加入了for循环其实是写复杂了:

// 版本一
#include
#include
using namespace std;
int main() {
    int n;
    string s;
    cin >> n;
    cin >> s;
    int len = s.size(); //获取长度

    reverse(s.begin(), s.end()); // 整体反转
    reverse(s.begin(), s.begin() + n); // 先反转前一段,长度n
    reverse(s.begin() + n, s.end()); // 再反转后一段

    cout << s << endl;

} 

这道题也可以先局部反转再整体反转,代码如下:

        

// 版本二 
#include
#include
using namespace std;
int main() {
    int n;
    string s;
    cin >> n;
    cin >> s;
    int len = s.size(); //获取长度
    reverse(s.begin(), s.begin() + len - n); // 先反转前一段,长度len-n ,注意这里是和版本一的区别
    reverse(s.begin() + len - n, s.end()); // 再反转后一段
    reverse(s.begin(), s.end()); // 整体反转
    cout << s << endl;

}

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

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回  -1 

示例 1:

输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

示例 2:

输入:haystack = "leetcode", needle = "leeto"
输出:-1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。

         本题是KMP的经典题目了,KMP的思想就是,当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。

        KMP主要就是应用在字符串匹配上,其中next数组就是一个前缀表(prefix table),前缀表是用来回退的,他记录了模式串与文本串不匹配的时候,模式串应该从哪里开始重新匹配。什么是前缀表:记录下标 i 之前(包括 i)的字符串中,有多大长度的相同前缀后缀。

        构造next数组:

        定义一个getNext来构建next数组,函数参数为指向next数组的指针,和一个字符串,构造next数组主要分为三步:1 初始化 2 处理前后缀不相同的情况 3 处理前后缀相同的情况。

        1.初始化 定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置。这里我们选取的next为初始值为-1,令 j 初始化为-1,next【i】表示 i 之前最长相等的前后缀长度(其实就是 j)所以初始化next【0】= j 。

        2.处理前后缀不相同的情况,因为初始化为-1,那么i就从1开始,进行s【i】与s【j+1】的比较,如果不行同,那么就要向前回退,next【j】就是记录 j 之前的子串的相同前后缀的长度,如果s【i】与s【j+1】不相同,那么就要找j+1前一个元素在next数组里的值(next【j】)。

        3.处理前后缀相同的情况,

如果 s[i] 与 s[j + 1] 相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。

        这段文字简直是:太难理解了!!!

        于是,我打算用自己的理解重新梳理一下:举个例子,“aabaaf”;

        首先 j = 0 ,,初始化next【0】=0,循环开始i= 1,前后缀相等,j++,此时next【1】= j =1;下一个循环 i = 2,此时前后缀a和b不相等了,进入while循环,j调到next【j-1】=0,停止循环,next【2】= 0;下一个循环i = 3,前后缀相等,j++,next【3】=1;下一个循环i=4,前后缀相等,j++,next【4】= j = 2;最后一个循环 i = 5,前后缀b不等于f,j 回退到next【j - 1】= 1,此时前后缀还是不相等,再次回退,到0,此时next【5】只能等于0了,next数组创建完毕。

        整体代码如下:

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[i] != s[j])
            j = next[j - 1];
        if(s[i] == s[j])
            j++;
        next[i] = j;
    }
}

        其中的思想我简单概括如下:我们可以把 j 当成模式串的末尾, i 当成文本串的末尾,从s串中找next数组的过程可以近似于文本串与模式串的对比过程。如果两个串对比的字符相等了,那么就证明next可以,同时将这种属性继承到下一次的对比中,如果没有匹配成功,那么之前匹配的应该已经差不多了吧,我们就可以跳到上一个next看看能不能匹配成功,可以理解成我们在匹配不成功时看看最长的模式串应该是多少,一点一点减,直到为0停止,特别像主串与从串匹配不成功时的做法,具体的再好好想想吧,哈哈哈。

        同时,也开业将next数组的起始位置记为-1,前缀表统一减1,代码如下:

void getNext(int* next, const string& s){
    int j = -1;
    next[0] = j;
    for(int i = 1; i < s.size(); i++) { // 注意i从1开始
        while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
            j = next[j]; // 向前回退
        }
        if (s[i] == s[j + 1]) { // 找到相同的前后缀
            j++;
        }
        next[i] = j; // 将j(前缀的长度)赋给next[i]
    }
}

         我个人比较喜欢第一种什么也不变的方法,用这个next数组来进行KMP字符串匹配吧!

        首先如果模式串大小等于0,直接返回0就好了,定义一个next数组的长度为模式串的长度,利用上面写的getNext函数,得到一个next数组,之后开始遍历文本串,用i来遍历模式串用j来遍历。如果 j 大于 0 并且匹配不上的话,那么 j 就调到next数组里j-1的位置,如果能匹配到,j++继续匹配,最后如果j到达了模式串的end()处,就返回第一个匹配项的下标,即i - needle。size()+1,代码如下:时间复杂度为m+n。

        

 

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[i] != s[j]) {
                j = next[j - 1];
            }
            if (s[i] == s[j]) {
                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 = 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 - needle.size() + 1);
            }
        }
        return -1;
    }
};

 KMP,完结!累死了······

459 重复的子字符串

 

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

示例 1:

输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。

示例 2:

输入: s = "aba"
输出: false

示例 3:

输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)

         这道题首先要想到暴力解法,for循环获取子串的终止位置,然后嵌套for循环子串是否能重复构成字符串,时间复杂度为n²,这里不做详解。

移动匹配:

        如果字符串能够重复组成,那如果把两个s拼在一起,掐头去尾,如果还能够在其中找到s,就说明s可以由重复的子串组成了,代码如下:

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

        力扣里有大佬发明了一行解法,直接从下标为1开始find,也不用去尾,如果找到了的位置是s.size()那就说明完全从第二个s中找到的,反之就说明true,很厉害,代码如下;

class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        return (s + s).find(s, 1) != s.size();
    }
};

KMP法

        经过反复的实验,可以得出结论: next数组长度减去最长相同前缀的长度相当于是第一个周期的长度,如果这个周期的长度可以被s的长度整除,就说明这个是由重复的子字符串组成的。具体推导过程不做过多解释了,有点复杂,总之,当一个字符串由重复子串组成,最长相等前后缀不包含的子串就是最小重复子串。

 Day12 右旋转字符串 28KMP算法 459重复的子字符串 字符串总结_第1张图片

 

class Solution {
public:
     void GetNext(int*next, const string&s)
    {
        int j = 0;
        next[0] =0;
        for(int i = 1;i0&&s[i]!=s[j])
                j=next[j-1];
            if(s[i]==s[j])
                j++;
            next[i]=j;
        }
    }

    bool repeatedSubstringPattern(string s) {
        int len = s.size();
        int next[len];
        GetNext(next, s);
        if(next[len-1] != 0 && len % (len - next[len-1]) == 0)
            return true;
        return false;
    }
};

字符串部分总结:

        个人觉得字符串这里就开始考察真正的算法了,比较难以理解,但是要记住,双指针法是字符串里的常客,万事先往这上面想,同时KMP算法十分重要,时字符串查找中的强力算法,要继续加深理解,虽然现在也并不是完全理解······

 

你可能感兴趣的:(算法,c++,数据结构)