1616. Split Two Strings to Make Palindrome
Problem Description
You are given two strings a
and b
that have the same length. Your task is to determine if you can form a palindrome by splitting both strings at the same index and combining parts from each string.
Here's how the splitting works:
- Choose any index (including 0 or the length of the strings) to split both strings at the same position
- String
a
gets split intoa_prefix
anda_suffix
wherea = a_prefix + a_suffix
- String
b
gets split intob_prefix
andb_suffix
whereb = b_prefix + b_suffix
- Either part (prefix or suffix) can be empty
After splitting, you need to check if either of these two combinations forms a palindrome:
a_prefix + b_suffix
b_prefix + a_suffix
The function should return true
if it's possible to form a palindrome using any valid split, otherwise return false
.
For example, if s = "abc"
, valid splits include:
"" + "abc"
(split at index 0)"a" + "bc"
(split at index 1)"ab" + "c"
(split at index 2)"abc" + ""
(split at index 3)
The solution uses a two-pointer approach where pointers start from opposite ends of the two strings. One pointer i
starts from the beginning of one string while pointer j
starts from the end of the other string. The pointers move towards the center as long as the characters match. If they cross (meaning i >= j
), a palindrome can be formed. If they stop before crossing due to a mismatch, the algorithm checks if the remaining middle portion from either string forms a palindrome by itself. The process is repeated by swapping the roles of strings a
and b
.
Intuition
The key insight is that for a string to be a palindrome, characters at symmetric positions from both ends must match. When we combine a_prefix + b_suffix
to form a palindrome, the beginning of a_prefix
must match with the end of b_suffix
in reverse order.
Let's think about what happens when we try to form a palindrome from a_prefix + b_suffix
:
- The first character of
a
(at index 0) should equal the last character ofb
(at index n-1) - The second character of
a
(at index 1) should equal the second-to-last character ofb
(at index n-2) - This pattern continues...
We can check this condition using two pointers moving towards each other. Start with pointer i
at the beginning of string a
and pointer j
at the end of string b
. As long as a[i] == b[j]
, we know these characters will be in correct palindromic positions in the final string, so we can move both pointers inward (i++
and j--
).
At some point, we'll encounter one of two scenarios:
-
The pointers cross (
i >= j
): This means we've successfully matched all necessary characters from the outside-in. The remaining portion (if any) is already in the middle of our combined string and will naturally form a palindrome. -
The pointers stop due to a mismatch (
a[i] != b[j]
): Now we have a middle section that hasn't been validated. However, this middle section comes entirely from one string - eithera[i...j]
orb[i...j]
. If either of these substrings is already a palindrome by itself, then our combined string will also be a palindrome.
Why does checking the middle substring from either string work? Because once we've verified the outer portions match correctly, we just need the middle part to be palindromic on its own. It doesn't matter which string this middle part comes from - we can choose our split point accordingly.
Since we have two possible combinations (a_prefix + b_suffix
or b_prefix + a_suffix
), we need to check both possibilities by running the same algorithm with the roles of a
and b
swapped.
Learn more about Two Pointers patterns.
Solution Approach
The solution implements a two-pointer approach with helper functions to check palindrome formation possibilities.
Main Function Structure:
The main function checkPalindromeFormation
calls a helper function check1
twice - once with (a, b)
and once with (b, a)
. This covers both possible combinations: a_prefix + b_suffix
and b_prefix + a_suffix
.
Helper Function check1
:
This function implements the two-pointer validation:
- Initialize pointer
i
at index 0 (start of stringa
) - Initialize pointer
j
at indexlen(b) - 1
(end of stringb
) - While
i < j
anda[i] == b[j]
:- Move both pointers toward center:
i = i + 1
andj = j - 1
- This validates that the outer characters will form palindromic pairs
- Move both pointers toward center:
Checking the Middle Section: After the pointers stop moving, we have three possible outcomes:
- If
i >= j
: The pointers have crossed, meaning all necessary characters match. Returntrue
. - Otherwise, check if the middle substring from either string is a palindrome:
- Call
check2(a, i, j)
to verify ifa[i...j]
is a palindrome - Call
check2(b, i, j)
to verify ifb[i...j]
is a palindrome - If either returns
true
, the combined string can form a palindrome
- Call
Helper Function check2
:
This function checks if a substring is a palindrome:
- Extract substring
a[i : j + 1]
(inclusive of bothi
andj
) - Compare it with its reverse
a[i : j + 1][::-1]
- Return
true
if they match
Why This Works: The algorithm efficiently validates palindrome formation by:
- First matching characters from opposite ends of the two strings
- When a mismatch occurs, checking if the remaining middle portion (from either string) is already palindromic
- Trying both possible combinations by swapping the roles of strings
a
andb
The time complexity is O(n)
where n
is the length of the strings, as we traverse the strings at most twice and perform palindrome checks on substrings. The space complexity is O(n)
for creating substring slices during palindrome validation.
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 the solution with a = "abdef"
and b = "fecab"
.
Step 1: Check if we can form a palindrome from a_prefix + b_suffix
Call check1(a="abdef", b="fecab")
:
- Initialize pointers:
i = 0
(pointing to 'a' in string a),j = 4
(pointing to 'b' in string b) - Check if
a[0] == b[4]
: 'a' == 'b'? No, mismatch immediately! - Since pointers haven't crossed (
i < j
), check the middle sections:check2(a, 0, 4)
: Isa[0:5] = "abdef"
a palindrome? Nocheck2(b, 0, 4)
: Isb[0:5] = "fecab"
a palindrome? No
- This combination doesn't work
Step 2: Check if we can form a palindrome from b_prefix + a_suffix
Call check1(b="fecab", a="abdef")
:
- Initialize pointers:
i = 0
(pointing to 'f' in string b),j = 4
(pointing to 'f' in string a) - Check if
b[0] == a[4]
: 'f' == 'f'? Yes, match! - Move pointers:
i = 1
,j = 3
- Check if
b[1] == a[3]
: 'e' == 'e'? Yes, match! - Move pointers:
i = 2
,j = 2
- Now
i >= j
(both are at index 2), pointers have crossed! - Return
true
Verification: The algorithm found that splitting at index 2 works:
b_prefix = "fe"
(first 2 characters of b)a_suffix = "def"
(last 3 characters of a)- Combined string:
"fe" + "def" = "fedef"
- Is "fedef" a palindrome? Yes! (reads the same forwards and backwards)
The algorithm correctly returns true
because we can form the palindrome "fedef" by combining the prefix of b with the suffix of a.
Solution Implementation
1class Solution:
2 def checkPalindromeFormation(self, a: str, b: str) -> bool:
3 """
4 Check if we can form a palindrome by taking a prefix from one string
5 and a suffix from another string.
6
7 Args:
8 a: First string
9 b: Second string
10
11 Returns:
12 True if a palindrome can be formed, False otherwise
13 """
14
15 def can_form_palindrome_with_split(string1: str, string2: str) -> bool:
16 """
17 Try to form a palindrome by taking prefix from string1 and suffix from string2.
18
19 Args:
20 string1: String to take prefix from
21 string2: String to take suffix from
22
23 Returns:
24 True if palindrome can be formed with this combination
25 """
26 left = 0
27 right = len(string2) - 1
28
29 # Match characters from outside moving inward
30 # string1[left] should equal string2[right] for palindrome
31 while left < right and string1[left] == string2[right]:
32 left += 1
33 right -= 1
34
35 # If pointers crossed, we already have a palindrome
36 # Otherwise, check if remaining middle part is palindrome in either string
37 return (left >= right or
38 is_palindrome(string1, left, right) or
39 is_palindrome(string2, left, right))
40
41 def is_palindrome(string: str, left: int, right: int) -> bool:
42 """
43 Check if substring from index left to right (inclusive) is a palindrome.
44
45 Args:
46 string: The string to check
47 left: Starting index
48 right: Ending index (inclusive)
49
50 Returns:
51 True if the substring is a palindrome
52 """
53 substring = string[left:right + 1]
54 return substring == substring[::-1]
55
56 # Try both combinations: (prefix_a + suffix_b) and (prefix_b + suffix_a)
57 return can_form_palindrome_with_split(a, b) or can_form_palindrome_with_split(b, a)
58
1class Solution {
2 /**
3 * Checks if a palindrome can be formed by concatenating a prefix of one string
4 * with a suffix of another string.
5 *
6 * @param a First string
7 * @param b Second string
8 * @return true if a palindrome can be formed, false otherwise
9 */
10 public boolean checkPalindromeFormation(String a, String b) {
11 // Try both combinations: (prefix of a + suffix of b) and (prefix of b + suffix of a)
12 return canFormPalindrome(a, b) || canFormPalindrome(b, a);
13 }
14
15 /**
16 * Checks if a palindrome can be formed by taking a prefix from string 'prefixStr'
17 * and a suffix from string 'suffixStr'.
18 *
19 * @param prefixStr String providing the prefix
20 * @param suffixStr String providing the suffix
21 * @return true if a palindrome can be formed, false otherwise
22 */
23 private boolean canFormPalindrome(String prefixStr, String suffixStr) {
24 int left = 0;
25 int right = suffixStr.length() - 1;
26
27 // Match characters from prefix start with suffix end, moving inward
28 while (left < right && prefixStr.charAt(left) == suffixStr.charAt(right)) {
29 left++;
30 right--;
31 }
32
33 // If pointers crossed, the entire combination forms a palindrome
34 // Otherwise, check if the remaining middle part is a palindrome in either string
35 return left >= right ||
36 isPalindrome(prefixStr, left, right) ||
37 isPalindrome(suffixStr, left, right);
38 }
39
40 /**
41 * Checks if the substring of 'str' from index 'left' to 'right' (inclusive)
42 * forms a palindrome.
43 *
44 * @param str The string to check
45 * @param left Starting index
46 * @param right Ending index
47 * @return true if the substring is a palindrome, false otherwise
48 */
49 private boolean isPalindrome(String str, int left, int right) {
50 // Check if characters at corresponding positions match
51 while (left < right && str.charAt(left) == str.charAt(right)) {
52 left++;
53 right--;
54 }
55
56 // If pointers crossed or met, it's a palindrome
57 return left >= right;
58 }
59}
60
1class Solution {
2public:
3 /**
4 * Check if we can form a palindrome by splitting strings a and b at the same index
5 * and concatenating a_prefix + b_suffix or b_prefix + a_suffix
6 * @param a First string
7 * @param b Second string
8 * @return true if palindrome formation is possible, false otherwise
9 */
10 bool checkPalindromeFormation(string a, string b) {
11 // Try both combinations: (a_prefix + b_suffix) and (b_prefix + a_suffix)
12 return canFormPalindrome(a, b) || canFormPalindrome(b, a);
13 }
14
15private:
16 /**
17 * Check if we can form a palindrome using prefix of first string and suffix of second string
18 * @param first String providing the prefix
19 * @param second String providing the suffix
20 * @return true if palindrome can be formed, false otherwise
21 */
22 bool canFormPalindrome(string& first, string& second) {
23 int left = 0;
24 int right = second.size() - 1;
25
26 // Match characters from first[left...] with second[...right]
27 // Moving inward while they match
28 while (left < right && first[left] == second[right]) {
29 ++left;
30 --right;
31 }
32
33 // If pointers crossed, the entire combination forms a palindrome
34 // Otherwise, check if the middle portion of either string is a palindrome
35 return left >= right ||
36 isPalindrome(first, left, right) ||
37 isPalindrome(second, left, right);
38 }
39
40 /**
41 * Check if substring of given string from index left to right is a palindrome
42 * @param str String to check
43 * @param left Starting index
44 * @param right Ending index
45 * @return true if substring is palindrome, false otherwise
46 */
47 bool isPalindrome(string& str, int left, int right) {
48 // Check if the substring str[left...right] is a palindrome
49 while (left <= right && str[left] == str[right]) {
50 ++left;
51 --right;
52 }
53
54 // If pointers crossed, the substring is a palindrome
55 return left >= right;
56 }
57};
58
1/**
2 * Checks if a palindrome can be formed by concatenating a prefix of one string
3 * with a suffix of another string
4 * @param a - First input string
5 * @param b - Second input string
6 * @returns true if a palindrome can be formed, false otherwise
7 */
8function checkPalindromeFormation(a: string, b: string): boolean {
9 /**
10 * Checks if a palindrome can be formed by taking prefix from first string
11 * and suffix from second string
12 * @param firstString - String to take prefix from
13 * @param secondString - String to take suffix from
14 * @returns true if palindrome can be formed
15 */
16 const checkPrefixSuffixCombination = (firstString: string, secondString: string): boolean => {
17 let leftIndex: number = 0;
18 let rightIndex: number = secondString.length - 1;
19
20 // Match characters from outside moving inward
21 // Compare prefix of firstString with suffix of secondString
22 while (leftIndex < rightIndex && firstString.charAt(leftIndex) === secondString.charAt(rightIndex)) {
23 leftIndex++;
24 rightIndex--;
25 }
26
27 // If pointers crossed, we found a complete palindrome
28 // Otherwise, try to complete the palindrome using middle portion from either string
29 return leftIndex >= rightIndex ||
30 checkMiddlePalindrome(firstString, leftIndex, rightIndex) ||
31 checkMiddlePalindrome(secondString, leftIndex, rightIndex);
32 };
33
34 /**
35 * Checks if the substring between given indices forms a palindrome
36 * @param str - String to check
37 * @param startIndex - Starting index of substring
38 * @param endIndex - Ending index of substring
39 * @returns true if substring is a palindrome
40 */
41 const checkMiddlePalindrome = (str: string, startIndex: number, endIndex: number): boolean => {
42 // Check if the middle portion of the string is a palindrome
43 while (startIndex < endIndex && str.charAt(startIndex) === str.charAt(endIndex)) {
44 startIndex++;
45 endIndex--;
46 }
47
48 // If pointers crossed or met, the middle portion is a palindrome
49 return startIndex >= endIndex;
50 };
51
52 // Try both combinations: (prefix of a + suffix of b) OR (prefix of b + suffix of a)
53 return checkPrefixSuffixCombination(a, b) || checkPrefixSuffixCombination(b, a);
54}
55
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of string a
or b
.
Time Complexity Analysis:
- The
check1
function iterates through the strings from both ends using two pointersi
andj
. In the worst case, this loop runsn/2
times, which isO(n)
. - The
check2
function creates a substringa[i:j+1]
and its reversea[i:j+1][::-1]
, then compares them. Creating the substring takesO(j-i+1)
time, reversing takesO(j-i+1)
time, and comparison takesO(j-i+1)
time. In the worst case,j-i+1
could beO(n)
. - The
check2
function is called at most twice withincheck1
. - The main function calls
check1
twice: once with(a, b)
and once with(b, a)
. - Overall:
O(n) + O(n) = O(n)
The space complexity is O(n)
, not O(1)
as stated in the reference answer.
Space Complexity Analysis:
- The
check2
function creates a substring using slicinga[i:j+1]
, which creates a new string of length up ton
in the worst case, requiringO(n)
space. - The reverse operation
[::-1]
also creates a new string of the same length, requiring anotherO(n)
space. - Therefore, the space complexity is
O(n)
due to the string slicing and reversal operations incheck2
.
Note: The reference answer states space complexity is O(1)
, but this appears to be incorrect given the string slicing operations that create new strings rather than operating in-place.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Boundary Handling When Checking Middle Substring
A common mistake is misunderstanding what portion of the string needs to be checked for palindrome after the two-pointer matching stops. Developers often incorrectly assume they need to check the entire remaining string or use wrong indices.
Incorrect Implementation:
def can_form_palindrome_with_split(string1: str, string2: str) -> bool:
left = 0
right = len(string2) - 1
while left < right and string1[left] == string2[right]:
left += 1
right -= 1
# WRONG: Checking from left to end of string
return is_palindrome(string1, left, len(string1) - 1)
Why it's wrong: After matching outer characters, we only need to check the middle portion between indices left
and right
, not from left
to the end of the string.
Correct Approach: Check the substring from index left
to right
(inclusive) in either string, as shown in the original solution.
2. Forgetting to Check Both Strings for Middle Palindrome
Another pitfall is only checking if the middle portion of one string is a palindrome, missing valid solutions where the middle portion of the other string forms a palindrome.
Incorrect Implementation:
def can_form_palindrome_with_split(string1: str, string2: str) -> bool:
left = 0
right = len(string2) - 1
while left < right and string1[left] == string2[right]:
left += 1
right -= 1
# WRONG: Only checking string1's middle portion
return left >= right or is_palindrome(string1, left, right)
Why it's wrong: Consider a = "abcdef"
and b = "fecbaa"
. When we take prefix from a
and suffix from b
, we get a[0:2] + b[2:6] = "ab" + "cbaa"
. The pointers stop at left=2, right=3
because a[2]='c'
!= b[3]='b'
. However, b[2:4] = "cb"
is not a palindrome, but a[2:4] = "cd"
isn't either in this case. We need to check both strings' middle portions.
Correct Approach: Always check if the middle substring is a palindrome in EITHER string1[left:right+1]
OR string2[left:right+1]
.
3. Not Trying Both Combination Orders
A critical mistake is only checking one combination (prefix from a
+ suffix from b
) and forgetting to check the other combination (prefix from b
+ suffix from a
).
Incorrect Implementation:
def checkPalindromeFormation(self, a: str, b: str) -> bool:
# WRONG: Only checking one combination
return can_form_palindrome_with_split(a, b)
Why it's wrong: Consider a = "x"
and b = "y"
. Taking prefix from a
and suffix from b
gives us "x"
, which is a palindrome. But if we only checked prefix from b
and suffix from a
, we'd get "y"
, also a palindrome. However, for more complex strings, one combination might work while the other doesn't.
Correct Approach: Always check both can_form_palindrome_with_split(a, b)
OR can_form_palindrome_with_split(b, a)
to cover all valid palindrome formations.
Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
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!