Shortest Palindrome

HardStringRolling HashString MatchingHash Function

Solution

Solution1: Brute Force

export function shortestPalindrome(s: string): string {
  const n = s.length;
  const reversed = toReversed(s);
  for (let i = 0; i <= n; i++) {
    if (s.startsWith(reversed.slice(i))) {
      return reversed.slice(0, i) + s;
    }
  }
  return '';
}
 
function toReversed(str: string): string {
  return [...str].reverse().join('');
}

Complexity

  • Time: O(N^2)
  • Space: O(N)

Solution2: KMP(Knuth-Morris-Pratt) Algorithm

export function shortestPalindrome(s: string): string {
  const n = s.length;
  const reversed = toReversed(s);
  const combined = `${s}#${reversed}`;
  const prefixTable = createPrefixTable(combined);
  const palindromeLength = prefixTable[prefixTable.length - 1];
  const suffix = reversed.slice(0, n - palindromeLength);
  return suffix + s;
}
 
function toReversed(str: string): string {
  return [...str].reverse().join('');
}
 
function createPrefixTable(str: string): number[] {
  const prefixTable = new Array<number>(str.length).fill(0);
  for (let i = 1, l = 0; i < str.length; i++) {
    while (0 < l && str[i] !== str[l]) {
      l = prefixTable[l - 1];
    }
    if (str[i] === str[l]) {
      l += 1;
    }
    prefixTable[i] = l;
  }
  return prefixTable;
}

Complexity

  • Time: O(N)
  • Space: O(N)

Solution3: Manacher Algorithm

export function shortestPalindrome(s: string): string {
  if (s.length === 0) {
    return s;
  }
  const modified = `^#${[...s].join('#')}#$`;
  const n = modified.length;
  const radius = new Array<number>(n).fill(0);
 
  let centerIndex = 0;
  let rightBoundary = 0;
  let maxPalindromeLength = 0;
 
  for (let i = 1; i < n - 1; i++) {
    const mirrorIndex = 2 * centerIndex - i;
    if (i < rightBoundary) {
      radius[i] = Math.min(rightBoundary - i, radius[mirrorIndex]);
    }
 
    while (modified[i + 1 + radius[i]] === modified[i - 1 - radius[i]]) {
      radius[i] += 1;
    }
 
    if (i + radius[i] > rightBoundary) {
      centerIndex = i;
      rightBoundary = i + radius[i];
    }
 
    if (i - radius[i] === 1) {
      maxPalindromeLength = Math.max(maxPalindromeLength, radius[i]);
    }
  }
  const suffix = s.slice(maxPalindromeLength);
  return toReversed(suffix) + s;
}
 
function toReversed(str: string): string {
  return [...str].reverse().join('');
}

Complexity

  • Time: O(N)
  • Space: O(N)