Longest Palindromic Substring

LeetCode problem 5. Longest Palindromic Substring.

## Problem description

Given a string `s`, return *the longest palindromic substring* in `s`.

Example 1
**Input:** s = "babad"
**Output:** "bab"
**Note:** "aba" is also a valid answer.
Example 2
**Input:** s = "cbbd"
**Output:** "bb"
Example 3
**Input:** s = "a"
**Output:** "a"
Example 4
**Input:** s = "ac"
**Output:** "a"
Constraints
* `1 <= s.length <= 1000`
* `s` consist of only digits and English letters (lower-case and/or upper-case).

## Approach 1: Dynamic programming

We know that if a substring is already a palindrome, adding the same character on its both ends extends it into a longer palindrome. Hence we can use dynamic programming to solve the problem.

Let `len` be the length of the input. We can construct an `isPalindrome` table of `len` rows and `len` columns, in which `isPalindrome[left][right]` will represent whether `input[left..right]` is a palindromic substring.

We start from substring length 1, and increase substring length step-wise until we cover the entire input string.

* Base case for substring length 1: `isPalindrome[left][right] = true`.
* Base case for substring length 2: `isPalindrome[left][right] = input[left] == input[right]`.
* For substring lengths greater than 2: `isPalindrome[left][right] = (input[left] == input[right]) && isPalindrome[left+1][right-1]`.

Whenever we fill in the table, we keep track of the `maxLen` we have found, and the `maxLenStart` corresponding to the starting position of the substring with maximum length.

Once we fill up the table, we can obtain the result using `maxLen` and `maxLenStart`.

### Working example

* Input: "google"

* Final result: `maxLen = 4`, `maxLenStart = 0`, `result = "goog"`.

### Complexity analysis

Let `len` be the length of the input string.

Space complexity
Since we created the `isPalindrome` table with `len` rows and `len` columns, the space complexity is **O(`len`2)**.
Time complexity
Since we created the `isPalindrome` table with `len` rows and `len` columns, and each cell takes constant time to compute, the time complexity is **O(`len`2)**.
### Solution

#### C++ solution

```
class Solution {
public:
 std::string longestPalindrome(const std::string& s) {
 const int len = s.length();
 if (len == 0) {
 return "";
 }
 else if (len == 1) {
 return s;
 }
 else if (len == 2) {
 return s[0] == s[1] ? s : (std::string() + s[0]);
 }

 // use dynamic programming
 // isPalindrome[i][j] means whether substring [i..j] is palindrome
 int maxLen = 1, maxLenStart = 0;
 bool isPalindrome[len][len];
 memset(isPalindrome, false, sizeof isPalindrome);

 for (int subLen = 1; subLen <= len; ++subLen) {
 for (int left = 0; left <= len - subLen; ++left) {
 int right = left - 1 + subLen;
 if (subLen <= 2) {
 isPalindrome[left][right] = s[left] == s[right];
 }
 else {
 isPalindrome[left][right] = (s[left] == s[right])
 && isPalindrome[left+1][right-1];
 }

 if (isPalindrome[left][right] && (subLen > maxLen)) {
 maxLen = subLen;
 maxLenStart = left;
 }
 }
 }

 return s.substr(maxLenStart, maxLen);
 }
};

```

#### Java solution

```
class Solution {
 public String longestPalindrome(String s) {
 final int len = s.length();
 if (len == 0) {
 return "";
 }
 else if (len == 1) {
 return s;
 }
 else if (len == 2) {
 return s.charAt(0) == s.charAt(1) ?
 s : new String(new char[]{s.charAt(0)});
 }

 // use dynamic programming
 // isPalindrome[i][j] means whether substring [i..j] is palindrome
 int maxLen = 1, maxLenStart = 0;
 boolean[][] isPalindrome = new boolean[len][len];

 for (int subLen = 1; subLen <= len; ++subLen) {
 for (int left = 0; left <= len - subLen; ++left) {
 int right = left - 1 + subLen;
 if (subLen <= 2) {
 isPalindrome[left][right] = s.charAt(left) == s.charAt(right);
 }
 else {
 isPalindrome[left][right] = (s.charAt(left) == s.charAt(right))
 && isPalindrome[left+1][right-1];
 }

 if (isPalindrome[left][right] && (subLen > maxLen)) {
 maxLen = subLen;
 maxLenStart = left;
 }
 }
 }

 return s.substring(maxLenStart, maxLenStart + maxLen);
 }
}

```

