String Algorithms
For each index (and each pair of adjacent indices), expand outward as long as characters match. Finds all palindromic substrings in O(n²) without extra space. The center can be a character or a gap.
LeetCode 5. Longest Palindromic Substring asks for the longest palindromic substring of a given string. Every palindrome has a center. For odd-length palindromes like "racecar" the center is a single character, the middle 'e'. For even-length palindromes like "abba" the center sits between two characters, between the two 'b's. This observation is the seed of the expand-around-center technique: instead of asking "is this substring a palindrome?", ask "what is the longest palindrome centered here?" and expand outward to find the answer.
A string of length has exactly possible centers: centers for odd-length palindromes (one per character) and centers for even-length palindromes (one per adjacent pair). Visiting each center and expanding takes per center in the worst case, giving an overall time complexity of with extra space, far better than the space required by a full DP table.
For a given center, you maintain two pointers l and r that start at the center and walk outward as long as the characters they point to match. When s[l] != s[r] (or a pointer walks off the string), the palindrome bounded by [l+1, r-1] is the longest one at that center.
def expand(s, l, r):
while l >= 0 and r < len(s) and s[l] == s[r]:
l -= 1
r += 1
# After the loop, s[l+1:r] is the palindrome
return l + 1, r - 1For odd-length palindromes, start with l = r = i. For even-length palindromes, start with l = i and r = i + 1. Both cases are handled in a single pass over the string.
Consider s = "babad". We iterate over each center:
s[0]='b', s[1]='a' don't match; length 0.s[0]='b' vs s[2]='b', match! Expand again: l goes to -1, stop. Palindrome is "bab", length 3.s[1]='a', s[2]='b' don't match; length 0.s[1]='a' vs s[3]='a', match! Expand again: l goes to 0, s[0]='b' vs s[4]='d', no match. Palindrome is "aba", length 3.s[2]='b' vs s[4]='d', no match. Just "a".Both "bab" and "aba" are length 3. The algorithm returns the first one found, which is "bab".
def longest_palindrome(s: str) -> str:
res = ""
for i in range(len(s)):
for l, r in [(i, i), (i, i + 1)]:
while l >= 0 and r < len(s) and s[l] == s[r]:
l -= 1
r += 1
# s[l+1:r] is the palindrome found at this center
if r - l - 1 > len(res):
res = s[l + 1 : r]
return resThe inner loop handles both center types in one elegant structure. After the while loop exits, l has overshot one step left and r one step right, so the actual palindrome is s[l+1:r] and its length is r - l - 1.
LeetCode 647. Palindromic Substrings asks for the total number of palindromic substrings.
To count every palindromic substring rather than find the longest, change the bookkeeping: increment a counter on each successful expansion step rather than tracking the maximum length.
def count_substrings(s: str) -> int:
count = 0
for i in range(len(s)):
for l, r in [(i, i), (i, i + 1)]:
while l >= 0 and r < len(s) and s[l] == s[r]:
count += 1
l -= 1
r += 1
return countEach iteration of the while body corresponds to one valid palindrome being discovered at that center, so incrementing inside the loop naturally counts all of them, including single characters (the first iteration of the odd-center loop always succeeds when l == r).
The expand-around-center approach is because it may redo work: if a long palindrome was found at center i, the palindromes centered inside it could be determined from symmetry rather than re-expanded. Manacher's algorithm exploits exactly this observation by maintaining a "rightmost boundary" palindrome and using previously computed radii to initialize new expansions, reducing the total work to . For most LeetCode problems, the approach is perfectly sufficient. Manacher's is worth knowing exists but rarely needed in interviews.
You're probably looking at expand-around-center when:
Common templates:
while loop. Example: Palindromic Substrings.