Skip to Content

Shortest Palindrome

Home | Coding Interviews | Arrays and Strings | Shortest Palindrome

You are given a string s. You can convert s to a palindrome by adding characters in front of it.

Return the shortest palindrome you can find by performing this transformation.

//KMP Knuth Morris Pratt https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
public String shortestPalindrome(String s) {
    String temp = s + "#" + new StringBuilder(s).reverse().toString();
    int[] table = getTable(temp);
    
    //get the maximum palin part in s starts from 0
    return new StringBuilder(s.substring(table[table.length - 1])).reverse().toString() + s;
}

public int[] getTable(String s){
    //get lookup table
    int[] table = new int[s.length()];
    
    //pointer that points to matched char in prefix part
    
    int index = 0;
    //skip index 0, we will not match a string with itself
    for(int i = 1; i < s.length(); i++){
        if(s.charAt(index) == s.charAt(i)){
            //we can extend match in prefix and postfix
            table[i] = table[i-1] + 1;
            index ++;
        }else{
            //match failed, we try to match a shorter substring
            
            //by assigning index to table[i-1], we will shorten the match string length, and jump to the 
            //prefix part that we used to match postfix ended at i - 1
            index = table[i-1];
            
            while(index > 0 && s.charAt(index) != s.charAt(i)){
                //we will try to shorten the match string length until we revert to the beginning of match (index 1)
                index = table[index-1];
            }
            
            //when we are here may either found a match char or we reach the boundary and still no luck
            //so we need check char match
            if(s.charAt(index) == s.charAt(i)){
                //if match, then extend one char 
                index ++ ;
            }
            
            table[i] = index;
        }
        
    }
    
    return table;
}
//Rabin Karp Rolling Hash Solution https://en.wikipedia.org/wiki/Rolling_hash
public String shortestPalindrome(String s) {
    int n = s.length(), pos = -1;
    long B = 29, MOD = 1000000007, POW = 1, hash1 = 0, hash2 = 0;
    for (int i = 0; i < n; i++, POW = POW * B % MOD) {
        hash1 = (hash1 * B + s.charAt(i) - 'a' + 1) % MOD;
        hash2 = (hash2 + (s.charAt(i) - 'a' + 1) * POW) % MOD;
        if (hash1 == hash2) pos = i;
    }
    return new StringBuilder().append(s.substring(pos + 1, n)).reverse().append(s).toString();
}

Expanding from the center is a good approach if not familiar with KMP or Rabin Karp (Both are covered in Robert Sedgewick's Algorithms book and the Stanford online course which can still be found on youtube)

//https://leetcode.com/problems/shortest-palindrome/solutions/4588959/expanding-window-from-center-48ms/
const shortestPalindrome = (()=> {
    "use strict";

    const shortestPalindrome = s => { // abcd --> dcbabcd
        if (s.length <= 1) return s;
        const mid = Math.ceil(s.length/2) - 1;

        for (let i = mid; i !== -1; ) {
            const [l, length] = getLeftPalLength(s, i, i === mid);
            if (length === -1) {
                i = l;
            } else if (l === -1) {
                return prependTail(s, length);
            } else {
                i--; // xabac
            }
        }
    };

/** 
 * Length of the palindrome from index 0.
 * If no such palindrome is found, return 0.
 */ const getLeftPalLength = (s, i, isMiddle) => {
        let [l, r] = sameCharOutsideBounds(s, i, isMiddle);
        if (r !== -1) { //// xabac
            while (l !== -1 && s[l] === s[r]) {
                l--;
                r++;
            }
        }
        return [l, r];
    };
    
/** 
 * Returns the boundaries outside the center contiguous 
 * characters or -1 if `s` cannot form a left-justified 
 * palindrome.
 * 
 * We return early with a negative `r` value when contiguous 
 * chars are offset to the right of the string's middle, as — 
 *    abc[ddd]c  
 * — or —
 *    bcxxc
 * where number of chars after `r` < number of chars before `l`.
 */ const sameCharOutsideBounds = (s, i, isMiddle) => {
        const palChar = s[i];
        let l = i;
        let r = i;
        while (s[--l] === palChar);
        if (isMiddle) {
            const tooFar = (i - l - 1) + (s.length - 1) % 2;
            for (let dr = 0; s[r] === palChar; dr++, r++) {
                if (dr === tooFar) return [l, -1];
            }
        } else r++;
        return [l, r];
    };

    const prependTail = (s, length) => {
        if (length === s.length) return s;
        return s.slice(length).split("").reverse().join("") + s;
    };

    return shortestPalindrome;
})();

Posted by Jamie Meyer 9 months ago

Related Problems

Given a string s, find the length of the longest substring without repeating characters.

Given a string S and an integer K, return the length of the longest substring of S that contains at most K distinct characters.

Given an integer array nums, you need to find one continuous subarray such that if you only sort this subarray in non-decreasing order, then the whole array will be sorted in non-decreasing order.

Return the shortest such subarray and output its length.

Given an array of strings strs, group the anagrams together. You can return the answer in any order.

An Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.