在刷力扣经常会尝试去优化代码,在做力扣28.实现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
1.暴力求解
这个方法是比较简单的,只要做两层循环就可以解决问题。
时间复杂度较高,不推荐
class Solution {
public int strStr(String haystack, String needle) {
int n = haystack.length();
int m = needle.length();
//使用i指针进行作为比较的字符串的头个字符位置,作为每次比较的起点.
for (int i = 0; i < n - m + 1; i++) {
boolean flag = true;
//滑动窗口的做法,将模式串的长度做为窗口的长度进行遍历
for (int j = 0; j < m; j++) {
if (haystack.charAt(i + j) != needle.charAt(j)) {
flag = false;
break;
}
}
if (flag) {
return i;
}
}
return -1;
}
}
2.KMP算法
1.算法原理
这个算法是费了挺长时间才理解的
KMP 算法是一个快速查找匹配串的算法,它的作用其实就是本题问题:如何快速在「原字符串」中找到「匹配字符串」。
KMP 之所以能够在 O(m + n) 复杂度内完成查找,是因为其能在「非完全匹配」的过程中提取到有效信息进行复用,以减少「重复匹配」的消耗,也就是在传统的暴力解法上,利用算法对匹配串在原字符串中的起始位置进行筛除,进行遍历的方法
你可能不太理解,没关系,我们可以通过举例来理解 KMP。
在进行字符匹配的时候,时常出现的状况是前几个字符能够进行匹配,到了后面的时候,字符不匹配了,字符又得回到之前匹配的起始位置的下一个字符进行匹配,这样就增加时间损耗,而KMP就很好的解决了这个问题
在匹配不到对应的字符串的时候,KMP就会查找原字符串中相等的前后缀,例如图中匹配串相等的前缀后缀为ab,当指针走到匹配串的f字符,原字符串的c字符时,匹配串的指针就会跳到前缀后的第一个字符也就是c,跟原字符串当前位置的字符c重新进行匹配,匹配成功时,两个指针都进行++操作,通过这个过程实际上就去除了暴力求法下,当没有到匹配字符时,指针需要回滚到原字符串的第二个位置进行重新遍历,匹配串的指针也要回到第一个位置这其实是没必要的时间开销
2.实现思路
在这个地方我们使用一个前缀表的操作,所谓字符串的前缀表,就是利用不包括最后一个字符的匹配串任意子串,利用这些组成next数组,next 数组存放的是当前长度下的 最长相同前后缀 的长度。
next 数组存放的是当前长度下的 最长相同前后缀 的长度
以 abcabf举例
a时,最长前后缀长度是 0
因为是缀。总长度就只有 1的话单独一个字母不算做缀
字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
ab时,很显然长度也是 0
abc时,很显然长度也是 0
abca时, 最长相同前后缀长度就是 1了,是 a
abcab时, 最长相同前后缀长度就是 2了,是 ab
abcabf时,没有 最长相同前后缀了,长度是0
那么如何使用next数组去确定回滚的位置呢,当匹配串指针到达字符f的位置发现与原字符串中的c字符不匹配,就去寻找前一个字符串对应的前缀表元素也就是图中的2,通过该元素可以让我们知道abcab的前缀长度为2,当匹配串指针回滚到索引2位置时,匹配到的子串为abc,原字符串的匹配到的子串也为abc。
接下来放上代码实现
3.代码
class Solution {
public int strStr(String haystack, String needle) {
if (needle.length() == 0) return 0;
//建立前缀表
int[] next = new int[needle.length()];
getNext(next,needle);
//j指针用做匹配串指针,i指针用作原字符串的指针
int j = 0 ;
for (int i = 0; i < haystack.length(); i++) {
//匹配不中
while (j > 0 && needle.charAt(j) != haystack.charAt(i)) {
//进行回滚操作,利用前缀表,到达相等前缀的下一个字符,然后继续匹配
j = next[j - 1];
}
//当匹配中,两个指针均移向下一位
if (needle.charAt(j) == haystack.charAt(i)) {
j++;
}
if (j == needle.length()) return i - needle.length() + 1;
}
return -1;
}
public void getNext(int[] next, String s) {
next[0] = 0;
int j = 0;
for (int i = 1; i < s.length(); i++) {
while (j > 0 && s.charAt(j) != s.charAt(i)) {
j = next[j - 1];
}
if (s.charAt(j) == s.charAt(i)) {
j++;
}
next[i] = j;
}
}
}
```java
### 4.参考资料
https://leetcode-cn.com/problems/implement-strstr/solution/duo-tu-yu-jing-xiang-jie-kmp-suan-fa-by-w3c9c/
http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html