- 
  Notifications
 
You must be signed in to change notification settings  - Fork 0
 
feat: add problem 125 valid palindrome #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
 
  Open
 
 
 
 
  Open
 Changes from all commits
 Commits
 
 
 File filter
Filter by extension
Conversations
 Failed to load comments. 
 
 
 
  Loading
 
 Jump to
 
 Jump to file
 
 
 
 Failed to load files. 
 
 
 
  Loading
 
 Diff view
Diff view
There are no files selected for viewing
 
 
 
 172 changes: 98 additions & 74 deletions
 
 
 
 explanations/125/en.md
 
 
 
 
  
 
 
 
 
 
 
 This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
 Learn more about bidirectional Unicode characters
 
 
 
 
 | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -1,112 +1,136 @@ | ||
| ## 125. Valid Palindrome [Easy] | ||
| https://leetcode.com/problems/valid-palindrome/ | ||
| # 125. Valid Palindrome | ||
| 
  | 
||
| A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers. | ||
| **Difficulty:** Easy | ||
| **Link:** https://leetcode.com/problems/valid-palindrome/ | ||
| 
  | 
||
| Given a string `s`, return `true` if it is a palindrome, or `false` otherwise. | ||
| ## Problem Description | ||
| 
  | 
||
| **Examples** | ||
| ```text | ||
| Example 1: | ||
| A phrase is a **palindrome** if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers. | ||
| 
  | 
||
| Given a string `s`, return `true` *if it is a **palindrome**, or* `false` *otherwise*. | ||
| 
  | 
||
| **Example 1:** | ||
| ``` | ||
| Input: s = "A man, a plan, a canal: Panama" | ||
| Output: true | ||
| Explanation: "amanaplanacanalpanama" is a palindrome. | ||
| ``` | ||
| 
  | 
||
| Example 2: | ||
| **Example 2:** | ||
| ``` | ||
| Input: s = "race a car" | ||
| Output: false | ||
| Explanation: "raceacar" is not a palindrome. | ||
| ``` | ||
| 
  | 
||
| Example 3: | ||
| **Example 3:** | ||
| ``` | ||
| Input: s = " " | ||
| Output: true | ||
| Explanation: s is an empty string "" after removing non-alphanumeric characters. | ||
| An empty string is a palindrome. | ||
| Since an empty string reads the same forward and backward, it is a palindrome. | ||
| ``` | ||
| 
  | 
||
| **Constraints:** | ||
| ```text | ||
| 1 <= s.length <= 2 * 10^5 | ||
| s consists only of printable ASCII characters. | ||
| ``` | ||
| - `1 <= s.length <= 2 * 10^5` | ||
| - `s` consists only of printable ASCII characters. | ||
| 
  | 
||
| ## Explanation | ||
| You're given a string `s`. Your task is to determine if it's a palindrome. A palindrome is a sequence that reads the same forwards and backwards. However, there are two special rules for this problem: | ||
| 1. You should ignore all non-alphanumeric characters (like spaces, commas, colons, etc.). | ||
| 2. You should treat uppercase and lowercase letters as the same (e.g., 'A' is the same as 'a'). | ||
| 
  | 
||
| So, "A man, a plan, a canal: Panama" should become "amanaplanacanalpanama" before checking if it's a palindrome. | ||
| 
  | 
||
| ### Strategy | ||
| You are given a string `s`. | ||
| The problem asks you to check if `s` is a palindrome after specific filtering and casing rules. | ||
| This is a string manipulation and two-pointer problem. | ||
| 
  | 
||
| **Constraints:** | ||
| * `1 <= s.length <= 2 * 10^5`: The string can be quite long, so an O(n) solution is preferred. O(n log n) might also pass, but O(n^2) would be too slow. | ||
| * `s consists only of printable ASCII characters`: This simplifies character handling, no complex encodings. | ||
| 
  | 
||
| The most robust and efficient strategy involves processing the string to meet the palindrome rules and then applying a two-pointer approach. You can either pre-process the entire string first, or filter characters on the fly while comparing. The "on-the-fly" method is often slightly more memory-efficient as it doesn't create a new full string immediately, but both are O(N) time and O(N) or O(1) space (depending on how you count auxiliary space for the cleaned string). The "on-the-fly" method often has better constant factors for space. | ||
| This is a **two-pointer string problem** that requires checking if a string is a palindrome after preprocessing. The key insight is to use two pointers from both ends and compare characters while skipping non-alphanumeric characters. | ||
| 
  | 
||
| Let's focus on the "Two Pointers with On-the-Fly Filtering" strategy, as it's generally preferred for interviews due to its space efficiency and direct manipulation of the original string. | ||
| **Key observations:** | ||
| - We need to ignore case (convert to lowercase) | ||
| - We need to skip non-alphanumeric characters (punctuation, spaces) | ||
| - We can use two pointers to compare from both ends | ||
| - Empty string after preprocessing is considered a palindrome | ||
| 
  | 
||
| **Decomposition:** | ||
| 1. Initialize two pointers, `left` at the beginning of the string and `right` at the end. | ||
| 2. While `left` is less than `right`: | ||
| a. Move `left` inwards until it points to an alphanumeric character. | ||
| b. Move `right` inwards until it points to an alphanumeric character. | ||
| c. If `left` is still less than `right` (meaning both found valid characters): | ||
| i. Compare the characters at `left` and `right`, ignoring case. | ||
| ii. If they don't match, return `false` (not a palindrome). | ||
| iii. If they match, move `left` one step right and `right` one step left. | ||
| d. If `left` becomes `right` or `left` crosses `right`, the loop ends. | ||
| 3. If the loop completes, it means all compared alphanumeric characters matched, so return `true` (it's a palindrome). | ||
| **High-level approach:** | ||
| 1. **Use two pointers**: One from start, one from end | ||
| 2. **Skip non-alphanumeric characters**: Move pointers until we find valid characters | ||
| 3. **Compare characters**: Check if characters match (case-insensitive) | ||
| 4. **Continue until pointers meet**: If all comparisons pass, it's a palindrome | ||
| 
  | 
||
| ### Steps | ||
| Let's use the example `s = "A man, a plan, a canal: Panama"` | ||
| 
  | 
||
| 1. Initialize `left = 0`, `right = len(s) - 1` (which is `29`). | ||
| `s[left]` is 'A', `s[right]` is 'a'. | ||
| Let's break down the solution step by step: | ||
| 
  | 
||
| **Step 1: Initialize pointers** | ||
| - `left = 0` (start of string) | ||
| - `right = len(s) - 1` (end of string) | ||
| 
  | 
||
| 2. **Loop starts (`left < right` is `0 < 29` which is True):** | ||
| **Step 2: Iterate while pointers don't cross** | ||
| While `left < right`: | ||
| - Skip non-alphanumeric characters from left | ||
| - Skip non-alphanumeric characters from right | ||
| - Compare characters (case-insensitive) | ||
| - If they don't match, return `false` | ||
| - Move pointers inward | ||
| 
  | 
||
| * **Find alphanumeric `s[left]`:** | ||
| * `s[0]` is 'A'. `s[0].isalnum()` is True. `left` stays `0`. | ||
| * **Find alphanumeric `s[right]`:** | ||
| * `s[29]` is 'a'. `s[29].isalnum()` is True. `right` stays `29`. | ||
| * **Compare:** | ||
| * `s[0].lower()` ('a') vs `s[29].lower()` ('a'). They match. | ||
| * **Move pointers:** `left` becomes `1`, `right` becomes `28`. | ||
| **Step 3: Return result** | ||
| - If we reach the end without finding a mismatch, return `true` | ||
| 
  | 
||
| 3. **Loop continues (`left < right` is `1 < 28` which is True):** | ||
| **Example walkthrough:** | ||
| Let's trace through the first example: | ||
| 
  | 
||
| * `s[1]` is ' '. `s[1].isalnum()` is False. `left` increments to `2`. | ||
| * `s[2]` is 'm'. `s[2].isalnum()` is True. `left` stays `2`. | ||
| * `s[28]` is 'm'. `s[28].isalnum()` is True. `right` stays `28`. | ||
| * **Compare:** | ||
| * `s[2].lower()` ('m') vs `s[28].lower()` ('m'). They match. | ||
| * **Move pointers:** `left` becomes `3`, `right` becomes `27`. | ||
| ``` | ||
| s = "A man, a plan, a canal: Panama" | ||
| 
  | 
