questions S-W
This commit is contained in:
203
backend/data/questions/valid-palindrome-ii.yaml
Normal file
203
backend/data/questions/valid-palindrome-ii.yaml
Normal file
@@ -0,0 +1,203 @@
|
||||
title: Valid Palindrome II
|
||||
slug: valid-palindrome-ii
|
||||
difficulty: easy
|
||||
leetcode_id: 680
|
||||
leetcode_url: https://leetcode.com/problems/valid-palindrome-ii/
|
||||
categories:
|
||||
- strings
|
||||
- two-pointers
|
||||
patterns:
|
||||
- two-pointers
|
||||
|
||||
description: |
|
||||
Given a string `s`, return `true` *if the* `s` *can be a palindrome after deleting **at most one** character from it*.
|
||||
|
||||
constraints: |
|
||||
- `1 <= s.length <= 10^5`
|
||||
- `s` consists of lowercase English letters.
|
||||
|
||||
examples:
|
||||
- input: 's = "aba"'
|
||||
output: "true"
|
||||
explanation: "The string is already a palindrome, so no deletion is needed."
|
||||
- input: 's = "abca"'
|
||||
output: "true"
|
||||
explanation: "You could delete the character 'c' to get 'aba', which is a palindrome."
|
||||
- input: 's = "abc"'
|
||||
output: "false"
|
||||
explanation: "No matter which character you delete, you cannot form a palindrome."
|
||||
|
||||
explanation:
|
||||
intuition: |
|
||||
Imagine checking if a word reads the same forwards and backwards by placing two fingers at opposite ends and moving them towards the centre.
|
||||
|
||||
For a regular palindrome check, if the characters under your fingers ever mismatch, the string fails immediately. But here we have a **second chance**: we're allowed to remove *one* character and try again.
|
||||
|
||||
Think of it like this: when you encounter your first mismatch at positions `left` and `right`, you have two options to "fix" it:
|
||||
- **Skip the left character**: Check if the substring from `left + 1` to `right` is a palindrome
|
||||
- **Skip the right character**: Check if the substring from `left` to `right - 1` is a palindrome
|
||||
|
||||
If either option produces a valid palindrome, the answer is `true`. This greedy approach works because you only get one deletion, so when characters don't match, you must decide immediately which one to skip.
|
||||
|
||||
approach: |
|
||||
We solve this using a **Two Pointer Approach with One Chance**:
|
||||
|
||||
**Step 1: Set up two pointers**
|
||||
|
||||
- `left`: Start at index `0` (beginning of string)
|
||||
- `right`: Start at index `len(s) - 1` (end of string)
|
||||
|
||||
|
||||
|
||||
**Step 2: Move pointers inward while characters match**
|
||||
|
||||
- While `left < right`, compare `s[left]` and `s[right]`
|
||||
- If they match, move both pointers inward (`left += 1`, `right -= 1`)
|
||||
- If they don't match, we've found a problem — proceed to Step 3
|
||||
|
||||
|
||||
|
||||
**Step 3: Handle the first mismatch**
|
||||
|
||||
- When `s[left] != s[right]`, try both deletion options:
|
||||
- Check if `s[left+1:right+1]` is a palindrome (skip left character)
|
||||
- Check if `s[left:right]` is a palindrome (skip right character)
|
||||
- If either substring is a palindrome, return `true`
|
||||
- If neither works, return `false`
|
||||
|
||||
|
||||
|
||||
**Step 4: Return true if no mismatch found**
|
||||
|
||||
- If we complete the loop without finding a mismatch, the string is already a palindrome — return `true`
|
||||
|
||||
|
||||
|
||||
This works because we greedily match characters from outside in, and only use our one deletion when absolutely necessary.
|
||||
|
||||
common_pitfalls:
|
||||
- title: Checking All Possible Deletions
|
||||
description: |
|
||||
A naive approach is to try deleting each character one by one and check if the result is a palindrome:
|
||||
|
||||
```
|
||||
for i in range(n):
|
||||
if is_palindrome(s[:i] + s[i+1:]):
|
||||
return True
|
||||
```
|
||||
|
||||
This results in **O(n^2) time complexity** because you create `n` substrings and check each one in O(n) time. With `s.length <= 10^5`, this approach will be too slow.
|
||||
|
||||
Instead, use two pointers to find the *exact* position where a deletion might help, then only check those two possibilities.
|
||||
wrong_approach: "Try deleting each character one by one"
|
||||
correct_approach: "Two pointers to find the mismatch, then check two substrings"
|
||||
|
||||
- title: Only Trying One Deletion Option
|
||||
description: |
|
||||
When you find a mismatch at positions `left` and `right`, you might be tempted to only try skipping one of them. For example, always skipping the left character.
|
||||
|
||||
Consider `s = "aguokepatgbnvfqmgmlcupuufxoohdfpgjdmysgvhmvffcnqxjjxqncffvmhvgsymdj"`. When you hit the first mismatch, skipping the wrong character leads to failure even though the string *can* become a palindrome.
|
||||
|
||||
Always try **both** options: skip left OR skip right.
|
||||
wrong_approach: "Only try skipping one character at mismatch"
|
||||
correct_approach: "Try both skip-left and skip-right, return true if either works"
|
||||
|
||||
- title: Creating New Strings for Palindrome Check
|
||||
description: |
|
||||
Using string slicing like `s[left+1:right+1]` creates a new string, which uses O(n) space. While this works, you can optimise by passing indices to a helper function that checks palindrome in-place.
|
||||
|
||||
For this problem, the O(n) space from slicing is acceptable, but in interviews, mentioning the in-place optimisation shows depth of understanding.
|
||||
|
||||
key_takeaways:
|
||||
- "**Two pointers for palindrome**: The classic technique of comparing from both ends works here with a twist — you get one 'undo' when characters don't match"
|
||||
- "**Greedy decision point**: When you hit a mismatch, you must try both deletion options since you can't know in advance which will succeed"
|
||||
- "**Building on fundamentals**: This problem extends the basic palindrome check — many interview problems are variations of simpler ones"
|
||||
- "**Early termination**: If no mismatch is found, the string is already a palindrome — no deletion needed"
|
||||
|
||||
time_complexity: "O(n). We traverse the string at most twice — once with the main two pointers, and potentially once more to verify a substring after a mismatch."
|
||||
space_complexity: "O(n) with string slicing for the substring palindrome check, or O(1) if using index-based helper function."
|
||||
|
||||
solutions:
|
||||
- approach_name: Two Pointers with Helper Function
|
||||
is_optimal: true
|
||||
code: |
|
||||
def valid_palindrome(s: str) -> bool:
|
||||
def is_palindrome(left: int, right: int) -> bool:
|
||||
"""Check if s[left:right+1] is a palindrome using indices."""
|
||||
while left < right:
|
||||
if s[left] != s[right]:
|
||||
return False
|
||||
left += 1
|
||||
right -= 1
|
||||
return True
|
||||
|
||||
left, right = 0, len(s) - 1
|
||||
|
||||
while left < right:
|
||||
if s[left] != s[right]:
|
||||
# Mismatch found — try skipping left or right character
|
||||
return is_palindrome(left + 1, right) or is_palindrome(left, right - 1)
|
||||
left += 1
|
||||
right -= 1
|
||||
|
||||
# No mismatch found — already a palindrome
|
||||
return True
|
||||
explanation: |
|
||||
**Time Complexity:** O(n) — We scan through the string at most twice.
|
||||
|
||||
**Space Complexity:** O(1) — We only use pointer variables; no extra space proportional to input size.
|
||||
|
||||
The helper function checks if a substring is a palindrome using indices, avoiding string slicing. When we find a mismatch, we try both options (skip left or skip right) and return true if either produces a palindrome.
|
||||
|
||||
- approach_name: Two Pointers with String Slicing
|
||||
is_optimal: false
|
||||
code: |
|
||||
def valid_palindrome(s: str) -> bool:
|
||||
def is_palindrome(substring: str) -> bool:
|
||||
"""Check if a string is a palindrome."""
|
||||
return substring == substring[::-1]
|
||||
|
||||
left, right = 0, len(s) - 1
|
||||
|
||||
while left < right:
|
||||
if s[left] != s[right]:
|
||||
# Try removing left character or right character
|
||||
skip_left = s[left + 1:right + 1]
|
||||
skip_right = s[left:right]
|
||||
return is_palindrome(skip_left) or is_palindrome(skip_right)
|
||||
left += 1
|
||||
right -= 1
|
||||
|
||||
return True
|
||||
explanation: |
|
||||
**Time Complexity:** O(n) — String reversal and comparison are both O(n).
|
||||
|
||||
**Space Complexity:** O(n) — String slicing creates new strings.
|
||||
|
||||
This version is more readable but uses extra space for the substring copies. The logic is identical: find the first mismatch, then check if either deletion option creates a palindrome.
|
||||
|
||||
- approach_name: Brute Force
|
||||
is_optimal: false
|
||||
code: |
|
||||
def valid_palindrome(s: str) -> bool:
|
||||
def is_palindrome(string: str) -> bool:
|
||||
return string == string[::-1]
|
||||
|
||||
# Check if already a palindrome
|
||||
if is_palindrome(s):
|
||||
return True
|
||||
|
||||
# Try deleting each character
|
||||
for i in range(len(s)):
|
||||
# Create string with character at index i removed
|
||||
modified = s[:i] + s[i + 1:]
|
||||
if is_palindrome(modified):
|
||||
return True
|
||||
|
||||
return False
|
||||
explanation: |
|
||||
**Time Complexity:** O(n^2) — We try n deletions, each requiring O(n) palindrome check.
|
||||
|
||||
**Space Complexity:** O(n) — Creating modified strings.
|
||||
|
||||
This brute force approach is correct but too slow for large inputs. It's included to illustrate why the two-pointer optimisation is necessary. With `n = 10^5`, this would perform up to 10 billion operations.
|
||||
Reference in New Issue
Block a user