代码随想录Day 9 | KMP 字符串+双指针回顾

代码随想录Day 9 | KMP 字符串+双指针回顾

  • KMP算法
  • 找出字符串中第一个匹配项的下标
  • 重复的子字符串
  • KMP
  • 字符串
  • 双指针

KMP算法


next数组代码模板

    void SetNext(vector& next,string s)
    {
        //前缀末尾
        int i = 0;
        //后缀末尾
        int j = 1;
        //初始化netx数组
        next[0] = i;

        for(j;j0&&s[j]!=s[i])
            {
                i=next[i-1];
            }
            if(s[j] == s[i])
            {
                i++;
            }
            next[j] = i;
        }
    }

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

文档讲解:代码随想录
视频讲解:
状态: ×

KMP算法的应用,求匹配字符串的过程也类似求前缀表的过程。前缀表的求解重点就是while循环里j的回退,当s[i]!=s[j]那么就一直会退,直到j为0或者两个相等。而在字符串匹配时,i指向了待匹配的字符串,不再是同一个字符串。j还是本身的字符串。
这样来看,当出现不等的情况,j就要回退,如果没有前缀表,那么只能从头重新开始匹配。有了前缀表,我们可以知道模式串中当前位置j之间的那一个变化点,我们就可以先回退到哪一个变化点进行判断是否相等,如果不相等继续回退,直到j=0或者相等。

class Solution {
public:
//next数组
    void SetNext(vector& next,string s)
    {
        //前缀末尾
        int i = 0;
        //后缀末尾
        int j = 1;
        //初始化netx数组
        next[0] = i;

        for(j;j0&&s[j]!=s[i])
            {
                i=next[i-1];
            }
            if(s[j] == s[i])
            {
                i++;
            }
            next[j] = i;
        }
    }
public:
    int strStr(string haystack, string needle) {
        vector next(needle.size());
        //int next[needle.size()];
        SetNext(next,needle);
        int i = 0;//haystack指针
        int j = 0;//needle next指针

        for(i;i0&&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;
    }
};

重复的子字符串

文档讲解:代码随想录
视频讲解: 字符串这么玩,可有点难度! | LeetCode:459.重复的子字符串
状态:×

利用KMP求解,我们之前一直是利用最长相等前后缀的长度,现在来考虑其中的元素,由于两者相等那说明对应的i和j位置上的元素也应该相等。那么对应到原本字符串的下标k上是什么样的关系呢?
假设最长相等前后缀的长度是m,字符串的长度是n,最长相等前缀为p[i],最长相等后缀为q[j],
p [ 0 ] = q [ 0 ] = > s [ 0 ] = s [ n − m ] p[0] = q[0] =>s[0] = s[n-m] p[0]=q[0]=>s[0]=s[nm]
如果存在一个最小的重复子串,字符串0~n-m-1这段距离所构成的子串是能够重复的,且是最小的,也即是说
n % ( n − m ) = = 0 n\%(n-m)==0 n%(nm)==0

class Solution {
public:
    void SetNext(vector& next, string s)
    {
        //前缀末尾
        int i = 0;
        //后缀 末尾
        int j = 1;
        next[0] = i;
        for(j;j0&&s[j]!=s[i])
            {
                i = next[i-1];
            }
            if(s[j] == s[i])
            {
                i++;
            }
            next[j] = i;
        }
    }
public:
    bool repeatedSubstringPattern(string s) {
        vector next(s.size());
        SetNext(next,s);
        //最长相等前后缀的长度
        int m = next[s.size()-1];
        if(m!=0&&(s.size()%(s.size()-m))==0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
};

KMP

虽然前两道题都应用了KMP算法,但在具体求解中的应用却是不一样的,第一道题是比较两个字符串,利用next数组求取回退位置,如果其不为0说明不匹配位置之前的字符串内有一样的元素,这样可以避免从头开始匹配,而只需要关注前方一样的元素后面那个不一样的元素,与待匹配字符串是否匹配,如果还是不匹配则继续回退
而第二题是一个字符串,得到的next数组表面从当前元素位置i向前拥有的最大相等前后缀长度k,就说明从头开始k个元素和从i开始向前k个元素相对应相等。而字符串最后一个元素对应的k值,表明该字符串的最大相等前后缀。如果有,那么在原始字符串看来从0到n-k-1与n-k到2n-k-1是对应相等的。如果是完全由重复子串构成的字符串,那么肯定就可以被n-k这样一个长度均分。且这个长度是最小的。如果不是最小的,那说明n-k还可以被一个长度p对应的数据均分,说明当前的k仍然不是最大的。

字符串

文档讲解:代码随想录
视频讲解:

字符串,可以看作一个字符数组。对于字符串的操作可以考虑使用数组的方法。
对于数组填充类的问题,可以采用先扩容再从后向前填充的方法进行,避免从前向后填充导致整体数组的重复性移动
在翻转字符串中,同样利用双指针来进行翻转,当固定规律去处理字符串可以考虑上添加一个循环来限制一个指针的移动。
有些时候可以通过局部翻转+整体翻转实现字符串中单词不变,但单词的位置发生了翻转。

双指针

文档讲解:代码随想录
视频讲解:

双指针的基本目的就是想将两个循环的事情放在一个循环里完成。对于链表,数组的题目如果涉及到删除或者替换元素可以考虑双指针的做法。
同时如果替换元素的话,可以先扩容,再从后向前替换。
对于删除元素,主要是利用一个指针来存储不需要删除的元素,另外一个指针用来寻找要删除的元素。两者同时遍历,如果遇到目标删除元素,则保留指针停止移动,寻找指针继续移动,直到寻找指针遇见不是目标删除元素,然后开始覆盖操作。
这是针对数组,针对链表直接更改指向就可以了。
但在字符串中删除空格中,这时的方法我们是通过先只记录不是目标的元素,当下一次寻找指针找到不是空格的元素后,添加上一个目标,然后继续记录直到又遇到目标。

//有目标target = ' '
int left = 0;
int right = 0;
while(right < s.slength())
{
	if(s[right] != ' ')
	{
		//下一次进入先增加空格
		if(left>0) s[left++] = ' ';
		//以一个单词为一组,所以在不是空格的逻辑里面,循环输入一个单词直到遇到空格说明单词结束
		while(right

数组对重复元素的删除相较于删除空格要稍微简单一点,如果保留k个重复的,那么可以先判断当前数组长度是否小于等于k,如果是则直接输出,如果不是,则开始循环

if(nums.size()<=k)
{
	return nums.size();
}
else
{
	int left = k;
	int right = k;
	while(right

双指针还经常用来翻转链表和数组。

双指针还有一种变体,就是快慢指针,即两个指针的移动速度不一样,其经常用来寻找链表是否有环这类的问题。

N数之和的问题,受到第一题的影响很容易想到利用哈希表的方法来求解,但需要注意的是哈希表Map只能存放两个值,set只能存放一个值。回想第一题,它没有重复性的要求。所以记录下下标或者数值就可以了。
当然双指针的办法也可以解决这道题,可以先排序然后利用两指针指向元素的和去判断于目标和的大小关系就如果数组寻找某个元素一样。
只是排序之后原始顺序肯定就不相同了。
但三数之和以及后面的四数之和就不在适用哈希表了,主要是它提出了重复性的要求,而哈希表的存储有一个元素肯定是两数和,另一元素不足以支撑起重复性的判断,但这道题可以发现要求我们返回的是元素而不是下标,所以可以考虑使用双指针来解决,先排序然后向内收缩。

你可能感兴趣的:(算法)