Longest Palindromic Subsequence II

LeetCode problem 1682. Longest Palindromic Subsequence II.

## Problem description

A subsequence of a string `s` is considered a **good palindromic subsequence** if:

* It is a subsequence of `s`.
* It is a palindrome (has the same value if reversed).
* It has an **even** length.
* No two consecutive characters are equal, except the two middle ones.

For example, if `s = "abcabcabb"`, then `"abba"` is considered a **good palindromic subsequence**, while `"bcb"` (not even length) and `"bbbb"` (has equal consecutive characters) are not.

Given a string `s`, return *the **length** of the **longest good palindromic subsequence** in* `s`.

Example 1
**Input:** s = "bbabab"
**Output:** 4
**Explanation:** The longest good palindromic subsequence of s is "baab".
Example 2
**Input:** s = "dcbccacdb"
**Output:** 4
**Explanation:** The longest good palindromic subsequence of s is "dccd".
Constraints
* `1 <= s.length <= 250`
* `s` consists of lowercase English letters.

## Problem analysis

We can use dynamic programming to find out the longest palindromic substring in a similar fashion as in the Longest Palindromic Subsequence problem. But there are two additional constraints here:

* The length of good palindromes must be even.
* No two adjacent characters are the same, except for the two characters in the middle.

So we have to store an additional piece of information: the *edge characters* for the palindromic substrings.

We can define a class `Cell` as follows:

```
class Cell {
public:
 int maxLen = 0;
 char edgeChar = '\0';

 bool canExtend(char ch) {
 if (edgeChar == '\0') {
 return true;
 }
 return edgeChar != ch;
 }

 bool operator==(const Cell& other) const {
 return this->val == other.val;
 }

 bool operator>(const Cell& other) const {
 return this->val > other.val;
 }

 bool operator<(const Cell& other) const {
 return this->val < other.val;
 }
};

```

* `maxLen` means the maximum length of good palindromic subsequences contained in `input[left..right]`.
* `edgeChar` means the edge character of the longest good palindromic subsequences.
	+ Put the null character '\0' if `maxLen` is 0, or if `maxLen` > 0 and there are more than one good palindromic subsequences of length `maxLen`. In this case we can always manage to extend the palindrome without concerning about its edge characters.
* `canExtend` means whether we can extend the good palindromic subsequence by adding character `ch` on both sides.
	+ We cannot extend if there is exactly one good palindromic subsequence of length `maxLen`, and this palindrome's edge character is equal to `ch`.
* Operators ==, >, and < are overloaded to compare the two cells. (C++ only)

Then we can start solving the problem using dynamic programming.

Let `len` be the length of the input string. We first build the `dp` table with `len` rows and `len` columns. Let `dp[left][right]` be the cell containing the information about the maximal good palindromic subsequence on `input[left..right]`.

The transition equations are as follows:

* Base case: `subLen == 2`, `dp[left][right] = (input[left] == input[right]) ? 2 : 0`.
* If `subLen >= 3`:
	+ If `input[left] == input[right]`, this means we can *possibly* extend the palindromic subsequence using the edge characters. We need to check the down-left cell `dp[left+1][right-1]`.
		- If `dp[left+1][right-1].edgeChar` is the same as `input[left]`, then it would be inevitable that we will make two adjacent characters in the palindrome be the same, which is not allowed. So we copy `dp[left+1][right-1].maxLen` and `dp[left+1][right-1].edgeChar` into `dp[left][right]`.
		- Otherwise we will not be constrained by the edge characters. We put `dp[left][right].maxLen = dp[left+1][right-1] + 2` and `dp[left][right] = input[left]`.
	+ Otherwise we cannot extend the longest good palindrome in any substring of `input[left..right]`. So we look into the left cell `dp[left][right+1]` and the down cell `dp[left][right-1]`.
		- If the down cell and the right cell has the same `maxLen`, we make use of the `maxLen`. Then check the `edgeChar` of the two cells: if they are the same then we use that `edgeChar`; otherwise we set `edgeChar` to null character (there are two different subsequences of `maxLen` with different edge characters, which will not preclude us from extending the palindromes).
		- Otherwise pick the cell with greater `maxLen`, and copy its `edgeChar`.

We can start filling up the table from `subLen = 2`, and then increase `subLen` step-wise until it matches the input length.

The final returned result is `dp[0][len-1]`.

### Complexity analysis

Let `len` be the input string's length.

Space complexity
Since we created the `dp` table, the space complexity is **O(len2)**.
Time complexity
Since we created the `dp` table, and each cell requires constant computation, the time complexity is **O(len2)**.
## Solution

### C++ solution