||
| Initial state: | ||
| left = 0, right = 29 | ||
| 
  | 
||
| 4. **Loop continues (`left < right` is `3 < 27` which is True):** | ||
| Step 1: left points to 'A', right points to 'a' | ||
| Both are alphanumeric, compare 'A' == 'a' (case-insensitive) ✓ | ||
| left = 1, right = 28 | ||
| 
  | 
||
| * `s[3]` is 'a'. `left` stays `3`. | ||
| * `s[27]` is 'a'. `right` stays `27`. | ||
| * **Compare:** `s[3].lower()` ('a') vs `s[27].lower()` ('a'). Match. | ||
| * **Move pointers:** `left` becomes `4`, `right` becomes `26`. | ||
| Step 2: left points to ' ' (space), right points to 'm' | ||
| Skip space from left: left = 2 | ||
| Compare 'm' == 'm' ✓ | ||
| left = 3, right = 27 | ||
| 
  | 
||
| ...This process repeats. Non-alphanumeric characters (spaces, commas, colons) will cause one of the inner `while` loops to advance a pointer. For example, if `s[left]` is a space, `left` will increment until it finds an alphanumeric character. The same happens for `right` moving inwards. | ||
| Step 3: left points to 'a', right points to 'a' | ||
| Compare 'a' == 'a' ✓ | ||
| left = 4, right = 26 | ||
| 
  | 
