5. Longest Palindromic Substring
Find the longest palindromic substring in string S. You may assume that there exists one unique longest palindromic substring.
class Solution { private int resultBegin, resultLen; public String longestPalindrome(String s) { int len = s.length(); if (len < 2) return s; for (int center = 0; center < len - 1; ++center) { extendBothEnds(s, center, center); //when longestPalindrome length is odd extendBothEnds(s, center, center + 1); //when longestPalindrome length is even } return s.substring(resultBegin, resultBegin + resultLen); } private void extendBothEnds(String s, int left, int right) { while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) { --left; ++right; } ++left; --right; int currentLen = right - left + 1; if (currentLen > resultLen) { resultBegin = left; resultLen = currentLen; } } }
214. Shortest Palindrome
Given a string S, you are allowed to convert it to a palindrome by adding characters in front of it. Find and return the shortest palindrome you can find by performing this transformation.
For example:
Given "aacecaaa"
, return "aaacecaaa"
.
Given "abcd"
, return "dcbabcd"
.
An intuitive solution, but got TLE.
The idea is to get the longest palindrome prefix, and then reverse the rest of the string and add it to the front. O(n2) complexity.
class Solution { public String shortestPalindrome(String s) { if (s.length() <= 1) return s; int end = s.length(); for (; end >= 1; --end) if (isPalindrome(s.substring(0, end))) break; return new StringBuilder(s.substring(end)).reverse().toString() + s; } private boolean isPalindrome(String s) { for (int left = 0, right = s.length() - 1; left < right; ++left, --right) if (s.charAt(left) != s.charAt(right)) return false; return true; } }
Recursion Solution
class Solution { /** * The goal is to complete s into a Palindrome. We could tackle this problem little by little. * <p> * We need to split the string into two parts, s[0,j), s[j,len), such that * s[j, len) is impossible to be matched by s itself, we must prepend a reversed part for it * in front of s. * And we recursively do the same thing for s[0,j) to get this: * reverse(s[j,len)) + f(s[0,j]) + s[j,len) * <p> * One way to find j is that * s[j] is unique but not in the "middle" in the string. */ public String shortestPalindrome(String s) { int j = 0; for (int i = s.length() - 1; i >= 0; --i) //iterate i all the way to 0, such that we try to use s itself to match itself. if (s.charAt(i) == s.charAt(j)) //if j could be matched by i, whather on j itself(mid point), j's right or j's left. ++j; if (j == s.length()) return s; String suffix = s.substring(j); return new StringBuilder(suffix).reverse().toString() + shortestPalindrome(s.substring(0, j)) + suffix; } }
Solution2: KMP algorithm. Knuth-Morris-Pratt Algorithm
c a t a c b # b c a t a c
0 0 0 0 1 0 0 0 1 2 3 4 5
if we don't add #, "aaaaa" wouldn't work.
class Solution { public String shortestPalindrome(String s) { if(s.length() <= 1) return s; String temp = s + "#"+ new StringBuilder(s).reverse().toString(); int[] kmp = getTable(temp); //get the maximum palin part in s starts from 0 return new StringBuilder(s.substring(kmp[kmp.length - 1])).reverse().toString() + s; } /** * ml indicates the max MatchedLength for index i based on Knuth–Morris–Pratt algorithm * "cacfcaca" returns [0, 0, 1, 0, 1, 2, 3, 2] * "babbbabbaba" returns [0, 0, 1, 1, 1, 2, 3, 4, 2, 3, 2] */ private static int[] getTable(String s) { int[] ml = new int[s.length()]; for (int i = 1; i < s.length(); ++i) { /** * loc shows the location/index such that, * s.substring(loc-ml[loc-1],loc) of length ml[loc-1] has fully matched prefix, * we are trying to extend this substring by one more letter, which is at loc */ int loc = i; while (loc >= 1) { //ml[loc - 1] gives how many prefix we have matched, so the next index to compare/match is ml[loc - 1] int nextToMatch = ml[loc - 1]; if (s.charAt(i) == s.charAt(nextToMatch)) { ml[i] = nextToMatch + 1; break; } /** * If we don't find a match, then we try to re-use a shorter match * Think about "cacfcacb", if we cannot match 'f' with 'b', then we cannot use the fully matched "cac". * So, we go to previous letter 'c' at index 2, and check what's length that has been matched until 'c', * and reuse that shorter string. 'c' has a matched length 1, so we will compare 'a'(at 1) with 'b'... * Do this recursively. */ loc = nextToMatch; } } return ml; } }
336. Palindrome Pairs
Given a list of unique words. Find all pairs of distinct indices (i, j)
in the given list, so that the concatenation of the two words, i.e. words[i] + words[j]
is a palindrome.
Example 1:
Given words
= ["bat", "tab", "cat"]
Return [[0, 1], [1, 0]]
The palindromes are ["battab", "tabbat"]
Example 2:
Given words
= ["abcd", "dcba", "lls", "s", "sssll"]
Return [[0, 1], [1, 0], [3, 2], [2, 4]]
The palindromes are ["dcbaabcd", "abcddcba", "slls", "llssssll"]
public class Solution { class TrieNode { TrieNode[] children = new TrieNode[26]; int wordArrayIndex = -1; //keep the index for current string in the array //keeps the list of word indices in array where the substring(or prefix) is a palindrome List<Integer> list = new ArrayList<>(); } public List<List<Integer>> palindromePairs(String[] words) { List<List<Integer>> results = new ArrayList<>(); TrieNode root = new TrieNode(); for (int i = 0; i < words.length; i++) { //reverse each word and add it to the trie. addWord(root, words[i], i); } for (int i = 0; i < words.length; i++) { search(words[i], i, root, results); } return results; } private void addWord(TrieNode parent, String word, int wordIndex) { for (int i = word.length() - 1; i >= 0; --i) { int pos = word.charAt(i) - 'a'; if (parent.children[pos] == null) parent.children[pos] = new TrieNode(); if (isPalindrome(word, 0, i))//keep those words where the prefix is palindrome parent.list.add(wordIndex); parent = parent.children[pos]; } parent.list.add(wordIndex); parent.wordArrayIndex = wordIndex; } private void search(String word, int wordIndex, TrieNode node, List<List<Integer>> results) { for (int i = 0; i < word.length(); ++i) { //Case 1: word accounts for >= half of the palindrome if (node.wordArrayIndex >= 0 && node.wordArrayIndex != wordIndex //find word matching prefix. && isPalindrome(word, i, word.length() - 1) //rest of the word is palindrome ) results.add(Arrays.asList(wordIndex, node.wordArrayIndex)); node = node.children[word.charAt(i) - 'a']; if (node == null) return; } //Case 2: word accounts for < half of the palindrome //e.g. "abc", "XXXcba", as long as "XXX" part is a palindrome, then we are good. for (int j : node.list) { if (wordIndex == j) continue; results.add(Arrays.asList(wordIndex, j)); } } private boolean isPalindrome(String word, int i, int j) { while (i < j) { if (word.charAt(i++) != word.charAt(j--)) return false; } return true; } }