马拉车算法
理解:
马拉车算法的实质: 就是就是有判断的动态规划。 根据回文串的特性 - 对称性,用镜像位置的长度,来推导当前位置的回文长度。
/**
*
* 【最长回文串 - 马拉车算法】
*
* 描述:
* 求解字符串中最长回文子串。
* 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);