||
| Eventually, if `s = "A man, a plan, a canal: Panama"`, all corresponding alphanumeric characters will match. The pointers will cross or meet (`left >= right`). When `left` is no longer less than `right`, the `while left < right` loop terminates. | ||
| ... (continuing this process) | ||
| 
  | 
||
| At this point, since no mismatches were found, the function returns `true`. | ||
| Final comparison: All characters match | ||
| Result: Return true | ||
| ``` | ||
| 
  | 
||
| Example: `s = "race a car"` | ||
| 1. Initialize `left = 0`, `right = 9`. | ||
| `s[0]` is 'r', `s[9]` is 'r'. | ||
| 2. Loop: | ||
| * `left=0` ('r'), `right=9` ('r'). Match. `left=1`, `right=8`. | ||
| * `left=1` ('a'), `right=8` ('a'). Match. `left=2`, `right=7`. | ||
| * `left=2` ('c'), `right=7` ('c'). Match. `left=3`, `right=6`. | ||
| * `left=3` ('e'), `right=6` ('a'). **Mismatch!** ('e' != 'a'). Return `false`. | ||
| > **Note:** The two-pointer approach is efficient because we only need to traverse the string once. We don't need to create a new cleaned string - we can process it in-place by skipping invalid characters. | ||
| 
  | 
||
| ### Solution | ||
| 
  | 
||
| ```python | ||
| class Solution: | ||
| def isPalindrome(self, s: str) -> bool: | ||
| # Initialize two pointers | ||
| left, right = 0, len(s) - 1 | ||
| 
  | 
||
| # Iterate while pointers don't cross | ||
| while left < right: | ||
| # Skip non-alphanumeric characters from left | ||
| while left < right and not s[left].isalnum(): | ||
| left += 1 | ||
| 
  | 
||
| # Skip non-alphanumeric characters from right | ||
| while left < right and not s[right].isalnum(): | ||
| right -= 1 | ||
| 
  | 
||
| # Compare characters (case-insensitive) | ||
| if s[left].lower() != s[right].lower(): | ||
| return False | ||
| 
  | 
||
| # Move pointers inward | ||
| left += 1 | ||
| right -= 1 | ||
| 
  | 
||
| # If we reach here, it's a palindrome | ||
| return True | ||
| ``` | ||
| 
  | 
||
| This approach is efficient because it processes each character at most a constant number of times (once by `left`, once by `right`). So, time complexity is O(n). Space complexity is O(1) because you're only using a couple of pointers and not creating new large data structures. | ||
| **Time Complexity:** O(n) - we visit each character at most once | ||
| **Space Complexity:** O(1) - we only use a constant amount of extra space | 
 
 
 
 30 changes: 17 additions & 13 deletions
 
 
 
 solutions/125/01.py
 
 
 
 
  
 
 
 
 
 
 
 This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
 Learn more about bidirectional Unicode characters
 
 
 
 
 | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -1,21 +1,25 @@ | ||
| def is_palindrome(s: str) -> bool: | ||
| left = 0 # Pointer starting from the beginning of the string | ||
| right = len(s) - 1 # Pointer starting from the end of the string | ||
| class Solution: | ||
| def isPalindrome(self, s: str) -> bool: | ||
| # Initialize two pointers | ||
| left, right = 0, len(s) - 1 | ||
| 
  | 
||
| while left < right: | ||
| while left < right and not s[left].isalnum(): | ||
| left += 1 | ||
| # Iterate while pointers don't cross | ||
| while left < right: | ||
| # Skip non-alphanumeric characters from left | ||
| while left < right and not s[left].isalnum(): | ||
| left += 1 | ||
| 
  | 
||
| while left < right and not s[right].isalnum(): | ||
| right -= 1 | ||
| # Skip non-alphanumeric characters from right | ||
| while left < right and not s[right].isalnum(): | ||
| right -= 1 | ||
| 
  | 
||
| if left < right: | ||
| # Compare characters (case-insensitive) | ||
| if s[left].lower() != s[right].lower(): | ||
| res = False | ||
| return res | ||
| return False | ||
| 
  | 
||
| # Move pointers inward | ||
| left += 1 | ||
| right -= 1 | ||
| 
  | 
||
| res = True | ||
| return res | ||
| # If we reach here, it's a palindrome | ||
| return True | 
 Add this suggestion to a batch that can be applied as a single commit.
 This suggestion is invalid because no changes were made to the code.
 Suggestions cannot be applied while the pull request is closed.
 Suggestions cannot be applied while viewing a subset of changes.
 Only one suggestion per line can be applied in a batch.
 Add this suggestion to a batch that can be applied as a single commit.
 Applying suggestions on deleted lines is not supported.
 You must change the existing code in this line in order to create a valid suggestion.
 Outdated suggestions cannot be applied.
 This suggestion has been applied or marked resolved.
 Suggestions cannot be applied from pending reviews.
 Suggestions cannot be applied on multi-line comments.
 Suggestions cannot be applied while the pull request is queued to merge.
 Suggestion cannot be applied right now. Please check back later.