```
class Solution {
private:
 class Cell {
 public:
 int maxLen = 0;
 char edgeChar = '\0';

 bool canExtend(char ch) {
 if (edgeChar == '\0') {
 return true;
 }
 return edgeChar != ch;
 }

 bool operator==(const Cell& other) const {
 return this->maxLen == other.maxLen;
 }

 bool operator>(const Cell& other) const {
 return this->maxLen > other.maxLen;
 }

 bool operator<(const Cell& other) const {
 return this->maxLen < other.maxLen;
 }
 };

public:
 int longestPalindromeSubseq(const std::string& s) {
 const int len = s.length();
 if (len <= 1) {
 return 0;
 }
 else if (len == 2) {
 return s[0] == s[1] ? 2 : 0;
 }

 // dp[left][right] indicates maximum length of good palindromic subsequence
 // on s[left..right]
 Cell dp[len][len];

 for (int subLen = 2; subLen <= len; ++subLen) {
 for (int left = 0; left <= len - subLen; ++left) {
 int right = left + subLen - 1;

 Cell& curr = dp[left][right];
 if (subLen == 2) {
 if (s[left] == s[right]) {
 curr.maxLen = 2;
 curr.edgeChar = s[left];
 }
 }
 else {
 // subLen >= 3
 if (s[left] != s[right]) {
 Cell& leftCell = dp[left][right-1];
 Cell& downCell = dp[left+1][right];

 if (leftCell == downCell) {
 curr.maxLen = leftCell.maxLen;
 curr.edgeChar = leftCell.edgeChar == downCell.edgeChar ?
 leftCell.edgeChar : '\0';
 }
 else {
 Cell& cellToUse = (downCell > leftCell) ? downCell : leftCell;
 curr.maxLen = cellToUse.maxLen;
 curr.edgeChar = cellToUse.edgeChar;
 }
 }
 else {
 Cell& downLeftCell = dp[left+1][right-1];
 if (downLeftCell.canExtend(s[left])) {
 curr.maxLen = downLeftCell.maxLen + 2;
 curr.edgeChar = s[left];
 }
 else {
 curr.maxLen = downLeftCell.maxLen;
 curr.edgeChar = downLeftCell.edgeChar;
 }
 }
 }
 }
 }

 return dp[0][len-1].maxLen;
 }
};

```

### Java solution

```
class Solution {
 private static class Cell implements Comparable<Cell> {
 int maxLen = 0;
 char edgeChar = '\0';

 boolean canExtend(char ch) {
 if (edgeChar == '\0') {
 return true;
 }
 return edgeChar != ch;
 }

 @Override
 public int compareTo(Cell other) {
 if (other == null) {
 return 1;
 }
 return Integer.compare(maxLen, other.maxLen);
 }
 };

 public int longestPalindromeSubseq(String s) {
 final int len = s.length();
 if (len <= 1) {
 return 0;
 }
 else if (len == 2) {
 return s.charAt(0) == s.charAt(1) ? 2 : 0;
 }

 // dp[left][right] indicates maximum length of good palindromic subsequence
 // on s[left..right]
 Cell[][] dp = new Cell[len][len];

 for (int subLen = 2; subLen <= len; ++subLen) {
 for (int left = 0; left <= len - subLen; ++left) {
 int right = left + subLen - 1;

 if (dp[left][right] == null) {
 dp[left][right] = new Cell();
 }
 Cell curr = dp[left][right];

 if (subLen == 2) {
 if (s.charAt(left) == s.charAt(right)) {
 curr.maxLen = 2;
 curr.edgeChar = s.charAt(left);
 }
 }
 else {
 // subLen >= 3
 if (s.charAt(left) != s.charAt(right)) {
 if (dp[left][right-1] == null) {
 dp[left][right-1] = new Cell();
 }
 if (dp[left+1][right] == null) {
 dp[left+1][right] = new Cell();
 }
 Cell leftCell = dp[left][right-1];
 Cell downCell = dp[left+1][right];

 if (leftCell.compareTo(downCell) == 0) {
 curr.maxLen = leftCell.maxLen;
 curr.edgeChar = leftCell.edgeChar == downCell.edgeChar ?
 leftCell.edgeChar : '\0';
 }
 else {
 Cell cellToUse = (downCell.compareTo(leftCell) > 0) ?
 downCell : leftCell;
 curr.maxLen = cellToUse.maxLen;
 curr.edgeChar = cellToUse.edgeChar;
 }
 }
 else {
 if (dp[left+1][right-1] == null) {
 dp[left+1][right-1] = new Cell();
 }
 Cell downLeftCell = dp[left+1][right-1];

 if (downLeftCell.canExtend(s.charAt(left))) {
 curr.maxLen = downLeftCell.maxLen + 2;
 curr.edgeChar = s.charAt(left);
 }
 else {
 curr.maxLen = downLeftCell.maxLen;
 curr.edgeChar = downLeftCell.edgeChar;
 }
 }
 }
 }
 }

 return dp[0][len-1].maxLen;
 }
}

```

## Notes

Operator overloading
C++ supports operator overloading, while Java does not.
Array of objects

* C++ requires the class to be instantiated to have a *default constructor* (a constructor that takes no parameters). Otherwise, trying to create an array of objects of that class will generate a *compile error*, because the compiler will not know how to initialize each object without parameters.
* Java, instead, initializes each element in the array to be null. You can only initialize each object in the array *after* you have created the array.