最长回文串 - 马拉车算法理解

197 阅读1分钟

马拉车算法

理解:

马拉车算法的实质: 就是就是有判断的动态规划。 根据回文串的特性 - 对称性,用镜像位置的长度,来推导当前位置的回文长度。
/**
 * 
 *  【最长回文串 - 马拉车算法】
 * 
 * 描述:
 *     求解字符串中最长回文子串。     
 * eg:
 *     =>| aba 
 *     <=| aba
 *      
 *     =>| abba
 *     <=| abba
 * 
 * 思路:(马拉车算法)=> 动态规划 + 中心扩散。
 *   前置:
 *      将 字符串 变成奇数 字符串。 添加‘#’等。 这样 ab#ba 就更方便中心扩散。
 *   过程:
 *      范围 [i' - i'R, i'] < [left, i'] 时, i'R = iR
 *      范围 [i' - i'R, i'] > [left, i'] 时, i'R >= iR = [left, i'] = [i', right]
 *          若iR 包含 [i, right + 1], 那么 由于镜像[i' - i'R, i'] > [left, i'].
 *          那么,由于对称,范围会变成[left+1, right+1]。 所以只能 iR = [left, i'] = [i, right]
 *      范围 [i' - i'R, i'] = [left, i'] 时, [i' - i'R, i'] <= [i, right]
 *          [right, ...)是可以关于 i 对称的。 因为镜像方不对称。
 * 方程:
 * // 不包含在范围中的时候
 *  dp[i] = centralDiffusion(i, i);
 * // 包含在最大范围中的时候
 *  dp[i] = (
 *     i' - i'R > left时,  dp[i']
 *     i' - i'R < left时,  i' - left
 *     i' - i'R = left时,  centralDiffusion (i, right) 
 * 
 *  )   
 *    
 * 代码过程
 *  
 *      // 1. 将字符串 变成 奇数串。方便中心扩散
 *      // 2. dp方程
 *          // 2.1 范围外 - 中心扩散 则 当前半径 = 中心扩散起点[i]
 *          // 2.2 范围内 - 动态规划,使用已经计算过的,来求解当前
 *              // 2.2.1 当 镜像 完全在范围内。则 当前半径 = 镜像半径
 *              // 2.2.2 当 镜像 超过边界时候。则 当前半径 = i' - left
 *              // 2.2.3 当 镜像 等于边界时候。则 当前半径 = 中心扩散起点[right]
 *              // 更新 maxRight 和 maxCenter
 *      // 3. 处理结果. 截取 dp半径的字符串。 并 剔除 分界符
 * 
 */

function manacher (str) {
    // 条件返回. // 当为空 或者 undefined
    if (str == null) return;
    // 初始化
    const len = (str.length << 1) + 1;
    const dp = new Array(len).fill(0);
    let maxCenter = 0;
    let maxRight = 0;
    let iResult = 0; // 最大回文串的index

    // 1. 将字符串 变成 奇数串。方便中心扩散.
    const separator = '#'; // 目前用 ‘#’ 做分隔符
    const s = separatorWrapper(str, separator);
    // 2. dp方程
    for (let i = 1; i < s.length; i++) {
        if (i >= maxRight) {
            // 2.1 范围外 - 中心扩散 则 当前半径 = 中心扩散起点[i]
            dp[i] = centralDiifusion(s, i, i);
        } else {
            // const ii = maxCenter - (i - maxCenter);
            const ii = (maxCenter << 1) - i;
            // ileft = i' - i'R
            const iiLeft = ii - dp[maxCenter];
            // left = center - centerR
            const left = maxCenter - dp[maxCenter];
            // 2.2 范围内 - 动态规划,使用已经计算过的,来求解当前
            if (left < iiLeft) {
                // 2.2.1 当 镜像 完全在范围内。则 当前半径 = 镜像半径
                dp[i] = dp[ii];
            } else if (left > iiLeft) {
                // 2.2.2 当 镜像 超过边界时候。则 当前半径 = i' - left
                dp[i] = ii - left;
            } else {
                // 2.2.3 当 镜像 等于边界时候。则 当前半径 = 中心扩散起点[right]
                dp[i] = centralDiifusion(s, i, maxRight);
            }                
        }

        // 更新maxRight 和 maxCenter
        const right = i + dp[i];
        if (right > maxRight) {
            maxRight = right;
            maxCenter = i; 
        }

        // 更新最大回文串的索引。
        if (dp[i] > dp[iResult]) {
            iResult = i;
        }
    }
    // 3. 处理结果. 截取 dp半径的字符串。 并 剔除 分界符
    const left = maxCenter - dp[maxCenter];
    const right = maxCenter + dp[maxCenter];
    const resultWrapper = s.slice(left, right + 1);
    // 剔除分隔符
    return resultWrapper.split(separator).join('');
}

function separatorWrapper (s,separator) {
    return `${separator}${s.split('').join(separator)}${separator}`;
}

function centralDiifusion (s, i, start) {
//    let left = i - (start - i);
    let left = (i << 1) - start;
    let right = start;
   
    // 中心扩散
    while (left >=0 && right < s.length) {
        // 结束条件
        if (left === 0 || right === s.length - 1) break;
        // 不相等 - 缩小边界
        if (s[left] !== s[right]) {
            left++;
            right--;
            break;
        }
        // 相等 - 扩大边界
        left--;
        right++;
    }

    return i - left;
}


// const result = manacher('aba');
// console.log('---------result-------', result);
const result = manacher('abba');
console.log('---------reuslt-------', result);