28.实现strStr
1.题目描述
给你两个字符串 haystack 和 needle ,请你在 haystack
字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr()
以及 Java 的 indexOf() 定义相符。
输入:haystack = "hello", needle = "ll"
输出:2
输入:haystack = "aaaaa", needle = "bba"
输出:-1
输入:haystack = "", needle = ""
输出:0
2.解题思路及具体代码
- 方法一:简单截取
思路:
1,首先从开始位置遍历主串元素,直到找到第一个与子串第一字符相同的元素。
2,这个时候,截取主串相同子串长度的字符串与子串进行比较
3,注意,在截取时可能出现主串剩余长度小于子串长度,所以要加个限定条件。
具体代码如下:
public int strStr(String haystack, String needle) {
if (needle.length()==0){
return 0;
}
int h=haystack.length();
int n=needle.length();
int result=-1;
int i,j;
i=j=0;
while (i<h && j<n){
if (haystack.charAt(i)==needle.charAt(j) && n<=(h-i)){
if (haystack.substring(i,i+n).equals(needle)){
result=i;
break;
}
}
i++;
}
return result;
}
时间复杂度:substring的时间复杂度是O(n),那么substring这段代码可能会执行多次,这取决于两个值字符串的特点。因此大概猜测时间复杂度为O(M*N),M是主串的长度,N是子串 的长度。(这里可能有点问题,希望大家帮忙指正)
空间复杂度:O(l)
- 方法二:KMP算法
解题思路:
这题妥妥的kmp算法解题,因为kmp算法就是在主串中寻找子串。
简单介绍kmp算法-得花点时间去理解,我这里只是快速介绍下kmp算法,有助于大家理解代码,尤其是next数组的生成。
kmp算法的作用是什么?
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。(就是减少重复匹配的次数,从而降低时间复杂度)
kmp算法的关键是求next数组,那么next数组是用来干嘛的呢?
简单来说就是记录当前子串(从起始位置到当前数组下标位置)的最大相等前后缀。
前缀:去掉最后一个字符的剩余子串,如“aab”,前缀为“aa”。
后缀:去掉开始第一个字符的剩余子串,如"aab",后缀为"ab"。
什么是最大相等前后缀呢?
如“aab”,找到前缀中“aa”与后缀中“ab”的最大相同子串,如下图所示:
再如下图:
这样,只要给出一个字符串(一般我们叫模式串或子串),就可以求出它所对应的next数组的值。如下图计算next数组:
那么kmp具体是减少匹配的次数的呢?(相较于朴素算法),如下图简单介绍下:
上面只是简单介绍,如果是初学KMP算法的,建议找个视频,反复观看,搞懂里面的细节,这里不在描述。
下面是获取next数组的代码段(这里next数组内的前后缀数均减一):
public int[] getNext(char[] p1){
int[] next=new int[p];
int j=-1;
next[0]=j;
//获得next数组
for (int i=1;i<needle.length();i++){
while (j>=0 && p1[i]!=p1[j+1]){
j=next[j];
}
if (p1[i]==p1[j+1]){
j++;
}
next[i]=j;
}
}
另一方法是设置“哨兵”,即将数组第一位设置为空,在子串与主串匹配时,更容易理解,如下代码:
public int[] getNext(char[] p){
// 构建 next 数组,数组长度为匹配串的长度(next 数组是和匹配串相关的)
int m=p.length;
int[] next = new int[m + 1];
// 构造过程 i = 2,j = 0 开始,i 小于等于匹配串长度 【构造 i 从 2 开始】
for (int i = 2, j = 0; i <= m; i++) {
// 匹配不成功的话,j = next(j)
while (j > 0 && p[i] != p[j + 1]) j = next[j];
// 匹配成功的话,先让 j++
if (p[i] == p[j + 1]) j++;
// 更新 next[i],结束本次循环,i++
next[i] = j;
}
}
整体实现代码如下:
// KMP 算法
// ss: 原串(string) pp: 匹配串(pattern)
public int strStr(String ss, String pp) {
if (pp.isEmpty()) return 0;
// 分别读取原串和匹配串的长度
int n = ss.length(), m = pp.length();
// 原串和匹配串前面都加空格,使其下标从 1 开始
ss = " " + ss;
pp = " " + pp;
char[] s = ss.toCharArray();
char[] p = pp.toCharArray();
// 构建 next 数组,数组长度为匹配串的长度(next 数组是和匹配串相关的)
int[] next = new int[m + 1];
// 构造过程 i = 2,j = 0 开始,i 小于等于匹配串长度 【构造 i 从 2 开始】
for (int i = 2, j = 0; i <= m; i++) {
// 匹配不成功的话,j = next(j)
while (j > 0 && p[i] != p[j + 1]) j = next[j];
// 匹配成功的话,先让 j++
if (p[i] == p[j + 1]) j++;
// 更新 next[i],结束本次循环,i++
next[i] = j;
}
// 匹配过程,i = 1,j = 0 开始,i 小于等于原串长度 【匹配 i 从 1 开始】
for (int i = 1, j = 0; i <= n; i++) {
// 匹配不成功 j = next(j)
while (j > 0 && s[i] != p[j + 1]) j = next[j];
// 匹配成功的话,先让 j++,结束本次循环后 i++
if (s[i] == p[j + 1]) j++;
// 整一段匹配成功,直接返回下标
if (j == m) return i - m;
}
return -1;
}
时间复杂度:O(n×m),其中 n 是字符串 haystac的长度,m 是字符串 needle的长度。最坏情况下我们需要将字符串 needle与字符串 haystack的所有长度为 m 的子串均匹配一次。
空间复杂度:O(1)。我们只需要常数的空间保存若干变量。