The Problem
Given a string s
, return the longest palindromic substring in s
.
A string is palindromic if it reads the same forward and backward.
Examples
Input: s = "babad"
Output: "bab"
Explanation: "aba" is also a valid answer.
Input: s = "cbbd"
Output: "bb"
Constraints
- 1 <= s.length <= 1000
s
consists of only digits and English letters.
Brute Force Solution
func longestPalindrome(_ s: String) -> String {
let sArray = Array(s)
var res = ""
var resLen = 0
let n = s.count
for i in 0 ..< n {
for j in i ..< n {
var l = i
var r = j
while l < r && sArray[l] == sArray[r] {
l += 1
r -= 1
}
if l >= r && resLen < (j - i + 1) {
res = String(sArray[i ..< j + 1])
resLen = j - i + 1
}
}
}
return res
}
Explanation
In the first example, we are given the input "babad"
. Let’s visualize it and try to find the longest palindromic substring.
As you can see, we have two different palindromic substrings: "bab"
and "aba"
, so we could return either of these.
The brute force solution to this problem involves checking every single substring, verifying if it’s a palindrome, and finding the longest one.
This solution is not very efficient because we need to scan through the entire string and check every substring. As a result, the time complexity is O(n * n^2) -> O(n^3)
.
Time/Space Complexity
- Time complexity: O(n³)
- Space complexity: O(1)
Dynamic Programming Solution
func longestPalindrome(_ s: String) -> String {
var resIdx = 0
var resLen = 0
let n = s.count
let sArray = Array(s)
var dp: [[Bool]] = Array(repeating: Array(repeating: false, count: n), count: n)
for i in stride(from: n - 1, to: -1, by: -1) {
for j in i ..< n {
if sArray[i] == sArray[j] && (j - i <= 2 || dp[i + 1][j - 1]) {
dp[i][j] = true
if resLen < (j - i + 1) {
resIdx = i
resLen = j - i + 1
}
}
}
}
return String(sArray[resIdx ..< resIdx + resLen])
}
Explanation
Another way to solve this problem is by using a dynamic programming algorithm.
- We create
resIdx
to store the starting index of the result. resLen
stores the length of the result.dp
is a 2D array to keep track of palindromic substrings.
After that, we iterate through the string in reverse and use an inner loop to iterate from i
to the end.
Next, we check if the characters are equal and either:
- The substring is shorter than or equal to length
3
, or - The inner substring is already a palindrome.
When we find a palindrome, we check if it’s longer than the current result. If so, we update our pointers.
Finally, we return the result substring using the calculated pointers.
This is not a very efficient solution in terms of space complexity. We can improve it using the two-pointer technique.
Time/Space Complexity
- Time complexity: O(n²)
- Space complexity: O(n²)
Two-Pointer Solution
func longestPalindrome(_ s: String) -> String {
var resIdx = 0
var resLen = 0
let n = s.count
let sArray = Array(s)
for i in 0 ..< n {
// Odd length
var l = i
var r = i
while l >= 0 && r < n && sArray[l] == sArray[r] {
if (r - l + 1) > resLen {
resIdx = l
resLen = r - l + 1
}
l -= 1
r += 1
}
// Even length
l = i
r = i + 1
while l >= 0 && r < n && sArray[l] == sArray[r] {
if (r - l + 1) > resLen {
resIdx = l
resLen = r - l + 1
}
l -= 1
r += 1
}
}
return String(sArray[resIdx ..< resIdx + resLen])
}
Explanation
Finally, we can solve this problem in a space-optimized way using the two-pointer technique.
Let’s visualize how we can check if a substring is a palindrome using the example "bab"
:
- We can check if it’s a palindrome by starting from the outside and comparing the characters. As long as they are equal, we continue until we reach the middle, confirming it is a palindrome.
Another way:
- We can start in the middle, expand outward, and check if it’s a palindrome that way.
You can see that the second method is more efficient, so we will use this approach.
For each character, we consider it as the center and expand outward. Since we take each character (n
) and expand outward (n
), the overall time complexity is O(n²)
.
Finally, we need to handle one edge case: checking for palindromes of even length.
Time/Space Complexity
- Time complexity: O(n²)
- Space complexity: O(1)