Before attempting this problem, you should be comfortable with:
We need two disjoint palindromic subsequences with maximum product of their lengths. For each character, we have three choices: add it to the first subsequence, add it to the second subsequence, or skip it entirely. At the end, if both subsequences are palindromes, we compute their product.
seq1, or add to seq2.class Solution:
def maxProduct(self, s: str) -> int:
def isPal(s):
i, j = 0, len(s) - 1
while i < j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
res = 0
def rec(i, seq1, seq2):
nonlocal res
if i == len(s):
if isPal(seq1) and isPal(seq2):
res = max(res, len(seq1) * len(seq2))
return
rec(i + 1, seq1, seq2)
rec(i + 1, seq1 + s[i], seq2)
rec(i + 1, seq1, seq2 + s[i])
rec(0, "", "")
return resSince the string length is small (up to 12), we can represent each subsequence as a bitmask. We first enumerate all possible subsequences, check which ones are palindromes, and store them. Then we check all pairs of palindromic subsequences. Two subsequences are disjoint if their masks have no overlapping bits (bitwise AND equals 0).
1 to .0), compute the product.class Solution:
def maxProduct(self, s: str) -> int:
def isPal(s):
i, j = 0, len(s) - 1
while i < j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
N, pali = len(s), {}
for mask in range(1, 1 << N):
subseq = ""
for i in range(N):
if mask & (1 << i):
subseq += s[i]
if isPal(subseq):
pali[mask] = len(subseq)
res = 0
for m1 in pali:
for m2 in pali:
if m1 & m2 == 0:
res = max(res, pali[m1] * pali[m2])
return resInstead of checking all pairs of palindromic masks, we can optimize by iterating through palindromic masks for the first subsequence and computing the longest palindromic subsequence (LPS) in the remaining characters. This avoids redundant pair comparisons while still finding the optimal solution.
1 to .class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
n = len(s)
if n == 0:
return 0
dp = [1] * n
for i in range(n - 1, -1, -1):
prev = 0
for j in range(i + 1, n):
tmp = dp[j]
if s[i] == s[j]:
dp[j] = 2 + prev
else:
dp[j] = max(dp[j - 1], dp[j])
prev = tmp
return dp[n - 1]
def maxProduct(self, s: str) -> int:
n = len(s)
res = 0
for i in range(1, 1 << n):
seq1, seq2 = [], []
for j in range(n):
if (i & (1 << j)) != 0:
seq1.append(s[j])
else:
seq2.append(s[j])
if not self.isPal(seq1):
continue
lps = self.longestPalindromeSubseq(''.join(seq2))
res = max(res, len(seq1) * lps)
return res
def isPal(self, s: str) -> bool:
i, j = 0, len(s) - 1
while i < j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return TrueWe can further optimize by computing the LPS directly on the original string while using the mask to skip characters that belong to the first subsequence. This avoids creating new strings and reduces overhead. We also validate palindromes efficiently using two pointers with mask-based skipping.
class Solution:
def longestPalindromeSubseq(self, s: str, mask: int, dp: List[int]) -> int:
n = len(s)
for i in range(n - 1, -1, -1):
if (mask & (1 << i)) != 0:
continue
prev = 0
for j in range(i + 1, n):
tmp = dp[j]
if (mask & (1 << j)) == 0 and s[i] == s[j]:
dp[j] = 2 + prev
else:
dp[j] = max(dp[j - 1], dp[j])
prev = tmp
return dp[-1]
def maxProduct(self, s: str) -> int:
n = len(s)
res = 0
dp = [1] * n
for i in range(1, 1 << n):
m1 = self.palsize(s, i)
if m1 == 0:
continue
for j in range(n):
if (i & (1 << j)) == 0:
dp[j] = 1
else:
dp[j] = 0
m2 = self.longestPalindromeSubseq(s, i, dp)
res = max(res, m1 * m2)
return res
def palsize(self, s: str, mask: int) -> int:
i, j = 0, len(s) - 1
res = 0
while i <= j:
if (mask & (1 << i)) == 0:
i += 1
elif (mask & (1 << j)) == 0:
j -= 1
else:
if s[i] != s[j]:
return 0
res += 1 if i == j else 2
i += 1
j -= 1
return resThe two palindromic subsequences must be disjoint, meaning they cannot share any character. When using bitmasks, ensure that (mask1 & mask2) == 0 before considering a pair. Forgetting this check leads to invalid solutions where the same character is counted in both subsequences.
When iterating through bitmasks, some masks represent subsequences that are not palindromes. Computing the product of lengths without first verifying that both subsequences are actually palindromes leads to incorrect maximum values.
Constructing new strings for each subsequence and then checking if they are palindromes is expensive. For optimal solutions, use two-pointer validation directly on the original string with mask-based index skipping to avoid repeated string allocations inside the exponential loop.