1 算法概述:实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
Notes:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。
解题思路:首先根据根据题意知道本题是字符串匹配问题
接下来判断一下边界条件:
- if(haystack == null && needle == null) 此时我们返回0;
- if(haystack == null && needle != null)此时我们返回-1;
也即if(haystack.length() < needle.length())此时我们返回-1;
- if(haystack != null && needle == null)此时我们返回0;
综上所述,我们可以利用if–else的关系,综合边界条件整合成两个条件
1)if(needle == null)此时我们返回0;
2)else if(haystack == null)此时我们返回-1;
除此之外的字符串匹配都可以用我们的KMP算法来解决。下面正式开始。
class Solution {
public int strStr(String haystack, String needle) {
return getIndexOf(haystack, needle);
}
public int getIndexOf(String s, String m) {
if(m.isEmpty()){
return 0;
}
else if(s.isEmpty()){
return -1;
}
char[] str1 = s.toCharArray();
char[] str2 = m.toCharArray();
int i1 = 0;
int i2 = 0;
int[] next = getNextArray(str2);
while (i1 < str1.length && i2 < str2.length) {
if (str1[i1] == str2[i2]) {
i1++;
i2++;
} else if (next[i2] == -1) {
i1++;
} else {
i2 = next[i2];
}
}
return i2 == str2.length ? i1 - i2 : -1;
}
public int[] getNextArray(char[] ms) {
if (ms.length == 1) {
return new int[] { -1 };
}
int[] next = new int[ms.length];
next[0] = -1;
next[1] = 0;
int i = 2;
int cn = 0;
while (i < next.length) {
if (ms[i - 1] == ms[cn]) {
next[i++] = ++cn;
} else if (cn > 0) {
cn = next[cn];
} else {
next[i++] = 0;
}
}
return next;
}
}
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。
KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。
具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。
注意:暴力解法时间复杂度为O(m * n)m为被匹配字符串长度,n为匹配字符串长度。
public static int getIndexOf(String s, String m) {
if (s == null || m == null || m.length() < 1 || s.length() < m.length()) {//判断边界条件
return -1;
}
在本题中我们将边界条件修改成如下代码:
if(m.isEmpty()){
return 0;
}
else if(s.isEmpty()){
return -1;
}
以下代码统一:
char[] str1 = s.toCharArray();//将字符串s转化成str1字符串数组
char[] str2 = m.toCharArray();//将字符串m转化成str2字符串数组
int i1 = 0;//定义指针i1
int i2 = 0;//定义指针i2
int[] next = getNextArray(str2);获取str2字符串数组的next数组
next数组用来表示在当前字母之前的字符串的最长前缀和最长后缀相等的前提下,长度为多少,不能取整体
举例说明:
str2:[ a a b a a b s]
next:[-1,0,1,0,1,2,3]
以第一个a为起点,前面没有字符串,人工设置,长度为-1
以第二个a为起点,只有一个字母a,没有最长前缀和最长后缀,长度为0
以第一个b为起点,最长前缀为a,最长后缀为a,长度为1
以第三个a为起点,无最长前缀和最长后缀,长度为0
以第四个a为起点,最长前缀为a,最长后缀为a,长度为1
以第二个b为起点,最长前缀为aa,最长后缀为aa,长度为2
以第一个s为起点,最长前缀为aab,最长后缀为aab,长度为3
while (i1 < str1.length && i2 < str2.length) {
举例说明:
str1:[ a a b a a b x a a b a a b s]。指针i1
0 1 2 3 4 5 6 7 8 9 10 11 12 13
str2:[ a a b a a b s] 指针i2。 str2对应的next数组为:next:[-1,0,1,0,1,2,3]
0 1 2 3 4 5 6
str1 和 str2 开始匹配:
在6位置字符匹配不成功,查找next数组,发现最长前缀和最长后缀为3,
此时i1 == 6, i2 == 6
i1不变,i2跳到3位置,进行匹配,
i1 == 6, i2 == 3
str1:[ a a b a a b x a a b a a b s]。指针i1
0 1 2 3 4 5 6 7 8 9 10 11 12 13
i1
str2: [ a a b a a b s] 指针i2。
0 1 2 3 4 5 6
i2
匹配不成功,查找next数组,发现最长前缀和最长后缀为0
则i2跳到0位置,进行匹配,
此时i1 == 6, i2 == 0
str1:[ a a b a a b x a a b a a b s]。指针i1
0 1 2 3 4 5 6 7 8 9 10 11 12 13
i1
str2: [ a a b a a b s] 指针i2。
0 1 2 3 4 5 6
i2
匹配不成功,查找next数组发现无最长前缀和最长后缀,i1++
str1:[ a a b a a b x a a b a a b s]。指针i1
0 1 2 3 4 5 6 7 8 9 10 11 12 13
str2: [ a a b a a b s] 指针i2。
0 1 2 3 4 5 6
此时,i1 == 7, i2 == 0
往下匹配,匹配成功。
匹配成功时,i1 == 14, i2 == 7
return i2 == str2.length == 7 返回 i1 - i2 == 7
则字符串str2在str1中以7位置开始匹配成功
if (str1[i1] == str2[i2]) {//如果两个字符相等,则两个下标++,去比对下一位
i1++;
i2++;
} else if (next[i2] == -1) {//else if (next[i2] == -1) 相当于i2 == 0
i1++;
} else {//i2跳到以当前字母为起点,最长前缀的下一位
i2 = next[i2];
}
}
return i2 == str2.length ? i1 - i2 : -1;//当i1越界或者i2越界,说明已经无法匹配。如果i2 != str2.length 说明匹配不成功,返回-1
}
public static int[] getNextArray(char[] ms) {//该函数用来获取字符串的next数组
if (ms.length == 1) {//如果ms数组长度为一,则没有最长前缀和最长后缀,人工设置为-1.
return new int[] { -1 };
}
int[] next = new int[ms.length];//定义next数组大小
next[0] = -1;//人工设置边界条件
next[1] = 0;//人工设置边界条件
int i = 2;//设置当前下标从2出发
int cn = 0;
起始str2数组和对应的next数组
str2:[ a a b a a b s]
0 1 2 3 4 5 6
next:[-1 0 0 0 0 0 0]长度为7
while (i < next.length) {
if (ms[i - 1] == ms[cn]) {
next[i++] = ++cn;
} else if (cn > 0) {
cn = next[cn];
} else {
next[i++] = 0;
}
}
循环演示
i == 2 < next.length
进入ms[2 - 1] == ms[0] a == a,所以进入第一个分支++cn ,next[2] = 1, i++;
i == 3 < next.length
进入ms[3 - 1] != ms[1] ,cn > 0,所以进入第二个分支cn = next[1] = 0;
i == 4 < next.length
进入ms[4 - 1] == ms[0] ,所以进入第一个分支,++cn ,next[4] = 1, i++,cn == 1;
i == 5 < next.length
进入ms[5 - 1] == ms[1] ,所以进入第一个分支,++cn ,next[5] = 2, i++,cn == 2;
i == 6 < next.length
进入ms[6 - 1] == ms[2] ,所以进入第一个分支,++cn ,next[6] = 3, i++,cn == 3;
i == 7 == next.length
跳出循环。
str2对应的next数组计算完毕。
next:[-1,0,1,0,1,2,3]
0 1 2 3 4 5 6
return next;
}