1771. Maximize Palindrome Length From Subsequences
Problem Description
You are given two strings word1
and word2
. Your task is to construct a palindrome by:
- Selecting a non-empty subsequence from
word1
(let's call itsubsequence1
) - Selecting a non-empty subsequence from
word2
(let's call itsubsequence2
) - Concatenating them as
subsequence1 + subsequence2
The goal is to find the length of the longest palindrome that can be formed this way. If no palindrome can be constructed, return 0
.
Key definitions:
- A subsequence is formed by deleting some (possibly zero) characters from the original string without changing the order of remaining characters
- A palindrome reads the same forwards and backwards
For example, if word1 = "cacb"
and word2 = "cbba"
:
- You could take subsequence "ab" from
word1
and subsequence "ba" fromword2
- Concatenating gives "abba", which is a palindrome of length 4
The constraint that makes this problem interesting is that you must use at least one character from each word - you cannot form the palindrome using only characters from a single word.
Intuition
The key insight is to recognize that when we concatenate subsequence1
from word1
and subsequence2
from word2
, we're essentially looking for a palindrome within the combined string word1 + word2
.
Think about what makes a valid palindrome from two subsequences: characters from word1
must match with characters from word2
(or within the same word) in a palindromic pattern. For the palindrome to be valid according to our constraints, at least one character from word1
must be paired with at least one character from word2
.
This naturally leads us to consider the classic "Longest Palindromic Subsequence" problem on the concatenated string s = word1 + word2
. The standard dynamic programming solution for finding the longest palindromic subsequence can be adapted here.
The crucial modification is ensuring our palindrome spans both words. When we find matching characters s[i] == s[j]
that contribute to our palindrome, we need to check if one character comes from word1
(index i < len(word1)
) and the other comes from word2
(index j >= len(word1)
). Only such palindromes are valid answers to our problem.
Why does this work? Because in a palindrome, characters are mirrored around the center. If we have characters from both words participating in this mirroring (one from the left part coming from word1
, one from the right part coming from word2
), we guarantee that both subsequences are non-empty.
The DP table f[i][j]
tracks the longest palindromic subsequence in the range [i, j]
of the concatenated string, and we only update our answer when we find a palindrome that satisfies our cross-word constraint.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement the solution using dynamic programming on the concatenated string s = word1 + word2
.
Step 1: Setup
- Concatenate both strings:
s = word1 + word2
- Create a 2D DP table
f[i][j]
wheref[i][j]
represents the length of the longest palindromic subsequence in the substrings[i...j]
- Initialize the diagonal:
f[i][i] = 1
for alli
, since each single character is a palindrome of length 1
Step 2: Fill the DP Table
We iterate through the string in a specific order - starting from the bottom-right and moving towards the top-left. For each pair of indices (i, j)
where i < j
:
-
Case 1: Characters match (
s[i] == s[j]
)- These characters can form the outer boundaries of our palindrome
- Update:
f[i][j] = f[i + 1][j - 1] + 2
- Check if this palindrome spans both words: if
i < len(word1) <= j
- This means character at index
i
is fromword1
and character at indexj
is fromword2
- Update answer:
ans = max(ans, f[i][j])
- This means character at index
-
Case 2: Characters don't match (
s[i] != s[j]
)- We can't include both characters in our palindrome
- Take the maximum of excluding either character:
- Update:
f[i][j] = max(f[i + 1][j], f[i][j - 1])
Step 3: Return Result
The variable ans
tracks the maximum length of a valid palindrome that uses characters from both words.
Why the iteration order matters:
We iterate i
from n-2
down to 0
and j
from i+1
to n-1
. This ensures that when we compute f[i][j]
, the subproblems f[i+1][j-1]
, f[i+1][j]
, and f[i][j-1]
have already been solved.
Time Complexity: O(n²)
where n = len(word1) + len(word2)
Space Complexity: O(n²)
for the DP table
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with word1 = "ac"
and word2 = "ca"
.
Step 1: Setup
- Concatenate:
s = "ac" + "ca" = "acca"
- Length:
n = 4
- Initialize DP table
f[i][j]
(4×4 matrix) - Set diagonal:
f[0][0] = f[1][1] = f[2][2] = f[3][3] = 1
- Track
ans = 0
for our result
Step 2: Fill DP Table
We iterate with i
from 2 down to 0, and for each i
, iterate j
from i+1
to 3:
i = 2, j = 3:
s[2] = 'c'
,s[3] = 'a'
→ characters don't matchf[2][3] = max(f[3][3], f[2][2]) = max(1, 1) = 1
i = 1:
-
j = 2:
s[1] = 'c'
,s[2] = 'c'
→ characters match!f[1][2] = f[2][1] + 2 = 0 + 2 = 2
(base case: empty range)- Check spanning:
1 < 2 ≤ 2
✓ (index 1 is from word1, index 2 is from word2) - Update
ans = 2
-
j = 3:
s[1] = 'c'
,s[3] = 'a'
→ characters don't matchf[1][3] = max(f[2][3], f[1][2]) = max(1, 2) = 2
i = 0:
-
j = 1:
s[0] = 'a'
,s[1] = 'c'
→ characters don't matchf[0][1] = max(f[1][1], f[0][0]) = max(1, 1) = 1
-
j = 2:
s[0] = 'a'
,s[2] = 'c'
→ characters don't matchf[0][2] = max(f[1][2], f[0][1]) = max(2, 1) = 2
-
j = 3:
s[0] = 'a'
,s[3] = 'a'
→ characters match!f[0][3] = f[1][2] + 2 = 2 + 2 = 4
- Check spanning:
0 < 2 ≤ 3
✓ (index 0 is from word1, index 3 is from word2) - Update
ans = 4
Step 3: Result The answer is 4, representing the palindrome "acca" formed by:
- Taking subsequence "ac" from word1 (the entire string)
- Taking subsequence "ca" from word2 (the entire string)
- Concatenating to get "acca", which is a palindrome
The DP table helped us identify that characters at positions 0 and 3 form a matching pair (both 'a'), and characters at positions 1 and 2 form another matching pair (both 'c'), creating our palindrome that spans both original words.
Solution Implementation
1class Solution:
2 def longestPalindrome(self, word1: str, word2: str) -> int:
3 # Concatenate both words to form a single string
4 combined_string = word1 + word2
5 n = len(combined_string)
6
7 # dp[i][j] represents the length of longest palindromic subsequence
8 # in combined_string[i:j+1]
9 dp = [[0] * n for _ in range(n)]
10
11 # Base case: single character is a palindrome of length 1
12 for i in range(n):
13 dp[i][i] = 1
14
15 # Variable to store the maximum palindrome length that satisfies the condition
16 max_palindrome_length = 0
17
18 # Fill the dp table bottom-up (from shorter to longer substrings)
19 for i in range(n - 2, -1, -1): # Start from second last character
20 for j in range(i + 1, n): # End position after start
21 if combined_string[i] == combined_string[j]:
22 # If characters match, add 2 to the inner subsequence length
23 dp[i][j] = dp[i + 1][j - 1] + 2
24
25 # Check if this palindrome uses characters from both words
26 # i must be in word1 (i < len(word1)) and j must be in word2 (j >= len(word1))
27 if i < len(word1) <= j:
28 max_palindrome_length = max(max_palindrome_length, dp[i][j])
29 else:
30 # If characters don't match, take maximum of excluding either character
31 dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
32
33 return max_palindrome_length
34
1class Solution {
2 public int longestPalindrome(String word1, String word2) {
3 // Concatenate both words to form a single string
4 String combinedString = word1 + word2;
5 int totalLength = combinedString.length();
6
7 // dp[i][j] represents the length of longest palindromic subsequence
8 // in substring from index i to j (inclusive)
9 int[][] dp = new int[totalLength][totalLength];
10
11 // Initialize base case: single character is a palindrome of length 1
12 for (int i = 0; i < totalLength; i++) {
13 dp[i][i] = 1;
14 }
15
16 // Track the maximum length of valid palindrome
17 // (must use at least one character from each word)
18 int maxPalindromeLength = 0;
19
20 // Fill the dp table in bottom-up manner
21 // Start from smaller substrings and build up to larger ones
22 for (int startIdx = totalLength - 2; startIdx >= 0; startIdx--) {
23 for (int endIdx = startIdx + 1; endIdx < totalLength; endIdx++) {
24
25 if (combinedString.charAt(startIdx) == combinedString.charAt(endIdx)) {
26 // Characters at both ends match, add 2 to inner substring's palindrome length
27 dp[startIdx][endIdx] = dp[startIdx + 1][endIdx - 1] + 2;
28
29 // Check if this palindrome uses characters from both words
30 // startIdx must be in word1 (< word1.length())
31 // endIdx must be in word2 (>= word1.length())
32 if (startIdx < word1.length() && endIdx >= word1.length()) {
33 maxPalindromeLength = Math.max(maxPalindromeLength, dp[startIdx][endIdx]);
34 }
35 } else {
36 // Characters don't match, take maximum of excluding either end
37 dp[startIdx][endIdx] = Math.max(dp[startIdx + 1][endIdx],
38 dp[startIdx][endIdx - 1]);
39 }
40 }
41 }
42
43 return maxPalindromeLength;
44 }
45}
46
1class Solution {
2public:
3 int longestPalindrome(string word1, string word2) {
4 // Concatenate both words to form a single string
5 string combinedString = word1 + word2;
6 int totalLength = combinedString.size();
7
8 // Dynamic programming table
9 // dp[i][j] represents the length of longest palindrome subsequence
10 // in combinedString from index i to j (inclusive)
11 int dp[totalLength][totalLength];
12 memset(dp, 0, sizeof(dp));
13
14 // Base case: single character is a palindrome of length 1
15 for (int i = 0; i < totalLength; ++i) {
16 dp[i][i] = 1;
17 }
18
19 int maxPalindromeLength = 0;
20
21 // Fill the DP table bottom-up
22 // Start from the second last character and move backwards
23 for (int startIdx = totalLength - 2; startIdx >= 0; --startIdx) {
24 // For each starting position, check all ending positions after it
25 for (int endIdx = startIdx + 1; endIdx < totalLength; ++endIdx) {
26
27 if (combinedString[startIdx] == combinedString[endIdx]) {
28 // If characters match, extend the palindrome
29 dp[startIdx][endIdx] = dp[startIdx + 1][endIdx - 1] + 2;
30
31 // Check if this palindrome spans across both words
32 // startIdx must be in word1 and endIdx must be in word2
33 if (startIdx < word1.size() && endIdx >= word1.size()) {
34 maxPalindromeLength = max(maxPalindromeLength, dp[startIdx][endIdx]);
35 }
36 } else {
37 // If characters don't match, take the maximum of:
38 // 1. Palindrome excluding current start character
39 // 2. Palindrome excluding current end character
40 dp[startIdx][endIdx] = max(dp[startIdx + 1][endIdx], dp[startIdx][endIdx - 1]);
41 }
42 }
43 }
44
45 return maxPalindromeLength;
46 }
47};
48
1/**
2 * Finds the longest palindrome that can be formed by concatenating word1 and word2,
3 * with at least one character from each word.
4 * @param word1 - The first word
5 * @param word2 - The second word
6 * @returns The length of the longest palindrome
7 */
8function longestPalindrome(word1: string, word2: string): number {
9 // Concatenate both words
10 const combinedString: string = word1 + word2;
11 const totalLength: number = combinedString.length;
12
13 // Initialize DP table where dp[i][j] represents the length of longest palindrome
14 // in substring from index i to j
15 const dp: number[][] = Array.from(
16 { length: totalLength },
17 () => Array.from({ length: totalLength }, () => 0)
18 );
19
20 // Base case: single characters are palindromes of length 1
21 for (let i = 0; i < totalLength; i++) {
22 dp[i][i] = 1;
23 }
24
25 // Track the maximum palindrome length that uses characters from both words
26 let maxPalindromeLength: number = 0;
27
28 // Fill the DP table bottom-up
29 // Start from the second last position and move backwards
30 for (let startIndex = totalLength - 2; startIndex >= 0; startIndex--) {
31 for (let endIndex = startIndex + 1; endIndex < totalLength; endIndex++) {
32 // If characters at both ends match
33 if (combinedString[startIndex] === combinedString[endIndex]) {
34 // Add 2 to the palindrome length of the inner substring
35 dp[startIndex][endIndex] = dp[startIndex + 1][endIndex - 1] + 2;
36
37 // Check if this palindrome spans across both words
38 // startIndex should be in word1 and endIndex should be in word2
39 if (startIndex < word1.length && endIndex >= word1.length) {
40 maxPalindromeLength = Math.max(maxPalindromeLength, dp[startIndex][endIndex]);
41 }
42 } else {
43 // If characters don't match, take the maximum of excluding either end
44 dp[startIndex][endIndex] = Math.max(
45 dp[startIndex + 1][endIndex], // Exclude character at startIndex
46 dp[startIndex][endIndex - 1] // Exclude character at endIndex
47 );
48 }
49 }
50 }
51
52 return maxPalindromeLength;
53}
54
Time and Space Complexity
The time complexity is O(n^2)
, where n = len(word1) + len(word2)
is the total length of the concatenated string s
. This is because we have two nested loops: the outer loop iterates from n-2
to 0
(which is O(n)
iterations), and for each iteration, the inner loop iterates from i+1
to n-1
(which can be up to O(n)
iterations). Therefore, the total number of operations is proportional to n^2
.
The space complexity is O(n^2)
, where n = len(word1) + len(word2)
. This is due to the 2D dynamic programming table f
which has dimensions n × n
, requiring n^2
space to store all the intermediate results for the longest palindromic subsequence calculations.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Boundary Check for Valid Palindromes
The Problem: A common mistake is incorrectly checking whether a palindrome spans both words. Developers might write:
if i < len(word1) and j >= len(word1): # Wrong!
This condition doesn't guarantee that characters at positions i
and j
are actually from different words. The character at position i
could be from word1
, but the character at position j
might not be the matching character that forms the palindrome boundary.
Why It Happens:
The confusion arises because when s[i] == s[j]
, we know these characters form the outer boundary of our palindrome. However, the palindrome might include many characters between them, and we need to ensure the actual matching pair crosses the word boundary.
The Correct Approach:
if i < len(word1) <= j: # Correct!
This simpler condition works because:
- When
s[i] == s[j]
and we're adding them as palindrome boundaries - If
i < len(word1)
, thens[i]
is fromword1
- If
j >= len(word1)
, thens[j]
is fromword2
- Together, this ensures our palindrome uses at least one character from each word
Pitfall 2: Forgetting to Initialize the DP Table Diagonal
The Problem:
Omitting the initialization of dp[i][i] = 1
leads to incorrect results:
# Missing initialization - Wrong!
dp = [[0] * n for _ in range(n)]
# Directly starting the nested loops...
Why It Matters:
- Single characters are palindromes of length 1
- The recurrence relation
dp[i][j] = dp[i+1][j-1] + 2
depends on these base cases - Without proper initialization, when
i+1 == j
(adjacent characters that match), we'd getdp[i][j] = 0 + 2 = 2
, which is correct by coincidence - But for longer sequences, the calculations cascade incorrectly
The Fix: Always initialize the diagonal before filling the rest of the table:
for i in range(n):
dp[i][i] = 1
Pitfall 3: Wrong Iteration Order
The Problem: Iterating in the wrong order causes accessing uncomputed values:
# Wrong iteration order!
for i in range(n):
for j in range(i + 1, n):
# dp[i+1][j-1] might not be computed yet!
if s[i] == s[j]:
dp[i][j] = dp[i + 1][j - 1] + 2
Why It Fails: The recurrence depends on:
dp[i+1][j-1]
(smaller substring, one character removed from each end)dp[i+1][j]
(substring without first character)dp[i][j-1]
(substring without last character)
These subproblems must be solved before computing dp[i][j]
.
The Correct Order:
for i in range(n - 2, -1, -1): # Iterate i backwards
for j in range(i + 1, n): # Iterate j forwards
# Now all required subproblems are already computed
This ensures we compute smaller substrings before larger ones.
Given a sorted array of integers and an integer called target, find the element that
equals to the target and return its index. Select the correct code that fills the
___
in the given code snippet.
1def binary_search(arr, target):
2 left, right = 0, len(arr) - 1
3 while left ___ right:
4 mid = (left + right) // 2
5 if arr[mid] == target:
6 return mid
7 if arr[mid] < target:
8 ___ = mid + 1
9 else:
10 ___ = mid - 1
11 return -1
12
1public static int binarySearch(int[] arr, int target) {
2 int left = 0;
3 int right = arr.length - 1;
4
5 while (left ___ right) {
6 int mid = left + (right - left) / 2;
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
1function binarySearch(arr, target) {
2 let left = 0;
3 let right = arr.length - 1;
4
5 while (left ___ right) {
6 let mid = left + Math.trunc((right - left) / 2);
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!