LeetCode 3503 - Longest Palindrome After Substring Concatenation I
We are given two strings, s and t, and we are allowed to choose: 1. Any substring of s, including the empty string. 2. Any substring of t, including the empty string. The chosen substring from s must come first, and the chosen substring from t must come second.
Difficulty: 🟡 Medium
Topics: Two Pointers, String, Dynamic Programming, Enumeration
Solution
LeetCode 3503 - Longest Palindrome After Substring Concatenation I
Problem Understanding
We are given two strings, s and t, and we are allowed to choose:
- Any substring of
s, including the empty string. - Any substring of
t, including the empty string.
The chosen substring from s must come first, and the chosen substring from t must come second. We concatenate them to form a new string:
substring_of_s + substring_of_t
Among all possible choices, we must find the maximum possible length of a palindrome.
A palindrome is a string that reads the same forward and backward.
The important detail is that the palindrome can be formed in three different ways:
- Entirely from a substring of
s. - Entirely from a substring of
t. - Using characters from both strings, where a substring from
sis followed by a substring fromt.
Problem Understanding
The problem gives us two strings, s and t. We are allowed to choose any substring from s and any substring from t, including the possibility of choosing an empty substring from either string. After choosing them, we concatenate them in the order:
substring_from_s + substring_from_t
The resulting string must be a palindrome, and we want the maximum possible palindrome length.
A key detail is that we are not interleaving characters. The chosen substring from s must come first, and the chosen substring from t must come second. This ordering restriction is what makes the problem interesting.
For example:
s = "abcde"
t = "ecdba"
We can choose:
"abc" from s
"ba" from t
which produces:
"abcba"
a palindrome of length 5.
The constraints are very small:
1 <= |s|, |t| <= 30
Since each string contains at most 30 characters, algorithms that would be completely impractical for large strings may still be acceptable here. This strongly suggests that dynamic programming over substrings is likely feasible.
Some important edge cases are:
- The best palindrome may come entirely from one string.
- The optimal palindrome may use both strings.
- The longest answer may be just a single character.
- One selected substring may be empty.
- The palindrome may have either even or odd length.
Because substrings are contiguous, we cannot arbitrarily reorder characters. Every palindrome must arise from contiguous segments of the original strings. 1 <= len(s), len(t) <= 30
This immediately tells us that even relatively expensive dynamic programming solutions are completely acceptable. A solution with complexity around `O(n²)`, `O(n³)`, or even `O(n²m²)` would be practical for these input sizes.
Several edge cases are important:
- The optimal palindrome may come entirely from `s`.
- The optimal palindrome may come entirely from `t`.
- One chosen substring may be empty.
- The palindrome may have odd length, meaning there is a center character that does not need a matching counterpart.
- The palindrome may have even length, where all characters are paired.
- There may be no matching characters between `s` and `t`, in which case the answer is simply the longest palindromic substring inside either string.
Because the answer can come from only one string, we must not focus exclusively on palindromes that cross the concatenation boundary.
## Approaches
### Brute Force
A direct brute force solution would enumerate every substring of `s` and every substring of `t`.
For every pair:
sub_s sub_t candidate = sub_s + sub_t
we check whether the resulting string is a palindrome and update the maximum length.
Since each string of length `n` has `O(n²)` substrings, we would have:
```sql
O(n²) choices from s
O(m²) choices from t
and for each pair we may spend O(n+m) checking palindromicity.
The complexity becomes:
O(n² · m² · (n+m))
Although the constraints are small enough that this might pass, it does a huge amount of redundant work.
Key Insight
A palindrome built from both strings must have matching characters across its center.
Suppose we reverse t and call it:
rt = reverse(t)
If a substring of s matches a substring of rt, then those matching characters can form mirrored positions in the palindrome.
The crucial observation is that if we know the longest common substring between:
s[i:]
and
reverse(t)[j:]
then we know how many mirrored character pairs can be placed around the center.
After using those mirrored pairs, the palindrome may optionally extend into one side by attaching a palindrome that lies completely inside either s or t.
Therefore we need two pieces of information:
- Longest palindromic substring lengths inside
sand insidet. - Longest common substring lengths between
sandreverse(t).
Dynamic programming can compute both efficiently.
The most direct solution is to enumerate every substring of s and every substring of t.
There are:
O(n²) substrings of s
O(m²) substrings of t
For every pair, we concatenate them and check whether the resulting string is a palindrome.
The number of combinations becomes:
O(n²m²)
and each palindrome check can cost up to:
O(n + m)
Therefore the total complexity is:
O(n²m²(n + m))
This works for the given constraints because the strings are tiny, but it does not scale and does not exploit any structure of the palindrome.
The reason it is correct is straightforward: every legal choice is examined, and the longest valid palindrome is recorded.
Optimal Dynamic Programming Approach
The key observation is that any palindrome crossing the boundary between s and t consists of two parts:
- Matching mirrored characters coming from both strings.
- An optional palindromic center that lies entirely inside one string.
Suppose we reverse t.
Now matching characters between s and reverse(t) correspond exactly to mirrored pairs in the final palindrome.
If we know the longest common suffix ending at positions:
s[i]
reverse(t)[j]
then we know how many mirrored character pairs can be placed around the palindrome.
After building those mirrored pairs, we may still extend the palindrome by inserting a palindromic substring entirely inside one string as the center.
This leads to two preprocessing tasks:
- Compute, for every starting position, the longest palindromic substring beginning there.
- Compute a DP table that stores matching lengths between
sandreverse(t).
The resulting solution runs in O(nm + n² + m²) time.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²m²(n+m)) | O(1) | Enumerate every pair of substrings and test palindromes |
| Optimal | O(n² + m² + nm) | O(n² + m² + nm) | DP for palindromic substrings and common substrings |
Algorithm Walkthrough
Step 1: Compute palindromic substrings inside each string
For a string x, create:
pal[i][j]
which is true if x[i:j+1] is a palindrome.
This is the classic palindrome DP:
-
Length 1 substrings are palindromes.
-
Length 2 substrings are palindromes if both characters match.
-
Longer substrings are palindromes if:
-
endpoints match
-
interior substring is also a palindrome
While building this table, record the maximum palindrome length starting at each position and ending at each position.
These values allow us to later attach a palindrome entirely contained within one string.
Step 2: Reverse t
Construct:
rt = t[::-1]
A common substring between s and rt corresponds to matching mirrored characters between s and t.
Step 3: Compute longest common substring DP
Define:
lcs[i][j]
as the length of the longest common substring beginning at:
| Brute Force | O(n²m²(n+m)) | O(n+m) | Enumerates every substring pair and checks palindrome validity |
| Optimal | O(nm + n² + m²) | O(nm) | Uses palindrome preprocessing and matching DP |
Algorithm Walkthrough
Step 1: Precompute palindromic substrings in each string
For every index in a string, we want to know the length of the longest palindrome that starts at that index.
For example:
s = "ababa"
start index 0 -> "ababa" length 5
start index 1 -> "bab" length 3
start index 2 -> "aba" length 3
We can find these using expand-around-center.
Whenever we discover a palindrome spanning:
[l ... r]
we update:
best[l] = max(best[l], r - l + 1)
We compute:
g1 for s
g2 for reverse(t)
Step 2: Reverse t
Let:
rt = reverse(t)
A palindrome requires mirrored characters.
Matching:
s[i]
rt[j]
The recurrence is:
if s[i] == rt[j]:
lcs[i][j] = 1 + lcs[i+1][j+1]
else:
lcs[i][j] = 0
We compute it from bottom-right to top-left.
Step 4: Use matching pairs as the palindrome boundary
means:
s[i]
t[m-1-j]
are equal, which is exactly the mirror relationship we need.
Step 3: Build longest common suffix DP
Create:
dp[i][j]
where:
dp[i][j]
=
length of longest matching suffix
ending at s[i-1] and rt[j-1]
Transition:
if s[i-1] == rt[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = 0
This is the classic longest common substring DP.
Step 4: Use matching pairs as palindrome arms
Suppose:
k = lcs[i][j]
Then the first k characters form mirrored pairs.
This immediately gives an even palindrome: dp[i][j] = k
Then we have:
k mirrored pairs
contributing:
2 * k
### Step 5: Extend with a center palindrome
After consuming the mirrored pairs, one side may still contain additional characters.
We can place a palindromic substring entirely inside `s` or entirely inside `t` in the center.
Using the precomputed palindrome tables, determine the longest extension possible and update the answer.
### Step 6: Consider palindromes entirely inside one string
The optimal answer might never use both strings.
Therefore we also compare against:
- longest palindromic substring in `s`
- longest palindromic substring in `t`
### Step 7: Return the maximum length found
The maximum over all possibilities is the answer.
### Why it works
The DP over `s` and `reverse(t)` finds every possible block of mirrored character pairs that can appear on opposite sides of a palindrome. Any palindrome using both strings must contain such matching pairs. After fixing those mirrored pairs, the remaining middle portion must itself be a palindrome entirely contained within one string. The palindrome DP precomputes all such possibilities. Since every valid palindrome falls into one of these cases, and every case is examined, the algorithm always returns the optimal length.
characters.
After those mirrored pairs, we can place a center palindrome.
The center may lie inside:
s
or inside:
rt
Therefore we update:
2*k + longest_palindrome_starting_at_next_position_in_s
and
2*k + longest_palindrome_starting_at_next_position_in_rt
### Step 5: Also consider palindromes entirely inside one string
Because either chosen substring may be empty, we must include:
max(g1) max(g2)
in the answer.
### Why it works
Every valid palindrome can be decomposed into mirrored character pairs around a center. The mirrored pairs correspond exactly to a common substring between `s` and `reverse(t)`. The remaining center, if it exists, must be a palindrome entirely inside one string. The DP captures every possible mirrored region, while the palindrome preprocessing captures every possible center. Since every valid construction is represented by one of these combinations, the maximum value produced by the algorithm is the correct answer.
## Python Solution
```python
class Solution:
def longestPalindrome(self, s: str, t: str) -> int:
def longest_pal_info(x: str):
n = len(x)
pal = [[False] * n for _ in range(n)]
best = 1
start_best = [0] * n
end_best = [0] * n
for i in range(n):
pal[i][i] = True
start_best[i] = 1
end_best[i] = 1
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
if x[i] == x[j]:
if length == 2 or pal[i + 1][j - 1]:
pal[i][j] = True
best = max(best, length)
start_best[i] = max(start_best[i], length)
end_best[j] = max(end_best[j], length)
suffix_best = [0] * (n + 1)
for i in range(n - 1, -1, -1):
suffix_best[i] = max(suffix_best[i + 1], start_best[i])
prefix_best = [0] * (n + 1)
for i in range(n):
prefix_best[i + 1] = max(prefix_best[i], end_best[i])
return best, suffix_best, prefix_best
ans_s, suf_s, pre_s = longest_pal_info(s)
ans_t, suf_t, pre_t = longest_pal_info(t)
ans = max(ans_s, ans_t)
rs = t[::-1]
n = len(s)
m = len(t)
dp = [[0] * (m + 1) for _ in range(n + 1)]
for i in range(n - 1, -1, -1):
for j in range(m - 1, -1, -1):
if s[i] == rs[j]:
dp[i][j] = dp[i + 1][j + 1] + 1
k = dp[i][j]
ans = max(ans, 2 * k)
pos_t = m - 1 - j
if i + k < n:
ans = max(ans, 2 * k + suf_s[i + k])
if pos_t - k >= 0:
ans = max(ans, 2 * k + pre_t[pos_t - k + 1])
return ans
Implementation Explanation
The helper function computes all palindromic substrings of a single string. While building the palindrome DP table, it records the longest palindrome beginning at every index and ending at every index.
From those values, two additional arrays are built:
suffix_best[i], longest palindrome starting at or afteri.prefix_best[i], longest palindrome ending before positioni.
The main algorithm first computes the best palindrome entirely inside s and entirely inside t.
Next, t is reversed and a longest common substring DP is constructed. Whenever matching characters are found, dp[i][j] gives the number of mirrored character pairs available.
Each matching block contributes an even palindrome of length 2 * k.
The algorithm then attempts to extend the center using a palindrome entirely inside the unused part of s or the unused part of t. These possibilities are evaluated using the precomputed prefix and suffix arrays.
The maximum value encountered is returned. from typing import List
class Solution: def longestPalindrome(self, s: str, t: str) -> int: def expand(string: str, longest_start: List[int], left: int, right: int) -> None: while ( left >= 0 and right < len(string) and string[left] == string[right] ): longest_start[left] = max( longest_start[left], right - left + 1 ) left -= 1 right += 1
def compute_longest_start(string: str) -> List[int]:
n = len(string)
longest_start = [0] * n
for center in range(n):
expand(string, longest_start, center, center)
expand(string, longest_start, center, center + 1)
return longest_start
m = len(s)
n = len(t)
rt = t[::-1]
g1 = compute_longest_start(s)
g2 = compute_longest_start(rt)
answer = max(max(g1), max(g2))
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if s[i - 1] == rt[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
center_in_s = g1[i] if i < m else 0
center_in_t = g2[j] if j < n else 0
answer = max(
answer,
dp[i][j] * 2 + center_in_s,
dp[i][j] * 2 + center_in_t,
)
return answer
The implementation begins by computing helper arrays that store the longest palindromic substring starting at each index. This is done with the expand-around-center technique because it naturally enumerates all palindromic substrings.
After reversing `t`, the code constructs a longest-common-substring style DP table. Whenever characters match, we extend the previous matching suffix.
Each matching suffix length represents a collection of mirrored pairs that can form the outer layers of a palindrome. The algorithm then checks whether a palindromic center can be attached from either string and updates the answer accordingly.
Finally, the answer already includes palindromes formed entirely inside one string because we initialized it using the largest values in the palindrome-start arrays.
## Go Solution
```go
func longestPalindrome(s string, t string) int {
longestPalInfo := func(x string) (int, []int, []int) {
n := len(x)
pal := make([][]bool, n)
for i := range pal {
pal[i] = make([]bool, n)
}
best := 1
startBest := make([]int, n)
endBest := make([]int, n)
for i := 0; i < n; i++ {
pal[i][i] = true
startBest[i] = 1
endBest[i] = 1
}
for length := 2; length <= n; length++ {
for i := 0; i+length-1 < n; i++ {
j := i + length - 1
if x[i] == x[j] &&
(length == 2 || pal[i+1][j-1]) {
pal[i][j] = true
if length > best {
best = length
}
if length > startBest[i] {
startBest[i] = length
}
if length > endBest[j] {
endBest[j] = length
}
}
}
}
suffixBest := make([]int, n+1)
for i := n - 1; i >= 0; i-- {
if startBest[i] > suffixBest[i+1] {
suffixBest[i] = startBest[i]
} else {
suffixBest[i] = suffixBest[i+1]
}
}
prefixBest := make([]int, n+1)
for i := 0; i < n; i++ {
if prefixBest[i] > endBest[i] {
prefixBest[i+1] = prefixBest[i]
} else {
prefixBest[i+1] = endBest[i]
}
}
return best, suffixBest, prefixBest
}
ansS, sufS, preS := longestPalInfo(s)
ansT, sufT, preT := longestPalInfo(t)
ans := ansS
if ansT > ans {
ans = ansT
}
m := len(t)
rs := make([]byte, m)
for i := 0; i < m; i++ {
rs[i] = t[m-1-i]
}
n := len(s)
dp := make([][]int, n+1)
for i := range dp {
dp[i] = make([]int, m+1)
}
for i := n - 1; i >= 0; i-- {
for j := m - 1; j >= 0; j-- {
if s[i] == rs[j] {
dp[i][j] = dp[i+1][j+1] + 1
k := dp[i][j]
if 2*k > ans {
ans = 2 * k
}
posT := m - 1 - j
if i+k < n {
if 2*k+sufS[i+k] > ans {
ans = 2*k + sufS[i+k]
}
}
if posT-k >= 0 {
if 2*k+preT[posT-k+1] > ans {
ans = 2*k + preT[posT-k+1]
}
}
package main
func longestPalindrome(s string, t string) int {
m := len(s)
n := len(t)
rt := reverse(t)
g1 := computeLongestStart(s)
g2 := computeLongestStart(rt)
ans := max(sliceMax(g1), sliceMax(g2))
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if s[i-1] == rt[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
centerS := 0
centerT := 0
if i < m {
centerS = g1[i]
}
if j < n {
centerT = g2[j]
}
ans = max(ans, dp[i][j]*2+centerS)
ans = max(ans, dp[i][j]*2+centerT)
}
}
}
return ans
}
Go Specific Notes
The Go implementation mirrors the Python solution closely. Since Go does not provide built in dynamic arrays like Python lists, all DP tables are explicitly allocated using slices. Integer overflow is not a concern because the maximum palindrome length is at most 60, far below Go's integer limits.
func computeLongestStart(s string) []int { n := len(s) res := make([]int, n)
for center := 0; center < n; center++ {
expand(s, res, center, center)
expand(s, res, center, center+1)
}
return res
}
func expand(s string, res []int, left int, right int) { for left >= 0 && right < len(s) && s[left] == s[right] {
length := right - left + 1
if length > res[left] {
res[left] = length
}
left--
right++
}
}
func reverse(s string) string { bytes := []byte(s)
for l, r := 0, len(bytes)-1; l < r; l, r = l+1, r-1 {
bytes[l], bytes[r] = bytes[r], bytes[l]
}
return string(bytes)
}
func sliceMax(nums []int) int { best := 0
for _, v := range nums {
if v > best {
best = v
}
}
return best
}
func max(a int, b int) int { if a > b { return a } return b }
The Go implementation follows the exact same logic as the Python version. The main differences are the explicit allocation of the two-dimensional DP table and the use of helper functions for reversing strings and computing maximum values. Integer overflow is not a concern because the maximum answer is at most `60`.
## Worked Examples
### Example 1
s = "a" t = "a"
Reverse `t`:
rt = "a"
LCS table:
| i | j | dp |
| --- | --- | --- |
| 0 | 0 | 1 |
Therefore:
k = 1 answer = 2 * 1 = 2 Palindrome-start arrays:
| Index | g1 |
|---|---|
| 0 | 1 |
| Index | g2 |
|---|---|
| 0 | 1 |
Initial answer:
1
DP:
| i | j | Match | dp[i][j] |
|---|---|---|---|
| 1 | 1 | a=a | 1 |
Contribution:
2 * 1 = 2
Answer becomes:
2
Result:
2
Example 2
s = "abc"
t = "def"
Longest palindrome inside each string: No matching characters exist.
Largest palindrome inside either string:
1
No matching characters exist between s and reverse(t).
LCS values remain zero. The DP table remains all zeros.
Result:
1
Example 3
s = "b"
t = "aaaa"
Longest palindrome inside s:
1
Longest palindrome inside t:
4
Even though cross string matching is possible only for length 1, the palindrome entirely inside t is longer.
Longest palindrome inside t:
"aaaa"
length:
4
No cross-boundary construction improves it.
Result:
4
Example 4
s = "abcde"
t = "ecdba"
Reverse t:
"abdce"
The common substring:
"ab"
produces mirrored pairs:
ab + ba
and the center character:
c
comes from the remaining palindrome inside s.
Constructed palindrome: rt = "abdce"
Matching sequence:
ab
gives:
2 mirrored pairs
Contribution:
2 * 2 = 4
Remaining center in `s`:
"c"
length:
1
Total:
4 + 1 = 5
Palindrome:
abcba
Length:
5
Result:
5
## Complexity Analysis
| Measure | Complexity | Explanation |
| --- | --- | --- |
| Time | O(n² + m² + nm) | Palindrome DP on each string plus common substring DP |
| Space | O(n² + m² + nm) | DP tables for palindromes and common substrings |
The palindrome preprocessing requires quadratic time and space for each string. The longest common substring DP requires `O(nm)` time and space. Since both string lengths are at most 30, these costs are extremely small.
| Time | `O(n² + m² + nm)` | Palindrome preprocessing plus matching DP |
| Space | `O(nm)` | DP table for matching suffix lengths |
The palindrome preprocessing uses expand-around-center. For a string of length `n`, every center can expand at most `O(n)` positions, resulting in `O(n²)` time. We perform this once for `s` and once for `t`.
The matching DP table contains `(n+1)(m+1)` cells and each cell is processed in constant time, producing `O(nm)` work.
The dominant memory usage comes from the DP table, which requires `O(nm)` space.
## Test Cases
sol = Solution()
assert sol.longestPalindrome("a", "a") == 2 # identical single characters assert sol.longestPalindrome("abc", "def") == 1 # no matching characters assert sol.longestPalindrome("b", "aaaa") == 4 # palindrome entirely in t assert sol.longestPalindrome("abcde", "ecdba") == 5 # sample cross-string palindrome
assert sol.longestPalindrome("a", "b") == 1 # smallest non-matching case assert sol.longestPalindrome("aa", "aa") == 4 # full usage of both strings assert sol.longestPalindrome("racecar", "x") == 7 # palindrome entirely in s assert sol.longestPalindrome("x", "racecar") == 7 # palindrome entirely in t
assert sol.longestPalindrome("ab", "ba") == 4 # even palindrome across strings assert sol.longestPalindrome("abc", "cba") == 6 # complete mirrored strings
assert sol.longestPalindrome("aaaa", "aaaa") == 8 # repeated characters assert sol.longestPalindrome("abcdef", "fedcba") == 12 # maximum mirror usage
assert sol.longestPalindrome("abca", "xyz") == 1 # no useful extension assert sol.longestPalindrome("abc", "ba") == 3 # odd-length cross palindrome assert sol.longestPalindrome("a", "a") == 2 # simplest cross-string palindrome assert sol.longestPalindrome("abc", "def") == 1 # no matching characters assert sol.longestPalindrome("b", "aaaa") == 4 # palindrome entirely in t assert sol.longestPalindrome("abcde", "ecdba") == 5 # palindrome crossing boundary
assert sol.longestPalindrome("aaaa", "aaaa") == 8 # entire strings form palindrome assert sol.longestPalindrome("a", "bcdef") == 1 # only single-character palindrome assert sol.longestPalindrome("aba", "x") == 3 # palindrome entirely in s assert sol.longestPalindrome("x", "aba") == 3 # palindrome entirely in t
assert sol.longestPalindrome("ab", "ba") == 4 # "abba" assert sol.longestPalindrome("abc", "cba") == 6 # full mirrored match
assert sol.longestPalindrome("race", "car") == 7 # "racecar" assert sol.longestPalindrome("aaab", "baaa") == 8 # large mirrored construction
assert sol.longestPalindrome("z", "z") == 2 # smallest even palindrome assert sol.longestPalindrome("abcd", "dcba") == 8 # complete palindrome across strings
### Test Summary
| Test | Why |
| --- | --- |
| `("a","a")` | Smallest even palindrome |
| `("abc","def")` | No matching characters |
| `("b","aaaa")` | Best answer entirely in `t` |
| `("abcde","ecdba")` | Official mixed-string example |
| `("a","b")` | Minimum non-matching input |
| `("aa","aa")` | Full concatenation palindrome |
| `("racecar","x")` | Best answer entirely in `s` |
| `("x","racecar")` | Symmetric case for `t` |
| `("ab","ba")` | Even-length cross palindrome |
| `("abc","cba")` | Entire strings form palindrome |
| `("aaaa","aaaa")` | Many repeated characters |
| `("abcdef","fedcba")` | Large mirrored structure |
| `("abca","xyz")` | No cross contribution |
| `("abc","ba")` | Odd-length palindrome using both strings |
## Edge Cases
### The optimal palindrome uses only one string
A common mistake is assuming the answer must use characters from both strings. Consider:
| `("a", "a")` | Smallest nontrivial palindrome |
| `("abc", "def")` | No cross-string matches |
| `("b", "aaaa")` | Best answer entirely inside `t` |
| `("abcde", "ecdba")` | Official mixed-string example |
| `("aaaa", "aaaa")` | Heavy repetition |
| `("a", "bcdef")` | Single-character fallback |
| `("aba", "x")` | Best palindrome entirely in `s` |
| `("x", "aba")` | Best palindrome entirely in `t` |
| `("ab", "ba")` | Even-length cross palindrome |
| `("abc", "cba")` | Full mirror across strings |
| `("race", "car")` | Classic odd-length palindrome |
| `("aaab", "baaa")` | Large mirrored arms |
| `("z", "z")` | Minimum input size |
| `("abcd", "dcba")` | Entire concatenation is a palindrome |
## Edge Cases
One important edge case occurs when there are no matching characters between the two strings. A solution that only searches for palindromes crossing the concatenation boundary would incorrectly return `0`. In reality, we are allowed to choose an empty substring from one side, so the answer must be the longest palindromic substring contained entirely within either string. The initialization using `max(g1)` and `max(g2)` handles this correctly.
Another subtle edge case is an odd-length palindrome whose center lies entirely inside one string. For example:
s = "abc" t = "ba"
The optimal palindrome is:
abcba
The character `'c'` acts as the center. A solution that only counts mirrored pairs would produce length `4` instead of `5`. The precomputed palindrome-start arrays allow the algorithm to attach such a center and obtain the correct answer.
A third edge case appears when the entire palindrome comes from a single string, such as:
s = "b" t = "aaaa"
The best palindrome is simply `"aaaa"`, with length 4. The implementation explicitly computes the longest palindromic substring within each string before considering cross-string constructions.
### No matching characters between the strings
Consider:
s = "abc" t = "def"
No mirrored pairs can be formed across the two strings. The longest common substring DP produces only zeros, and the algorithm correctly falls back to the longest single-character palindrome.
### Odd-length palindromes
Palindromes do not always have even length. For example:
abc + ba = abcba
The outer mirrored pairs come from both strings, while the center character comes from a palindrome entirely contained in one string. The extension logic using the prefix and suffix palindrome arrays correctly handles these odd-length cases.
### Repeated characters
Strings such as:
s = "aaaa" t = "aaaa"
contain many overlapping palindromic substrings. A naive implementation can easily double count or miss valid choices. The dynamic programming formulation systematically evaluates every valid matching segment and therefore handles repeated characters correctly.
The optimal answer is `4`, not `2`. Any implementation that assumes both strings must contribute characters would fail here. Since the algorithm separately tracks the longest palindromic substrings in each string before considering cross-boundary constructions, it correctly returns `4`.