algorighm/top-interview-leetcode150/numbers-and-strings/28找出字符串中第一个匹配的下标.js
2025-04-01 11:20:39 +08:00

134 lines
4.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
*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初始化为1j初始化为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; // 没有匹配到
}