diff --git a/top-interview-leetcode150/numbers-and-strings/28找出字符串中第一个匹配的下标.js b/top-interview-leetcode150/numbers-and-strings/28找出字符串中第一个匹配的下标.js new file mode 100644 index 0000000..4899866 --- /dev/null +++ b/top-interview-leetcode150/numbers-and-strings/28找出字符串中第一个匹配的下标.js @@ -0,0 +1,133 @@ +/** + *https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/?envType=study-plan-v2&envId=top-interview-150 + * @param {string} haystack + * @param {string} needle + * @return {number} + */ +const strStr = function (haystack, needle) { + // return haystack.indexOf(needle); // 直接使用数组api +}; + +/* +暴力解法,暴力解法思路非常简单,needle和haystack所有和needle长度一致的子串,如果匹配到相同的就返回,如果没有匹配到就返回-1 +*/ +function f1(haystack, needle) { + // 如果要查找的子串比字符串还长,直接返回 + if (haystack.length < needle.length) return -1; + const lastIndex = haystack.length - needle.length; // haystack中最后一个子串的起始下标 + for (let i = 0; i <= lastIndex; i++) { + let match = true; + for (let j = 0; j < needle.length; j++) { + if (haystack[i + j] !== needle[j]) { + // 当前位置的子串不符合要求,直接结束循环 + match = false; + break; + } + } + if (match) { + return i; + } + } + return -1; // 如果不匹配直接返回-1 +} + +/* +使用kmp来快速找出匹配的子串 +*/ +const kmpSearch = function (haystack, needle) { + const n = haystack.length; + const m = needle.length; + + // 如果模式字符串为空,直接返回0 + if (m === 0) { + return 0; + } + + // 构建部分匹配表 (pi数组),用于记录模式串的匹配信息 + const pi = new Array(m).fill(0); + + // 计算pi数组 + for (let i = 1, j = 0; i < m; i++) { + // 如果当前字符不匹配,则回溯到之前匹配的部分 + while (j > 0 && needle[i] !== needle[j]) { + j = pi[j - 1]; // 回溯到上一个可能的匹配位置 + } + + // 如果当前字符匹配,增加匹配长度 + if (needle[i] === needle[j]) { + j++; + } + + // 更新pi数组 + pi[i] = j; + } + + // 开始搜索主字符串haystack + for (let i = 0, j = 0; i < n; i++) { + // 如果当前字符不匹配,则回溯 + while (j > 0 && haystack[i] !== needle[j]) { + j = pi[j - 1]; // 回溯到上一个可能的匹配位置 + } + + // 如果匹配成功,继续检查下一个字符 + if (haystack[i] === needle[j]) { + j++; + } + + // 如果完整匹配模式串,则返回匹配的起始位置 + if (j === m) { + return i - m + 1; + } + } + + // 如果没有匹配成功,返回-1 + return -1; +}; + +/* +使用kmp,上面的写法是leetcode官方提供的,我按照自己的理解也写了下面这部分实现 +*/ +function f2(haystack, needle) { + const n = haystack.length; // 匹配串的长度 + const m = needle.length; // 模式串的长度 + + // 1. 如果想使用kmp来快速跳过无用的遍历,就需要计算出模式串needle的next数组 + const next = new Array(m).fill(0); // 初始化所有位置的前缀值为0(值初始化第一个位置也行) + for (let i = 1, j = 0; i < m; i++) { // i指向后缀的最后一个字符,j指向前缀的最后一个字符,按照kmp对前缀的定义,i初始化为1,j初始化为0 + // 如果不相等,表示i指向的这个后缀最后一个字符和j指向前缀的最后一个字符不相等,这个时候我们需要找next[j-1]这个位置的字符 + // 是否等于i指向的这个字符,以abaabaf为例,假设现在j指向abaa,i指向abaf,由于j和i指向的字符不相等,所以按照要求j应该移动 + // next[j-1]的位置,j-1指向的是aba,next[j-1]表示的就是最大公共前后缀的长度那就是前缀”a“和后缀”a“长度为1,所以j指向了1, + // j=1时 指向的b并不等于f,所以还要继续把j移动到当前位置的next[j-1],所以这是一个循环的过程,并且要保证j>0,不然就会出现越界错误 + // 上面的描述可能有点模糊可能体现不出为什么需要把j移动到next[j-1]的位置, + // abaabaf j = 3, i = 6, 前缀abaa 后缀abaf,显然不相等 所以后缀不可能包含aba了,只能aba里面的最大工作前后缀 + // abaabaf j = 1, i = 6, 前缀ab,后缀af,显然不相等,继续往前找 + while (j > 0 && needle[i] !== needle[j]) { + j = next[j - 1]; + } + if (needle[i] === needle[j]) { // 如果相等,表示最大相同前缀需要往后移动一位 + j++; + } + // 把当前j表示的就是最大前后缀长度 + next[i] = j; + } + + // 经过上面的计算,我们获得了一个模式串的next数组,现在我们就可以通过这个数组快速匹配了 + 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; // 没有匹配到 +}