Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
前言
第二题的题目为简单,这道题简单是简单在可以用暴力解法去做,或者你偷懒,直接用 js 自带的 indexOf 都可以做出来,但是难点也是有的,就在于需要去理解新的一个 KMP 算法
每日一题
第二题的题目是 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
提示:
0 <= haystack.length, needle.length <= 5 * 104haystack和needle仅由小写英文字符组成
题解
暴力解题
暴力解法,循环遍历字符串 haystack 并且每次遍历都会再去循环遍历 needle 判断当前位置 needle 是否和 haystack 部分相等,全相等则返回对象的下标,不相等则结束循环继续 haystack 的下一次遍历
-
我们可以先用 i 来循环遍历 haystack。
-
然后在每一次的 i 里面,我们设定一个 flag 来判断字符串是否相等,初始值为true
-
用 j 去循环遍历 needle ,这时候,我们要去判断 needle 的第 j 位,也就是对应了 haystack 的第 j+i 位是否相等
-
一直到 needle 遍历结束都相等的话,那么说明存在,并且返回开始的位置,也就是当前的 i
-
中间如果有一个不相等那么都会结束掉当前 needle 的循环,并且进入下一个 i 的循环。
/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/
var strStr = function(haystack, needle) {
const n = haystack.length, m = needle.length;
for (let i = 0; i + m <= n; i++) {
let flag = true;
for (let j = 0; j < m; j++) {
if (haystack[i + j] != needle[j]) {
flag = false;
break;
}
}
if (flag) {
return i;
}
}
return -1;
};
1. KMP 算法
上面我们通过暴力解法的方式去解这道题,虽然是能够得出最后的答案的,但是很明显的,时间复杂度来到了 O(mn) 其中 m 和 n 就是两个字符串的长度,那么有什么办法去优化这个时间复杂度吗?
答案是有的,这里就要讲一下字符串匹配的经典算法--KMP算法,它所用的时间复杂度只需要 O(m+n)
我们先来看一个字符串匹配:
假如现在我们要在 ‘ababaf’ 中去查找 ‘abaf’ 字符串,那么暴力解法就是如下图,一位一位进行匹配,不匹配的话就向下移动一位继续匹配:
那么在 KMP 算法当中,就会直接跳过中间的一个过程,直接来到下图的这个位置:
因为 KMP 算法会根据 前缀表 来进行跳过不必要的一些流程,那么我们先来介绍一下什么是前缀,什么是后缀
2. 前缀?后缀?
那上面的字符串 ‘ababf’ 来做一个例子,前缀是包含首字母但是不包含尾字母的所有子串,后缀相反,是包含尾字母但是不包含首字母的所有子串。
从上图和它的一个定义,那么我们就知道了,什么是前缀,什么是后缀。
3. 最长相等前后缀
接下来我们要去求一个字符串,它的最长相等前后缀,这个就是会组成它的一个前缀表:
上面的例子可能看起来不是特别的直观,我们来换一个能更加直观的看出来的例子,我们把求前缀表的字符串先换为 ‘aabbaaf’:
这样从这张图,我们就能够很清楚地看出来如何去求一个字符串的前缀表。
4. next数组
上面我们获得的前缀表就称之为 next 数组。接着我们就要来看一下一开始是如何用 next 数组直接完成跳过部门不需要的匹配过程的。
在前缀表中,我们需要去找到它最大的那个数,代表了它的最大相等前后缀,比如在 第二个 a 的这个位置最大值为 1,那么我们就能够知道,在一次不匹配之后,可以直接往后面跳一格,这也就是为什么上图中我们不会按照最开始那种一位一位模拟的方式:
而是直接往后面跳了一位:
5. KMP解题代码
/**
* @param {string} haystack
* @param {string} needle
* @return {number}
*/
var strStr = function (haystack, needle) {
const n = haystack.length,
m = needle.length;
if (m === 0) {
return 0;
}
const next = new Array(m).fill(0);
for (let i = 1, j = 0; i < m; i++) {
while (j > 0 && needle[i] !== needle[j]) {
j = next[j - 1];
}
if (needle[i] == needle[j]) {
j++;
}
next[i] = j;
}
for (let i = 0, j = 0; i < n; i++) {
while (j > 0 && haystack[i] != needle[j]) {
j = next[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
}
if (j === m) {
return i - m + 1;
}
}
return -1;
};