马拉车算法Kimi详解(js版)

马拉车算法(Manacher's Algorithm)讲解

马拉车算法是一种用于寻找字符串中最长回文子串的高效算法。它的核心思想是利用回文的对称性,避免不必要的重复计算,从而将时间复杂度降低到 (O(n))。

1. 问题背景

给定一个字符串 ( s ),要求找到其中最长的回文子串。回文子串是指正读和反读都相同的子串。

2. 马拉车算法的核心思想

马拉车算法通过在字符串中插入特殊字符(如 #),将原字符串转换为一个长度为奇数的新字符串,从而简化回文中心的处理。

2.1 插入特殊字符

假设原字符串为 s,长度为 ( n )。我们可以在每个字符之间和字符串的首尾插入特殊字符 #,将原字符串转换为新字符串 t,长度为 ( 2n + 1 )。

例如:

  • 原字符串:s = "abba"
  • 转换后:t = "#a#b#b#a#"

这样做的好处是,回文中心可以统一处理为一个字符,避免了奇偶长度的区分。

2.2 定义回文半径

对于新字符串 t 中的每个字符 ( t[i] ),定义 ( P[i] ) 为以 ( t[i] ) 为中心的最长回文子串的半径。例如:

  • 如果 ( t[i] = 'a' ),且以 ( t[i] ) 为中心的最长回文子串为 "#a#",则 ( P[i] = 1 )。
2.3 利用对称性

假设我们已经计算了 ( P[i] ),并且知道以 ( t[i] ) 为中心的最长回文子串。当计算 ( P[j] ) 时,如果 ( j ) 在以 ( t[i] ) 为中心的回文子串内,可以通过对称性快速计算 ( P[j] )。

具体来说:

  • 如果 ( j ) 在以 ( t[i] ) 为中心的回文子串内,且 ( j ) 关于 ( i ) 的对称位置为 ( j' ),则 ( P[j] ) 至少为 ( P[j'] )。
  • 如果 ( j + P[j'] ) 超出了以 ( t[i] ) 为中心的回文子串范围,则需要进一步扩展 ( P[j] )。

3. 算法步骤

  1. 预处理字符串 :将原字符串 s 转换为新字符串 t,并在每个字符之间插入 #
  2. 初始化数组:定义数组 ( P ),其中 ( P[i] ) 表示以 ( t[i] ) 为中心的最长回文子串的半径。
  3. 遍历字符串
    • 维护两个变量:centerright,分别表示当前已知的最长回文子串的中心和右边界。
    • 对于每个位置 ( i ):
      • 如果 ( i ) 在 right 的范围内,计算 P[i] 的初始值: P[i]=min(P[2×center−i],right−i)
      • 如果 ( i ) 超出 right 的范围,初始化 P[i] = 0
      • 从 ( i + P[i] + 1 ) 开始扩展,直到不满足回文条件。
      • 更新 P[i]
      • 如果 ( i + P[i] > right ),更新 center = iright = i + P[i]
  4. 计算最长回文子串 :遍历 P 数组,找到最大值及其位置,从而确定最长回文子串。

4. JavaScript 代码实现

javascript 复制代码
function longestPalindromicSubstring(s) {
    // 预处理字符串
    const t = `#${s.split('').join('#')}#`;
    const n = t.length;
    const P = new Array(n).fill(0);
    let center = 0, right = 0;
    let maxLen = 0, centerIndex = 0;

    for (let i = 0; i < n; i++) {
        // 如果当前字符在已知的最长回文子串范围内,利用对称性计算初始 P[i]
        if (i < right) {
            const mirror = 2 * center - i;
            P[i] = Math.min(P[mirror], right - i);
        }

        // 尝试扩展回文子串
        while (i - P[i] - 1 >= 0 && i + P[i] + 1 < n && t[i - P[i] - 1] === t[i + P[i] + 1]) {
            P[i]++;
        }

        // 如果扩展后的回文子串超出了已知的右边界,更新中心和右边界
        if (i + P[i] > right) {
            center = i;
            right = i + P[i];
        }

        // 更新最长回文子串的信息
        if (P[i] > maxLen) {
            maxLen = P[i];
            centerIndex = i;
        }
    }

    // 提取最长回文子串
    const start = (centerIndex - maxLen) >>> 1;
    return s.substring(start, start + maxLen);
}

// 示例
const s = "babad";
console.log(longestPalindromicSubstring(s)); // 输出 "bab" 或 "aba"

代码说明

  1. 预处理字符串 :通过在每个字符之间插入 #,将原字符串转换为新字符串 t
  2. 初始化数组P 数组用于存储每个位置的回文半径。
  3. 遍历字符串
    • 使用 centerright 维护当前已知的最长回文子串的中心和右边界。
    • 对于每个位置 i,根据是否在 right 范围内,计算 P[i] 的初始值。
    • 通过扩展回文子串更新 P[i],并根据需要更新 centerright
  4. 提取结果 :根据 P 数组找到最长回文子串的中心和长度,提取对应的子串。

时间复杂度分析

马拉车算法的时间复杂度为 (O(n)),其中 ( n ) 是新字符串 t 的长度。这是因为每个字符最多被扩展一次,且每次扩展操作的时间复杂度为 (O(1))。

个人总结

通过定义两个重要变量,回文中心位置以及回文半径,进行遍历计算。 优化计算的关键是运用上之前计算值,因此在遍历回文中心时,想办法用上之前的回文中心计算结果,找到两者之间关系(即参照之前某次回文中心位置和回文半径,是否能够直接求出当前回文中心的回文半径),进行优化。

相关推荐
xingkongvv1239 分钟前
C# 异步编程
java·服务器·开发语言·前端·javascript
杨超越luckly40 分钟前
HTML应用指南:利用GET请求获取全国Apple Store 零售店位置信息
大数据·前端·arcgis·html·数据可视化·门店
adminwolf3 小时前
基于Vue.js和Golang构建高效在线客服系统:前端实现与后端交互详解
前端·vue.js·golang
二哈喇子!4 小时前
Vue3生命周期
前端·javascript·vue.js
运维帮手大橙子8 小时前
完整的登陆学生管理系统(配置数据库)
java·前端·数据库·eclipse·intellij-idea
金智维科技官方8 小时前
常见的大模型分类
人工智能·算法·ai·语言模型·数据挖掘
yzzzzzzzzzzzzzzzzz8 小时前
leetcode热题——有效的括号
算法·
_Kayo_9 小时前
CSS BFC
前端·css
崎岖Qiu9 小时前
leetcode1343:大小为K的子数组(定长滑动窗口)
java·算法·leetcode·力扣·滑动窗口
Shun_Tianyou10 小时前
Python Day25 进程与网络编程
开发语言·网络·数据结构·python·算法