28实现strStr()(KMP算法、字符串匹配)

1、题目描述

实现 strStr() 函数。

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。

2、示例

输入: haystack = "hello", needle = "ll"
输出: 2

输入: haystack = "aaaaa", needle = "bba"
输出: -1

说明:

当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。

对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。

3、题解

KMP算法

第一步:计算模式串的next数组

  • j指向前缀下标,i指向后缀下标,next数组第一个字符next值为-1,next[0]=-1。
  • 循环扫描模式串,给模式串每个字符算出一个next数值,next值的含义是该字符之前的字符串中前缀和后缀相等的最长串的字符个数,也就是前缀串的下一个字符下标。当模式串第j个字符与主串第i个字符不匹配时,i不回溯,j回溯到该字符下的next值即next[j],也就是j字符之前字符串的后缀与主串i字符之前后缀是相同的,又因为j字符之前字符串的前缀与后缀相同,所以j字符之前字符串的前缀与主串i字符之前后缀是相同的(算作已匹配),所以j回溯到相同前缀的下一个字符,也就是相同前缀的个数。
  • 当j回溯到第一个字符的next值时,重新匹配j++=0前缀从头开始i++后缀从下一个字符开始或者当前缀字符等于后缀字符T[j]=T[i]时,继续看下一个字符是否匹配j++ i++,同时给下一个字符i的next值赋值,next等于i字符之前的字符串中前缀和后缀相等的最长串的字符个数,也就是前缀串的下一个字符下标即next[i]=j++
  • 如果当前缀字符与后缀字符不相等,j通过next值回溯到适当位置,也就是回溯到j字符之前的字符串中前缀和后缀相等的最长串的字符个数,也就是前缀串的下一个字符下标处,也就是j字符之前字符串的后缀与i字符之前后缀是相同的,又因为j字符之前字符串的前缀与后缀相同,所以j字符之前字符串的前缀与主串i字符之前后缀是相同的(算作已匹配),所以j回溯到相同前缀的下一个字符,也就是相同前缀的个数

第二步:字符串匹配

  • 如果模式串和主串都为空,也匹配了,返回0
  • 如果主串为空,模式串不为空,匹配不了返回-1
  • 如果匹配成功模式串的下标j就等于len2循环结束,或者主串下标i等于len1结束循环
  • 当模式串下标j回溯到第一个字符的next值时,说明没有匹配成功,重新匹配j++=0模式串从头开始 i++主串从下一个字符开始或者当模式串字符等于主串字符haystack[i]=needle[j]时,继续看下一个字符是否匹配j++ i++
  • 当模式串第j个字符与主串第i个字符不匹配时,i不回溯,j回溯到该字符下的next值即next[j],也就是j字符之前字符串的后缀与主串i字符之前后缀是相同的,又因为j字符之前字符串的前缀与后缀相同,所以j字符之前字符串的前缀与主串i字符之前后缀是相同的(算作已匹配),所以j回溯到相同前缀的下一个字符,也就是相同前缀的个数
#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
	int strStr(string haystack, string needle) {
		int len1 = haystack.size(), len2 = needle.size();
		int i = 0;
		int j = 0;
		vector<int> next;
		//如果模式串和主串都为空,也匹配了,返回0
		if (len1 == 0 && len2 == 0)
			return 0;
		//如果主串为空,模式串不为空,匹配不了返回-1
		if (len1 == 0)
			return -1;
		//计算模式串的next值
		get_next(needle, next);
		//如果匹配成功模式串的下标j就等于len2循环结束,或者主串下标i等于len1结束循环
		while (i < len1 && j < len2)
		{
			//当模式串下标j回溯到第一个字符的next值时,说明没有匹配成功,重新匹配j++=0模式串从头开始 i++主串从下一个字符开始
			//或者当模式串字符等于主串字符haystack[i]=needle[j]时,继续看下一个字符是否匹配j++ i++
			if (j == -1 || haystack[i] == needle[j])
			{
				i++;
				j++;
			}
			//当模式串第j个字符与主串第i个字符不匹配时,i不回溯,j回溯到该字符下的next值即next[j],也就是j字符之前字符串的后缀与主串i字符之前后缀是相同的,又因为j字符之前字符串的前缀与后缀相同,所以j字符之前字符串的前缀与主串i字符之前后缀是相同的(算作已匹配),所以j回溯到相同前缀的下一个字符,也就是相同前缀的个数
			else
				j = next[j];
		}
		//最后如果模式串的下标j就等于len2,表示匹配成功,返回主串开始匹配字符的下标值i-j
		if (j == len2)
			return i - j;
		else
			return -1;
	}
	void get_next(string T, vector<int> &next)
	{
		int i, j;  //j指向前缀下标,i指向后缀下标
		i = 0;  //i初始为0
		j = -1;  //j初始为-1
		next.push_back(-1);  //next数组第一个字符next值为-1,next[0]=-1
		//循环扫描模式串,给模式串每个字符算出一个next数值,next值的含义是该字符之前的字符串中前缀和后缀相等的最长串的字符个数,也就是前缀串的下一个字符下标。
		//当模式串第j个字符与主串第i个字符不匹配时,i不回溯,j回溯到该字符下的next值即next[j],也就是j字符之前字符串的后缀与主串i字符之前后缀是相同的,又因为j字符之前字符串的前缀与后缀相同,所以j字符之前字符串的前缀与主串i字符之前后缀是相同的(算作已匹配),所以j回溯到相同前缀的下一个字符,也就是相同前缀的个数
		while (i < T.size())
		{
			//当j回溯到第一个字符的next值时,重新匹配j++=0前缀从头开始i++后缀从下一个字符开始
			//或者当前缀字符等于后缀字符T[j]=T[i]时,继续看下一个字符是否匹配j++ i++,同时给下一个字符i的next值赋值,next等于i字符之前的字符串中前缀和后缀相等的最长串的字符个数,也就是前缀串的下一个字符下标即next[i]=j++
			if (j == -1 || T[j] == T[i])
			{
				j++;
				i++;
				next.push_back(j);
			}
			//如果当前缀字符与后缀字符不相等,j通过next值回溯到适当位置,也就是回溯到j字符之前的字符串中前缀和后缀相等的最长串的字符个数
			//也就是前缀串的下一个字符下标处,也就是j字符之前字符串的后缀与i字符之前后缀是相同的,又因为j字符之前字符串的前缀与后缀相同,所以j字符之前字符串的前缀与主串i字符之前后缀是相同的(算作已匹配),所以j回溯到相同前缀的下一个字符,也就是相同前缀的个数
			else
				j = next[j];
		}
	}
};
int main()
{
	string haystack = "hello";
	string needle = "lo";
	Solution solute;
	cout << solute.strStr(haystack, needle) << endl;
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值