LeetCode28-实现strStr()

LeetCode28-实现strStr()

实现 strStr() 函数。

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

示例 1:

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

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

说明:

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

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

一、思路

(一)字符串匹配

每次匹配从第一个字符开始,若匹配上了,则继续匹配直到某一次匹配不成功或者全部都能匹配上。

C++代码:

class Solution {
public:
	int strStr(string haystack, string needle) {
		if (needle.empty())
			return 0;

		if (needle.size() > haystack.size())
			return -1;

		for (int i = 0; i < haystack.size(); i++) {
			if (haystack[i] == needle[0]) {
				int j = 1;
				while (j < needle.size() && (i + j) < haystack.size() && haystack[i + j] == needle[j])
					j++;
				if (j == needle.size())
					return i;
			}
		}
		return -1;
	}
};

执行效率:
在这里插入图片描述

(二)KMP算法

详细的讲解可以去学堂在线看看清华的数据结构课程,这里只是简要说明一下:
假设待匹配的字符串为T,模式串为P,需要在字符串T中找出P子串,设P的长度为n,T的长度为m

在某次匹配中,P[i]与T[j]不匹配,这就意味着:
之前的匹配都是成立的,即:子串P[0,i)=T[j-i,j)
考虑那个接替字符P[i]与T[j]进行匹配的字符P[k]需要什么条件:
如果将P[k]放置在P[i]的位置上,那就意味着k之前的位置都是匹配的,即:
(1)P[0,k)=T[j-k,j)
又因为:P[0,i)=T[j-i,j)并且k<i,所以有:T[j-k,j)=P[i-k,i),即:
(2)P[0,k)=P[i-k,i)
此时,移动的距离为:i-k
为了不漏掉某个子串,移动距离应该尽可能的小,因此k要尽量大些
看到这里你会发现:
每次接替不匹配字符P[i]的P[k],这里的k和字符串T是没有任何关系的,它只与模式串P有关
因此,我们可以事先构建好一张表next,这样每当发生不匹配的时候,我们都可以直接找到下一个最合适的接替字符

如果在一开始的时候,就发生了不匹配,即:P[0]!=T[j],这时已经没有可以接替P[0]的字符,next表中该填什么?
这时,我们可以假设P[0]前面还有一个通配符,它与任意字符都是匹配的,即:P[-1]=*

如何生成next表?我们可以先看看next[i]与next[i+1]的关系:
从next表的含义出发:
next[i]是接替字符P[i]的字符在P中的位置,即:用P[next[i]]来代替P[i]
那么对于next[i]来说,必然有:
P[0,next[i])=P[i-next[i],i)

如果:P[next[i]]=P[i],那么对于P[i]来说,就是一次无用的匹配,但是从next[i+1]的角度来看:
P[0,next[i])=P[i-next[i],i)可以更进一步:P[0,next[i]+1)=P[i-next[i],i+1)
对比next[i+1]的意义:P[0,next[i+1])=P[i+1-next[i+1],i+1)
这里替换一下,令:next[i+1]=next[i]+1
P[0,next[i]+1)=P[i-next[i],i+1)

之前说过,next[i]应该要尽可能的大,因为这个next[i]说的是从0~(i-1)找出能接替P[i]的字符位置,所以next[i]<i
根据之前的分析:next[i+1]的最大长度也只能是next[i]+1,因此next[i+1]=next[i]+1<i+1

如果P[next[i]]!=P[i],这是不匹配的状况
分析一下,P[next[i]]!=P[i]这个不等式隐含的条件是:
P[0,next[i])=P[i-next[i],i)
也就是说前半部分相等,这个时候应该考虑找个字符串来接替P[next[i]]了,根据next的意义,我们知道:
设k=next[i],接替P[k]的是P[next[k]],然后再使用P[next[k]]]与P[i]进行比较,如此反复,直到相等

生成next表的C++代码:

vector<int> KMP(string s) {
		vector<int> next;
		next.push_back(-1);
		int k = -1;
		int j = 1;
		while (j < s.size()) {
			if (k == -1 || s[j - 1] == s[k]) {
				next.push_back(++k);
				j++;
			}
			else
				k = next[k];
		}
		return next;
	}

C++代码:

class Solution {
public:
	int strStr(string haystack, string needle) {
		if (needle.empty())
			return 0;

		if (needle.size() > haystack.size())
			return -1;

		vector<int> next = KMP(needle);
		int i = 0, j = 0;
		
		while (i < haystack.size() && abs(j) < needle.size()) {
			if (j == -1 || haystack[i] == needle[j]) {
				i++;
				j++;
				if (j == needle.size())
					return (i - j);
			}
			else {
				j = next[j];
			}
		}
		return -1;
	}

	vector<int> KMP(string s) {
		vector<int> next;
		next.push_back(-1);
		int k = -1;
		int j = 1;
		while (j < s.size()) {
			if (k == -1 || s[j - 1] == s[k]) {
				next.push_back(++k);
				j++;
			}
			else
				k = next[k];
		}
		return next;
	}
};

执行效率:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值