## Approach 2: Refined dynamic programming (O(1) space)

From the previous approach we can see that in order to extend a shorter palindrome into a longer palindrome, we only need to take care of the characters at both ends.

So we can employ another strategy: start from the middle, and expand in both directions.

* The middle contains either 1 or 2 characters. Set two pointers `left` and `right` to start from the middle.
* Move `left` towards the left, and `right` towards the right.
* Stop expanding when we encounter two different characters on both ends, because from that point on constructing a palindrome is no longer possible.

### Complexity analysis

Space complexity
Since we have used the `left` and `right` pointers instead of creating the `isPalindrome` table, we can achieve **O(1)** space complexity.
Time complexity
Let `len` be the input string's length. We have `len` letters to start with, and each letter is expanded at most `len * 2` times (for constructing odd-length and even-length palindromes). So the total time complexity is **O(`len`2)**.
### Solution

#### C++ solution

```
class Solution {
public:
 std::string longestPalindrome(const std::string& s) {
 const int len = s.length();
 if (len == 0) {
 return "";
 }
 else if (len == 1) {
 return s;
 }
 else if (len == 2) {
 return s[0] == s[1] ? s : (std::string() + s[0]);
 }

 int maxLen = 1, maxLenStart = 0;

 // find odd lenth substrings
 for (int i = 0; i < len; ++i) {
 int left = i, right = i;
 while (left >= 1 && right <= len - 2) {
 if (s[--left] == s[++right]) {
 int subLen = right - left + 1;
 if (subLen > maxLen) {
 maxLen = subLen;
 maxLenStart = left;
 }
 }
 else {
 break;
 }
 }
 }

 // then find odd length substrings
 for (int i = 0; i < len - 1; ++i) {
 int left = i, right = i + 1;
 if (s[left] != s[right]) {
 continue;
 }
 else if (maxLen < 2) {
 maxLen = 2;
 maxLenStart = left;
 }

 while (left >= 1 && right <= len - 2) {
 if (s[--left] == s[++right]) {
 int subLen = right - left + 1;
 if (subLen > maxLen) {
 maxLen = subLen;
 maxLenStart = left;
 }
 }
 else {
 break;
 }
 }
 }

 return s.substr(maxLenStart, maxLen);
 }
};

```

#### Java solution

```
class Solution {
 public String longestPalindrome(String s) {
 final int len = s.length();
 if (len == 0) {
 return "";
 }
 else if (len == 1) {
 return s;
 }
 else if (len == 2) {
 return s.charAt(0) == s.charAt(1) ?
 s : new String(new char[]{s.charAt(0)});
 }

 int maxLen = 1, maxLenStart = 0;

 // find odd lenth substrings
 for (int i = 0; i < len; ++i) {
 int left = i, right = i;
 while (left >= 1 && right <= len - 2) {
 if (s.charAt(--left) == s.charAt(++right)) {
 int subLen = right - left + 1;
 if (subLen > maxLen) {
 maxLen = subLen;
 maxLenStart = left;
 }
 }
 else {
 break;
 }
 }
 }

 // then find odd length substrings
 for (int i = 0; i < len - 1; ++i) {
 int left = i, right = i + 1;
 if (s.charAt(left) != s.charAt(right)) {
 continue;
 }
 else if (maxLen < 2) {
 maxLen = 2;
 maxLenStart = left;
 }

 while (left >= 1 && right <= len - 2) {
 if (s.charAt(--left) == s.charAt(++right)) {
 int subLen = right - left + 1;
 if (subLen > maxLen) {
 maxLen = subLen;
 maxLenStart = left;
 }
 }
 else {
 break;
 }
 }
 }

 return s.substring(maxLenStart, maxLenStart + maxLen);
 }
}

```

## See also

* Longest Palindromic Subsequence
* Palindromic Substrings