title: Longest Palindromic Substring slug: longest-palindromic-substring difficulty: medium leetcode_id: 5 leetcode_url: https://leetcode.com/problems/longest-palindromic-substring/ categories: - strings - dynamic-programming patterns: - two-pointers - dynamic-programming function_signature: "def longest_palindrome(s: str) -> str:" test_cases: visible: - input: { s: "babad" } expected: "bab" - input: { s: "cbbd" } expected: "bb" hidden: - input: { s: "a" } expected: "a" - input: { s: "ac" } expected: "a" - input: { s: "racecar" } expected: "racecar" - input: { s: "aacabdkacaa" } expected: "aca" description: | Given a string `s`, return *the longest palindromic substring* in `s`. A **palindrome** is a string that reads the same forward and backward. constraints: | - `1 <= s.length <= 1000` - `s` consists of only digits and English letters examples: - input: 's = "babad"' output: '"bab"' explanation: '"aba" is also a valid answer — both have length 3.' - input: 's = "cbbd"' output: '"bb"' explanation: "The longest palindromic substring is \"bb\" with length 2." explanation: intuition: | Every palindrome has a **center**. For odd-length palindromes like "aba", the center is the middle character 'b'. For even-length palindromes like "abba", the center is the gap between the two 'b's. Think of it like this: if we know the center, we can find the full palindrome by **expanding outward** — checking if the characters on both sides match. We keep expanding until they don't match. The strategy is simple: try every possible center (each character and each gap between characters), expand to find the longest palindrome for that center, and track the overall longest. This "expand around center" approach is intuitive and uses O(1) extra space, making it ideal for interviews. approach: | We solve this using **Expand Around Center**: **Step 1: Define the expand helper function** - `expand(left, right)` returns the bounds of the longest palindrome centered at this position - While `left >= 0` and `right < len(s)` and `s[left] == s[right]`: - Expand: decrement `left`, increment `right` - Return the bounds of the palindrome (after adjusting for the last failed expansion)   **Step 2: Try every possible center** - For each index `i`: - Try **odd-length** palindrome: `expand(i, i)` — center is single character - Try **even-length** palindrome: `expand(i, i+1)` — center is between characters - Update the best result if either expansion found a longer palindrome   **Step 3: Return the longest palindrome** - Track `start` and `end` indices of the best palindrome found - Return `s[start:end+1]`   Why does this work? By checking both odd and even centers at every position, we're guaranteed to find the center of the longest palindrome somewhere. common_pitfalls: - title: Forgetting Even-Length Palindromes description: | If you only expand around single characters, you'll miss even-length palindromes like "abba" or "bb". Every position needs two expansion attempts: one for odd (center at `i`) and one for even (center between `i` and `i+1`). wrong_approach: "Only calling expand(i, i)" correct_approach: "Call both expand(i, i) and expand(i, i+1)" - title: Index Out of Bounds During Expansion description: | The expansion loop must check bounds **before** accessing characters. A common mistake is checking equality first, which causes an index error. wrong_approach: "while s[left] == s[right] and left >= 0 and right < len(s)" correct_approach: "while left >= 0 and right < len(s) and s[left] == s[right]" - title: Returning Length Instead of Substring description: | The problem asks for the actual substring, not just its length. Track the start and end positions of the best palindrome so you can extract it. wrong_approach: "return max_length" correct_approach: "return s[start:end+1]" key_takeaways: - "**Expand around center**: O(n²) time, O(1) space — optimal for interviews" - "**Handle both odd and even**: Check single-character centers AND gaps between characters" - "**Track positions, not just length**: You need to return the actual substring" - "**Manacher's algorithm**: Can solve in O(n) but is complex — not expected in interviews" time_complexity: "O(n²). For each of n possible centers, expansion can take up to O(n) time in the worst case." space_complexity: "O(1). Only a few variables for tracking positions — no additional data structures." solutions: - approach_name: Expand Around Center is_optimal: true code: | def longest_palindrome(s: str) -> str: def expand(left: int, right: int) -> tuple[int, int]: """Expand around center and return palindrome bounds.""" while left >= 0 and right < len(s) and s[left] == s[right]: left -= 1 right += 1 # Return bounds of palindrome (undo last expansion) return left + 1, right - 1 start, end = 0, 0 for i in range(len(s)): # Try odd-length palindrome (single character center) l1, r1 = expand(i, i) if r1 - l1 > end - start: start, end = l1, r1 # Try even-length palindrome (center between characters) l2, r2 = expand(i, i + 1) if r2 - l2 > end - start: start, end = l2, r2 return s[start:end + 1] explanation: | **Time Complexity:** O(n²) — n centers, up to n expansion steps each. **Space Complexity:** O(1) — Only tracking start/end positions. For each position, we try both odd and even palindrome centers. The expand function returns the bounds of the longest palindrome for that center. We track the overall longest and return it at the end. - approach_name: Dynamic Programming is_optimal: false code: | def longest_palindrome(s: str) -> str: n = len(s) if n < 2: return s # dp[i][j] = True if s[i:j+1] is a palindrome dp = [[False] * n for _ in range(n)] start, max_len = 0, 1 # Single characters are palindromes for i in range(n): dp[i][i] = True # Check substrings of increasing length for length in range(2, n + 1): for i in range(n - length + 1): j = i + length - 1 if length == 2: # Two characters: palindrome if they match dp[i][j] = s[i] == s[j] else: # Longer: palindrome if ends match AND inner is palindrome dp[i][j] = s[i] == s[j] and dp[i + 1][j - 1] if dp[i][j] and length > max_len: start, max_len = i, length return s[start:start + max_len] explanation: | **Time Complexity:** O(n²) — Fill n×n table. **Space Complexity:** O(n²) — DP table storage. Build up palindrome information from smaller to larger substrings. `dp[i][j]` is True if substring from i to j is a palindrome. A substring is a palindrome if its ends match and its inner substring is also a palindrome. Track the longest one found.