一、题目
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例1
输入:haystack = “cad”, needle = “cc”
输出:-1
示例2
输入:haystack = null, needle = “mn”
输出-1
示例3
输入:haystack = “mhdkn”, needle = null
输出:0
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/implement-strstr
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
二、代码实现
1)双指针滑窗法
如图:开始时初始化滑窗,然后滑窗逐渐右移直到数组末端
class Solution {
public int strStr(String haystack, String needle) {
if(haystack==null||(needle!=null&&haystack.length()<needle.length()))return -1;
if(needle==null)return 0;
int i=0;//滑窗左指针
int j=needle.length()-1;//滑窗右指针
for(;j<haystack.length();j++) {
if(haystack.substring(i, j+1).equals(needle))
return i;
i++;
}
return -1;
}
}
时间复杂度是O((N-L)L)=O(NL)
,其中N是haystack
的长度,L是needle
的长度。
if中的条件可以简化
if(haystack==null||(needle!=null&&haystack.length()<needle.length()))return -1;
改成
if(haystack==null)return -1;
也是可以的,简单的去判断比较难判断出这两种的优劣。
2)next预处理数组实现的KMP
haystack
数组不走回头路,时间复杂度O(N+L)
问?
时间复杂度为什么会是O(N+L),难道不应该是O(N)吗
核心代码是求next数组的那五行代码
class Solution {
public int strStr(String haystack, String needle) {
if (haystack == null)
return -1;
if (needle == null||needle.length()==0)
return 0;
int[] next = new int[needle.length() + 1];
getNext(needle.toCharArray(), needle.length(), next);
int j = 1;// j为needle中的指针,i为haystack中的指针
boolean mark=false;
for (int i = 0; i <haystack.length(); i++) {
if(mark) {i--;mark=false;}
if (haystack.charAt(i) == needle.charAt(j - 1))
j++;
else {
j = next[j] != 0 ? next[j] : 1;
if(j!=1||needle.charAt(0)==haystack.charAt(i))mark=true;
}
if (j == needle.length() + 1)
return i - needle.length() + 1;
}
return -1;
}
private static void getNext(char[] needle, int length, int[] next) {
// TODO Auto-generated method stub
next[1] = 0;
int i = 1, j = 0;
while (i < needle.length) {
if (j == 0 || needle[i - 1] == needle[j - 1]) {
next[++i] = ++j;
} else
j = next[j];
}
}
}
刚学KMP,代码是第一次缝缝补补改出来的代码,
strStr(String haystack, String needle)
中还是比较冗余,有空的时会会更新优化代码。
注:像KMP这样常用的方法,其实可以封装在一个类中,这样下次使用就可以直接用这个接口了,以此来减少重复的代码
3)米利状态机实现的KMP
相比于2,在本质上仍然是通过预处理数组让字符串匹配具有了"记忆"功能,不需要傻瓜式的重复去匹配,时间复杂度和空间复杂度和next指针处理一样。个人觉得米利状态机实现的KMP是更容易理解的,有空的时候会把详细文字描述补上来。
class Solution {
public int strStr(String haystack, String needle) {
if (haystack == null)
return -1;
if (needle == null||needle.length()==0)
return 0;
int M = haystack.length();
int N = needle.length();
int[][] dp = new int[N][256];
initialDq(dp, needle);
int j = 0;
for (int i = 0; i < M; i++) {
j = dp[j][haystack.charAt(i)];
if (j == N)
return i - N + 1;
}
return -1;
}
private static void initialDq(int[][] dp, String needle) {
// TODO Auto-generated method stub
int X = 0;// 镜像位置
dp[0][needle.charAt(0)] = 1;
for (int j = 1; j < needle.length(); j++) {
for (int c = 0; c < 256; c++) {
dp[j][c] = dp[X][c];// 状态重启
dp[j][needle.charAt(j)] = j + 1;// 可以直接进一步的状态不用重启
}
X = dp[X][needle.charAt(j)];
}
}
}