Skip to content

字符串匹配算法完全指南 🔍 ​

一、KMP算法:高效的字符串匹配 ​

1. 为什么需要KMP? 🤔 ​

想象你在一个很长的文章中查找一个单词,普通的方法是一个字符一个字符地比较。但如果匹配失败,我们往往要回退很多位置重新开始。这样效率很低!

KMP算法就是为了解决这个问题,它能够避免不必要的回退,大大提高匹配效率。

2. 核心思想 💡 ​

KMP的关键在于:当匹配失败时,我们已经知道了一些信息,可以利用这些信息避免回退。

举个例子:

文本串:  hello world
模式串:  world
         ↑ 
         当这里匹配失败时,我们不需要完全回退

3. 代码实现 ​

3.1 构建next数组(最关键的部分) ​

js
function getNext(pattern) {
    const next = new Array(pattern.length).fill(0);
    next[0] = -1;  // 第一个字符失配时,模式串向右移动一位
    
    let i = 1;     // 当前位置
    let j = 0;     // 最长相同前后缀长度
    
    while (i < pattern.length) {
        if (pattern[i] === pattern[j]) {
            // 字符相同,前后缀长度加1
            next[i] = j;
            i++;
            j++;
        } else if (j > 0) {
            // 字符不同,缩短前缀长度
            j = next[j];
        } else {
            // j已经退到开头,无法再缩短
            next[i] = 0;
            i++;
        }
    }
    
    return next;
}

3.2 KMP搜索 ​

js
function kmpSearch(text, pattern) {
    if (!pattern || pattern.length === 0) return 0;
    
    const next = getNext(pattern);
    let i = 0;  // 文本串指针
    let j = 0;  // 模式串指针
    
    while (i < text.length && j < pattern.length) {
        if (text[i] === pattern[j]) {
            // 字符匹配,两个指针都向前移动
            i++;
            j++;
        } else if (next[j] === -1) {
            // 模式串第一个字符就不匹配
            i++;
        } else {
            // 根据next数组移动模式串指针
            j = next[j];
        }
    }
    
    // 如果模式串完全匹配,返回匹配起始位置
    return j === pattern.length ? i - j : -1;
}

4. 实际应用示例 🌟 ​

js
// 在文本编辑器中查找关键词
function findKeyword(content, keyword) {
    const index = kmpSearch(content, keyword);
    if (index !== -1) {
        console.log(`找到关键词!位置在: ${index}`);
        // 高亮显示关键词
        return content.slice(0, index) + 
               `<span class="highlight">${keyword}</span>` + 
               content.slice(index + keyword.length);
    }
    return '未找到关键词';
}

// 使用示例
const content = "这是一篇很长的文章,我们要在其中查找某个关键词...";
const keyword = "关键词";
console.log(findKeyword(content, keyword));

二、Manacher算法:寻找最长回文子串 ​

1. 什么是回文串? 🔄 ​

回文串就是正着读和倒着读都一样的字符串,比如:

  • "level"
  • "上海自来水来自海上"

2. 算法原理 ​

2.1 预处理 ​

为了统一处理奇数和偶数长度的回文串,我们在字符间插入特殊字符:

js
// 原串:  level
// 处理后:#l#e#v#e#l#
js
function preProcess(s) {
    return '#' + s.split('').join('#') + '#';
}

2.2 核心算法 ​

js
function manacher(s) {
    const str = preProcess(s);
    const p = new Array(str.length).fill(0); // 回文半径数组
    let center = 0;  // 当前中心
    let maxRight = 0;  // 当前最右边界
    
    for (let i = 0; i < str.length; i++) {
        if (i < maxRight) {
            // 利用对称性
            const mirror = 2 * center - i;
            p[i] = Math.min(maxRight - i, p[mirror]);
        }
        
        // 尝试扩展回文
        let left = i - (1 + p[i]);
        let right = i + (1 + p[i]);
        while (left >= 0 && right < str.length && str[left] === str[right]) {
            p[i]++;
            left--;
            right++;
        }
        
        // 更新中心和右边界
        if (i + p[i] > maxRight) {
            center = i;
            maxRight = i + p[i];
        }
    }
    
    // 找到最长回文子串
    let maxLen = 0;
    let centerIndex = 0;
    for (let i = 0; i < p.length; i++) {
        if (p[i] > maxLen) {
            maxLen = p[i];
            centerIndex = i;
        }
    }
    
    // 还原原始字符串中的回文子串
    const start = Math.floor((centerIndex - maxLen) / 2);
    return s.substring(start, start + maxLen);
}

3. 使用示例 ⭐ ​

js
// 查找最长回文子串
const text = "babadada";
console.log(manacher(text));  // "adada"

// 实际应用:检查文本是否对称
function isSymmetric(text) {
    const longest = manacher(text);
    return longest.length === text.length;
}

console.log(isSymmetric("level")); // true
console.log(isSymmetric("hello")); // false

三、性能对比 📊 ​

算法时间复杂度空间复杂度适用场景
朴素匹配O(mn)O(1)短文本匹配
KMPO(m+n)O(m)长文本搜索
ManacherO(n)O(n)回文串查找

四、实践建议 💡 ​

  1. 对于简单的字符串匹配,可以使用内置的 indexOf() 方法
  2. 需要高效的模式匹配时,使用KMP算法
  3. 查找回文串时,Manacher算法是最佳选择
  4. 注意边界情况的处理

五、扩展阅读 📚 ​