Longest Palindrome After Substring Concatenation II
HardPrompt
You are given two strings, s and t.
You can create a new string by selecting a substring from s (possibly empty) and a substring from t (possibly empty), then concatenating them in order.
Return the length of the longest palindrome that can be formed this way.
Example 1:
Input: s = "a", t = "a"
Output: 2
Explanation:
Concatenating "a" from s and "a" from t results in "aa", which is a palindrome of length 2.
Example 2:
Input: s = "abc", t = "def"
Output: 1
Explanation:
Since all characters are different, the longest palindrome is any single character, so the answer is 1.
Example 3:
Input: s = "b", t = "aaaa"
Output: 4
Explanation:
Selecting "aaaa" from t is the longest palindrome, so the answer is 4.
Example 4:
Input: s = "abcde", t = "ecdba"
Output: 5
Explanation:
Concatenating "abc" from s and "ba" from t results in "abcba", which is a palindrome of length 5.
Constraints:
1 <= s.length, t.length <= 1000sandtconsist of lowercase English letters.
Approaches
2 approaches with complexity analysis and trade-offs.
The most straightforward approach is to exhaustively check every possible palindrome that can be formed. We can iterate through all substrings of s and all substrings of t, concatenate them, and check if the resulting string is a palindrome. We keep track of the maximum length found.
Algorithm
- Initialize
maxLengthto 0. - Generate all possible non-empty substrings of
s. Let a substring besub_s. - Generate all possible non-empty substrings of
t. Let a substring besub_t. - For each pair of
(sub_s, sub_t):- Create the concatenated string
newStr = sub_s + sub_t. - Check if
newStris a palindrome. - If it is, update
maxLength = max(maxLength, newStr.length()).
- Create the concatenated string
- Also consider cases where one of the substrings is empty. This means finding the longest palindromic substring within
sandtindividually and updatingmaxLength. - Return
maxLength.
Walkthrough
This method involves a nested loop structure to generate all substrings and then test them.
- Generate Substrings: We use four nested loops to define the start and end indices for substrings from both
sandt. - Concatenate and Test: For each pair of substrings,
sub_sandsub_t, we form a new string. A helper function is used to check if this new string is a palindrome. This check can be done by comparing the string with its reverse. - Handle Empty Substrings: The problem allows for empty substrings. This is equivalent to finding the longest palindromic substring within
sortalone. This should be done as a base case.
Here is a conceptual code snippet for the core logic:
public int longestPalindrome(String s, String t) { int n = s.length(); int m = t.length(); int maxLen = 0; // Helper to find longest palindromic substring in a single string // maxLen = Math.max(lps(s), lps(t)); for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { String sub_s = s.substring(i, j + 1); for (int k = 0; k < m; k++) { for (int l = k; l < m; l++) { String sub_t = t.substring(k, l + 1); String combined = sub_s + sub_t; if (isPalindrome(combined)) { maxLen = Math.max(maxLen, combined.length()); } } } } } return maxLen;} private boolean isPalindrome(String str) { int left = 0, right = str.length() - 1; while (left < right) { if (str.charAt(left) != str.charAt(right)) { return false; } left++; right--; } return true;}Complexity
Time
O(N² * M² * (N + M)). There are O(N²) substrings in `s` and O(M²) in `t`. For each pair, concatenation takes O(N+M) and the palindrome check takes O(N+M). This is computationally prohibitive.
Space
O(N + M), where N and M are the lengths of `s` and `t`. This space is used to store the concatenated string.
Trade-offs
Pros
Simple to understand and implement.
Cons
Extremely inefficient due to the large number of substring pairs.
The time complexity makes it infeasible for the given constraints.
A more efficient approach recognizes the structure of the final palindrome. Any such palindrome P formed by sub_s + sub_t can be deconstructed into L + M + reverse(L), where M is itself a palindrome (and can be empty). The parts L and reverse(L) must come from different strings, while M must be a substring of one of them, adjacent to its L part.
This structure means we are looking for a string L that is a substring of s, while reverse(L) is a substring of t (or vice-versa). This is equivalent to finding a common substring between s and reverse(t). We can use dynamic programming to find all such common substrings and then, for each, find the longest possible central palindrome M to form the complete palindrome.
Algorithm
- Base Cases: Find the length of the longest palindromic substring within
sandtseparately. InitializemaxLenwith the maximum of these two lengths. - Palindrome Precomputation: For both
sandt, precompute information about palindromic substrings. Specifically, for each indexi, find the length of the longest palindrome that starts atiand the length of the longest palindrome that ends ati. This can be done in O(N²) and O(M²) respectively. - Reverse
t: Create a reversed version oft, let's call itt_rev. - Find Common Substrings: Use dynamic programming to find all common substrings between
sandt_rev. Letdp[i][j]be the length of the common suffix ofs.substring(0, i)andt_rev.substring(0, j). - Combine Parts: Iterate through the
dptable. Ifdp[i][j] > 0, it means we've found a common substringLof lengthdp[i][j]betweensandt_rev. ThisLis a substring ofs, andreverse(L)is a substring oft.- Case 1 (
sub_s = L+M, sub_t = reverse(L)): The palindrome is formed byLfroms, followed by a palindromic partMalso froms, andreverse(L)fromt. The length is2*len(L) + len(M). We use the precomputed data to find the length of the longest palindromeMstarting immediately afterLins. - Case 2 (
sub_s = L, sub_t = M+reverse(L)): The palindrome is formed byLfroms,reverse(L)fromt, and a palindromic partMfromtprecedingreverse(L). The length is2*len(L) + len(M). We use precomputed data fort.
- Case 1 (
- Update
maxLenwith the maximum length found from these cases and return it.
Walkthrough
This approach can be broken down into several steps:
-
Handle Base Cases: First, we find the longest palindrome that can be formed using a substring from only
sor onlyt. This covers the cases where one of the selected substrings is empty. -
Precompute Palindrome Lengths: We need to quickly query the length of the longest palindrome starting or ending at any given index. We can precompute this information for both
sandtin O(N²) and O(M²) time respectively.// Helper to compute palindrome info. isPal[i][j] is true if s[i..j] is a palindrome.private boolean[][] computeIsPalindrome(String s) { ... } // Helper to get lengths of palindromes starting or ending at each index from the isPal table.private int[] getPalLengths(String s, boolean starts, boolean[][] isPal) { ... } -
Find Common Substrings using DP: We find common substrings between
sandreverse(t). Lett_rev = reverse(t). We build a DP table wheredp[i][j]stores the length of the longest common substring ending ats[i-1]andt_rev[j-1]. -
Construct Palindromes and Find Max Length: We iterate through the DP table. A non-zero
dp[i][j]gives us a matching partL. We then check the two possible ways to form a larger palindrome:Lfroms,Mfroms,reverse(L)fromt.Lfroms,reverse(L)fromt,Mfromt.
We use our precomputed palindrome lengths to find the longest possible M in each case and update our maximum length.
class Solution { public int longestPalindrome(String s, String t) { // Step 1: Base cases (palindrome entirely in s or t) int maxLen = 0; boolean[][] isPalS = computeIsPalindrome(s); boolean[][] isPalT = computeIsPalindrome(t); maxLen = Math.max(findLongestPal(s, isPalS), findLongestPal(t, isPalT)); // Step 2: Precompute palindrome lengths int[] palLenSStarts = getPalLengths(s, true, isPalS); int[] palLenTEnds = getPalLengths(t, false, isPalT); // Step 3 & 4: Find common parts and combine String tRev = new StringBuilder(t).reverse().toString(); int n = s.length(), m = t.length(); int[][] dp = new int[n + 1][m + 1]; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (s.charAt(i - 1) == tRev.charAt(j - 1)) { int lenL = dp[i - 1][j - 1] + 1; dp[i][j] = lenL; // Case A: sub_s = L+M, sub_t = reverse(L) int lenM_s = (i < n) ? palLenSStarts[i] : 0; maxLen = Math.max(maxLen, 2 * lenL + lenM_s); // Case B: sub_s = L, sub_t = M + reverse(L) int t_idx = m - j - 1; int lenM_t = (t_idx >= 0) ? palLenTEnds[t_idx] : 0; maxLen = Math.max(maxLen, 2 * lenL + lenM_t); } } } return maxLen; } // ... (helper methods for palindrome computation)}Complexity
Time
O(N² + M²). Precomputing palindrome information for `s` and `t` takes O(N²) and O(M²) respectively. The DP table for common substrings is filled in O(N*M). The total complexity is the sum of these parts.
Space
O(N² + M²). The space is dominated by the tables for precomputing palindrome information (`isPalS`, `isPalT`) and the DP table for common substrings (`dp`), which is O(N*M).
Trade-offs
Pros
Significantly more efficient than the brute-force approach.
Correctly solves the problem within the given time limits.
The approach is systematic and covers all possible palindrome constructions.
Cons
More complex to implement compared to the brute-force approach.
Requires significant auxiliary space for DP tables and precomputed palindrome information.
Solutions
Solution
class Solution {public int longestPalindrome(String S, String T) { char[] s = S.toCharArray(); char[] t = new StringBuilder(T).reverse().toString().toCharArray(); int m = s.length, n = t.length; int[] g1 = calc(s), g2 = calc(t); int ans = Math.max(Arrays.stream(g1).max().getAsInt(), Arrays.stream(g2).max().getAsInt()); int[][] f = new int[m + 1][n + 1]; for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { if (s[i - 1] == t[j - 1]) { f[i][j] = f[i - 1][j - 1] + 1; ans = Math.max(ans, f[i][j] * 2 + (i < m ? g1[i] : 0)); ans = Math.max(ans, f[i][j] * 2 + (j < n ? g2[j] : 0)); } } } return ans; }private void expand(char[] s, int[] g, int l, int r) { while (l >= 0 && r < s.length && s[l] == s[r]) { g[l] = Math.max(g[l], r - l + 1); --l; ++r; } }private int[] calc(char[] s) { int n = s.length; int[] g = new int[n]; for (int i = 0; i < n; ++i) { expand(s, g, i, i); expand(s, g, i, i + 1); } return g; }}class Solution {public: int longestPalindrome(string s, string t) { int m = s.size(), n = t.size(); ranges ::reverse(t); vector<int> g1 = calc(s), g2 = calc(t); int ans = max(ranges ::max(g1), ranges ::max(g2)); vector<vector<int>> f(m + 1, vector<int>(n + 1)); for (int i = 1; i <= m; ++i) { for (int j = 1; j <= n; ++j) { if (s[i - 1] == t[j - 1]) { f[i][j] = f[i - 1][j - 1] + 1; ans = max(ans, f[i][j] * 2 + (i < m ? g1[i] : 0)); ans = max(ans, f[i][j] * 2 + (j < n ? g2[j] : 0)); } } } return ans; } private: void expand(const string &s, vector<int> &g, int l, int r) { while (l >= 0 && r < s.size() && s[l] == s[r]) { g[l] = max(g[l], r - l + 1); --l; ++r; } } vector<int> calc(const string &s) { int n = s.size(); vector<int> g(n, 0); for (int i = 0; i < n; ++i) { expand(s, g, i, i); expand(s, g, i, i + 1); } return g; }};class Solution: def longestPalindrome(self, s: str, t: str) -> int: def expand(s: str, g: List[int], l: int, r: int): while l >= 0 and r < len(s) and s[l] == s[r]: g[l] = max(g[l], r - l + 1) l, r = l - 1, r + 1 def calc(s: str) -> List[int]: n = len(s) g = [0] * n for i in range(n): expand(s, g, i, i) expand(s, g, i, i + 1) return g m, n = len(s), len(t) t = t[:: - 1] g1, g2 = calc(s), calc(t) ans = max(* g1, * g2) f = [[0] * (n + 1) for _ in range(m + 1)] for i, a in enumerate(s, 1): for j, b in enumerate(t, 1): if a == b: f[i][j] = f[i - 1][j - 1] + 1 ans = max(ans, f[i][j] * 2 + (0 if i >= m else g1[i])) ans = max(ans, f[i][j] * 2 + (0 if j >= n else g2[j])) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.