questions M-R
This commit is contained in:
220
backend/data/questions/palindromic-substrings.yaml
Normal file
220
backend/data/questions/palindromic-substrings.yaml
Normal file
@@ -0,0 +1,220 @@
|
||||
title: Palindromic Substrings
|
||||
slug: palindromic-substrings
|
||||
difficulty: medium
|
||||
leetcode_id: 647
|
||||
leetcode_url: https://leetcode.com/problems/palindromic-substrings/
|
||||
categories:
|
||||
- strings
|
||||
- dynamic-programming
|
||||
patterns:
|
||||
- two-pointers
|
||||
- dynamic-programming
|
||||
|
||||
description: |
|
||||
Given a string `s`, return *the number of **palindromic substrings** in it*.
|
||||
|
||||
A string is a **palindrome** when it reads the same backward as forward.
|
||||
|
||||
A **substring** is a contiguous sequence of characters within the string.
|
||||
|
||||
constraints: |
|
||||
- `1 <= s.length <= 1000`
|
||||
- `s` consists of lowercase English letters.
|
||||
|
||||
examples:
|
||||
- input: 's = "abc"'
|
||||
output: "3"
|
||||
explanation: "Three palindromic strings: \"a\", \"b\", \"c\"."
|
||||
- input: 's = "aaa"'
|
||||
output: "6"
|
||||
explanation: 'Six palindromic strings: "a", "a", "a", "aa", "aa", "aaa".'
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
Imagine each character in the string as a potential **centre** of a palindrome. A palindrome is symmetric — it mirrors around its centre.
|
||||
|
||||
The key insight is that palindromes can have either:
|
||||
- **Odd length**: A single character at the centre (e.g., "aba" centres on "b")
|
||||
- **Even length**: Two identical characters at the centre (e.g., "abba" centres between the two "b"s)
|
||||
|
||||
Think of it like dropping a pebble into still water and watching ripples expand outward. From each centre point, we **expand outward** in both directions as long as the characters on both sides match. Each successful expansion represents another valid palindrome we've found.
|
||||
|
||||
By systematically considering every possible centre (both single characters and pairs of adjacent characters), we can count all palindromic substrings without missing any or counting duplicates.
|
||||
|
||||
approach: |
|
||||
We solve this using the **Expand Around Centre** approach:
|
||||
|
||||
**Step 1: Initialise a counter**
|
||||
|
||||
- `count`: Set to `0` to track the total number of palindromic substrings
|
||||
|
||||
|
||||
|
||||
**Step 2: Iterate through each potential centre**
|
||||
|
||||
- For each index `i` from `0` to `n-1`, treat it as a potential centre
|
||||
- Each position can be the centre of both odd-length and even-length palindromes
|
||||
|
||||
|
||||
|
||||
**Step 3: Expand around odd-length centres**
|
||||
|
||||
- Call `expand(s, i, i)` — starting with a single character centre
|
||||
- Expand outward while `s[left] == s[right]`
|
||||
- Each successful comparison means we found another palindrome
|
||||
- Continue until characters don't match or we hit boundaries
|
||||
|
||||
|
||||
|
||||
**Step 4: Expand around even-length centres**
|
||||
|
||||
- Call `expand(s, i, i + 1)` — starting with two adjacent characters as the centre
|
||||
- Same expansion logic as odd-length centres
|
||||
- This catches palindromes like "aa", "abba", etc.
|
||||
|
||||
|
||||
|
||||
**Step 5: Return the total count**
|
||||
|
||||
- Return `count` after processing all centres
|
||||
|
||||
|
||||
|
||||
This approach is efficient because each expansion takes O(n) time in the worst case, and we have O(n) centres, giving us O(n²) total — much better than checking all O(n²) substrings explicitly.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Checking All Substrings Naively
|
||||
description: |
|
||||
A common first approach is to generate all substrings and check each one for being a palindrome:
|
||||
- Outer loop for start index: O(n)
|
||||
- Inner loop for end index: O(n)
|
||||
- Palindrome check for each substring: O(n)
|
||||
|
||||
This results in **O(n³) time complexity**. While it passes for `n <= 1000`, it's significantly slower than necessary. For larger inputs (in similar problems), this approach would cause TLE.
|
||||
wrong_approach: "Generate all substrings, check each for palindrome"
|
||||
correct_approach: "Expand around centres in O(n²)"
|
||||
|
||||
- title: Forgetting Even-Length Palindromes
|
||||
description: |
|
||||
When expanding from centres, it's easy to only consider single characters as centres (odd-length palindromes).
|
||||
|
||||
For example, in "abba", if you only expand from single characters, you'll find "a", "b", "b", "a" but miss "bb" and "abba".
|
||||
|
||||
You must expand from both:
|
||||
- Single characters: `expand(i, i)` for odd-length
|
||||
- Adjacent pairs: `expand(i, i+1)` for even-length
|
||||
wrong_approach: "Only expand from single character centres"
|
||||
correct_approach: "Expand from both single characters and adjacent pairs"
|
||||
|
||||
- title: Off-by-One Errors in Expansion
|
||||
description: |
|
||||
When expanding outward, boundary checks are crucial. The expansion should stop when:
|
||||
- `left < 0` (hit the start of string)
|
||||
- `right >= n` (hit the end of string)
|
||||
- `s[left] != s[right]` (characters don't match)
|
||||
|
||||
A common mistake is checking boundaries after accessing the characters, causing index out of bounds errors.
|
||||
wrong_approach: "Check boundaries after character comparison"
|
||||
correct_approach: "Check boundaries before accessing characters"
|
||||
|
||||
key_takeaways:
|
||||
- "**Expand around centre pattern**: For palindrome problems, thinking in terms of centres rather than endpoints often leads to cleaner solutions"
|
||||
- "**Odd vs even length**: Always consider both cases when dealing with palindromes — single character centres and two-character centres"
|
||||
- "**Foundation for Longest Palindromic Substring**: The same expand-around-centre technique solves LeetCode 5, just track the longest instead of counting"
|
||||
- "**Alternative: Dynamic Programming**: This problem can also be solved with DP where `dp[i][j]` indicates if `s[i:j+1]` is a palindrome, useful for problems requiring the actual substrings"
|
||||
|
||||
time_complexity: "O(n²). For each of the `n` positions, we potentially expand up to `n` times in each direction."
|
||||
space_complexity: "O(1). We only use a constant number of variables for counting and expansion indices."
|
||||
|
||||
solutions:
|
||||
- approach_name: Expand Around Centre
|
||||
is_optimal: true
|
||||
code: |
|
||||
def count_substrings(s: str) -> int:
|
||||
def expand(left: int, right: int) -> int:
|
||||
"""Count palindromes by expanding from centre."""
|
||||
count = 0
|
||||
# Expand while within bounds and characters match
|
||||
while left >= 0 and right < len(s) and s[left] == s[right]:
|
||||
count += 1 # Found a palindrome
|
||||
left -= 1 # Expand left
|
||||
right += 1 # Expand right
|
||||
return count
|
||||
|
||||
total = 0
|
||||
for i in range(len(s)):
|
||||
# Odd-length palindromes (single character centre)
|
||||
total += expand(i, i)
|
||||
# Even-length palindromes (two character centre)
|
||||
total += expand(i, i + 1)
|
||||
|
||||
return total
|
||||
explanation: |
|
||||
**Time Complexity:** O(n²) — For each of n centres, expansion can take up to O(n) time.
|
||||
|
||||
**Space Complexity:** O(1) — Only using a few integer variables.
|
||||
|
||||
We iterate through each position as a potential centre and expand outward for both odd and even length palindromes. Each expansion continues while characters match, counting each valid palindrome found.
|
||||
|
||||
- approach_name: Dynamic Programming
|
||||
is_optimal: false
|
||||
code: |
|
||||
def count_substrings(s: str) -> int:
|
||||
n = len(s)
|
||||
count = 0
|
||||
|
||||
# dp[i][j] = True if s[i:j+1] is a palindrome
|
||||
dp = [[False] * n for _ in range(n)]
|
||||
|
||||
# Single characters are palindromes
|
||||
for i in range(n):
|
||||
dp[i][i] = True
|
||||
count += 1
|
||||
|
||||
# Check substrings of length 2
|
||||
for i in range(n - 1):
|
||||
if s[i] == s[i + 1]:
|
||||
dp[i][i + 1] = True
|
||||
count += 1
|
||||
|
||||
# Check substrings of length 3 and above
|
||||
for length in range(3, n + 1):
|
||||
for i in range(n - length + 1):
|
||||
j = i + length - 1
|
||||
# Palindrome if ends match and middle is palindrome
|
||||
if s[i] == s[j] and dp[i + 1][j - 1]:
|
||||
dp[i][j] = True
|
||||
count += 1
|
||||
|
||||
return count
|
||||
explanation: |
|
||||
**Time Complexity:** O(n²) — We fill an n×n DP table.
|
||||
|
||||
**Space Complexity:** O(n²) — We store the DP table.
|
||||
|
||||
This approach builds up from smaller substrings: single characters are palindromes, length-2 substrings are palindromes if both characters match, and longer substrings are palindromes if the ends match and the middle (already computed) is a palindrome. While correct and useful for understanding, the expand-around-centre approach is preferred due to O(1) space.
|
||||
|
||||
- approach_name: Brute Force
|
||||
is_optimal: false
|
||||
code: |
|
||||
def count_substrings(s: str) -> int:
|
||||
def is_palindrome(substring: str) -> bool:
|
||||
"""Check if a string is a palindrome."""
|
||||
return substring == substring[::-1]
|
||||
|
||||
count = 0
|
||||
n = len(s)
|
||||
|
||||
# Check all possible substrings
|
||||
for i in range(n):
|
||||
for j in range(i, n):
|
||||
if is_palindrome(s[i:j + 1]):
|
||||
count += 1
|
||||
|
||||
return count
|
||||
explanation: |
|
||||
**Time Complexity:** O(n³) — O(n²) substrings, each taking O(n) to check.
|
||||
|
||||
**Space Complexity:** O(n) — Creating substring copies for comparison.
|
||||
|
||||
This approach explicitly generates every substring and checks if it's a palindrome by comparing it to its reverse. While straightforward and correct, it's significantly slower than the optimal approaches. Included to illustrate the progression from naive to optimal thinking.
|
||||
Reference in New Issue
Block a user