目录
实现strStr()
描述
实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
说明
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。
这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。
示例 1
输入:haystack = "hello", needle = "ll" 输出:2
示例 2
输入:haystack = "aaaaa", needle = "bba" 输出:-1
示例 3
输入:haystack = "", needle = "" 输出:0
提示
- haystack 和 needle 仅由小写英文字符组成
方法:暴力搜索
暴力搜索的方法大家应该都熟:拿匹配串在原串上一个个比较,不匹配则右移一位继续比较。
class Solution {
public int strStr(String haystack, String needle) {
int len_haystack = haystack.length(), len_needle = needle.length();
char[] haystackArr = haystack.toCharArray(), needleArr = needle.toCharArray();
for (int i = 0; i <= len_haystack - len_needle; i++) {// 从原串的「发起点」和匹配串的「首位」开始,尝试匹配
int a = i, b = 0;
while (b < len_needle && haystackArr[a] == needleArr[b]) {//对原串和匹配串进行匹配,直到结束或者某位不同
a++;
b++;
}
if (b == len_needle) return i;// 如果能够完全匹配,返回原串的「发起点」下标
}
return -1;
}
}
方法二:KMP算法
KMP是D.E.Knuth、J,H,Morris和V.R.Pratt三位共同提出的,即Knuth-Morris-Pratt算法(简称KMP算法),是用于解决字符串匹配问题的一种方法。
我们举个例子:如果我们要在原串"aabaabaac"中查找匹配串"aabaac"的下标,当我们完成第一轮匹配发现最后一个字符不同("aabaabaac"和"aabaac"),暴力法则会将原串右移一位,比较"aabaabaac"和"aabaac",发现第二个字符就不同了,然后继续右移直到结束。
而KMP算法精髓在于根据已有的信息快速跳过一些不可能的解,加速我们搜索的过程。提起KMP算法,一个很重要的概念是前缀表。
前缀表
前缀的定义是包含首字符,但不包含尾字符的所有字符串,比如匹配串aabaac的所有前缀有:
- a
- aa
- aab
- aaba
- aabaa
这里我们还要提一下后缀的概念,后缀即包含尾字符,但不包含首字符的所有字符串,aabaac的所有后缀有:
- c
- ac
- aac
- baac
- abaac
有了这些概念后,我们接着来看看前缀表存储的是什么,前缀表存储的是最长相等前后缀长度。
最长相等前后缀长度就是字符串的前缀和后缀相等并且取最大值。我们举个例子,还是字符串aabaac,我们从第一个字符开始求最长相等前后缀长度
- a:该字符没有前后缀,所以长度为0
- aa:该字符前后缀均为a,长度为1
- aab:该字符前后缀没有相等的,所以长度为0
- aaba:该字符相等前后缀为a,长度为1
- aabaa:该字符相等前后缀有a、aa,最长长度为2
- aabaac:该字符前后缀没有相等的,所以长度为0
所以我们得到这样一个前缀表。
KMP算法
有了前缀表后,我们先进行第一轮匹配,在字符c处发现不匹配,此时我们查看前缀表,找到下次匹配的下标。
我们查看字符c前面的字符串aabaa,该子串的最长相等前后缀为2,所以我们将匹配串aabaac中下标为2的字符"b"和当前原串中的字符"b"进行匹配,发现可以匹配就结束。
如果此时仍然不匹配,那么就将当前字符和匹配串的第一个字符比较,重新开始匹配。
class Solution {
public int strStr(String haystack, String needle) {
if ("".equals(needle)) return 0;//空字符串直接返回0
int n = haystack.length(), m = needle.length();// 分别读取原串和匹配串的长度
haystack = " " + haystack;// 原串和匹配串前面都加空格,使其下标从 1 开始
needle = " " + needle;
char[] s = haystack.toCharArray();//将字符串转换为字符数组
char[] p = needle.toCharArray();
int[] next = new int[m + 1];// 构建 next 数组,数组长度为匹配串的长度(next 数组是和匹配串相关的)
//构造前缀表
for (int i = 2, j = 0; i <= m; i++) {// 构造过程 i = 2,j = 0 开始,i 小于等于匹配串长度 【构造 i 从 2 开始】
while (j > 0 && p[i] != p[j + 1]) j = next[j];// 匹配不成功的话,j = next(j)
if (p[i] == p[j + 1]) j++;// 匹配成功的话,先让 j++
next[i] = j;// 更新 next[i],结束本次循环,i++
}
//进行匹配
for (int i = 1, j = 0; i <= n; i++) {// 匹配过程,i = 1,j = 0 开始,i 小于等于原串长度 【匹配 i 从 1 开始】
while (j > 0 && s[i] != p[j + 1]) j = next[j];// 匹配不成功 j = next(j)
if (s[i] == p[j + 1]) j++;// 匹配成功的话,先让 j++,结束本次循环后 i++
if (j == m) return i - m;// 整一段匹配成功,直接返回下标
}
return -1;
}
}