diff --git a/DIRECTORY.md b/DIRECTORY.md index 5bedba9e..c04945a5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -218,6 +218,18 @@ * [Test Next Permutation](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/next_permutation/test_next_permutation.py) * Pair With Sum In Array * [Test Pair With Sum In Array](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/pair_with_sum_in_array/test_pair_with_sum_in_array.py) + * Palindrome + * Largest Palindrome Product + * [Test Largest Palindrome Product](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/palindrome/largest_palindrome_product/test_largest_palindrome_product.py) + * [Longest Palindrome](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/palindrome/longest_palindrome.py) + * [Longest Palindromic Substring](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/palindrome/longest_palindromic_substring.py) + * [Palindrome Index](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/palindrome/palindrome_index.py) + * [Palindrome Pairs](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/palindrome/palindrome_pairs.py) + * Permutation Palindrome + * [Test Permutation Palindrome](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/palindrome/permutation_palindrome/test_permutation_palindrome.py) + * [Test Palindrome](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/palindrome/test_palindrome.py) + * [Test Palindrome Index](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/palindrome/test_palindrome_index.py) + * [Test Palindrome Pairs](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/palindrome/test_palindrome_pairs.py) * Rain Water Trapped * [Test Trapped Rain Water](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/rain_water_trapped/test_trapped_rain_water.py) * Reverse Array @@ -844,18 +856,6 @@ * [Test Max Vowels In Substring](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/max_vowels_in_substring/test_max_vowels_in_substring.py) * Merge Strings * [Test Merge Strings](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/merge_strings/test_merge_strings.py) - * Palindrome - * Largest Palindrome Product - * [Test Largest Palindrome Product](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/palindrome/largest_palindrome_product/test_largest_palindrome_product.py) - * [Longest Palindrome](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/palindrome/longest_palindrome.py) - * [Longest Palindromic Substring](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/palindrome/longest_palindromic_substring.py) - * [Palindrome Index](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/palindrome/palindrome_index.py) - * [Palindrome Pairs](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/palindrome/palindrome_pairs.py) - * Permutation Palindrome - * [Test Permutation Palindrome](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/palindrome/permutation_palindrome/test_permutation_palindrome.py) - * [Test Palindrome](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/palindrome/test_palindrome.py) - * [Test Palindrome Index](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/palindrome/test_palindrome_index.py) - * [Test Palindrome Pairs](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/palindrome/test_palindrome_pairs.py) * Pangram * [Test Pangram Checker](https://github.com/BrianLusina/PythonSnips/blob/master/pystrings/pangram/test_pangram_checker.py) * Parenthesis diff --git a/algorithms/backtracking/partition_string/__init__.py b/algorithms/backtracking/partition_string/__init__.py index b9a91626..92f5deb7 100644 --- a/algorithms/backtracking/partition_string/__init__.py +++ b/algorithms/backtracking/partition_string/__init__.py @@ -1,5 +1,5 @@ from typing import List -from pystrings.palindrome import is_palindrome +from algorithms.two_pointers.palindrome import is_palindrome def partition(s: str) -> List[List[str]]: diff --git a/algorithms/greedy/jump_game/README.md b/algorithms/greedy/jump_game/README.md index f796ebf7..f5ef9ea1 100644 --- a/algorithms/greedy/jump_game/README.md +++ b/algorithms/greedy/jump_game/README.md @@ -67,17 +67,17 @@ target remains beyond index 0, then no path exists from the start to the end, an #### Algorithm -1. We begin by setting the last index of the array as our initial target using the variable target = len(nums) - 1. This +1. We begin by setting the last index of the array as our initial target using the variable `target = len(nums) - 1`. This target represents the position we are trying to reach, starting from the end and working backward. By initializing the target this way, we define our goal: to find out if there is any index i from which the target is reachable based on the value at that position, nums[i]. This also sets the stage for updating the target if such an index is found. -2. Next, we loop backward through the array using for i in range(len(nums) - 2, -1, -1). Here, i represents the current +2. Next, we loop backward through the array using `for i in range(len(nums) - 2, -1, -1)`. Here, `i` represents the current index we are analyzing. At each index i, the value nums[i] tells us how far we can jump forward from that position. - By checking whether i + nums[i] >= target, we determine whether it can reach the current target from index i. This + By checking whether `i + nums[i] >= target`, we determine whether it can reach the current target from index i. This step allows us to use the jump range at each position to decide if it can potentially lead us to the end. -3. If the condition i + nums[i] >= target is TRUE, the current index i can jump far enough to reach the current target. +3. If the condition `i + nums[i] >= target` is TRUE, the current index i can jump far enough to reach the current target. In that case, we update target = i, effectively saying, “Now we just need to reach index i instead.” If the condition fails, we move back in the array one step further and try again with the previous index. We repeat this process until we either: diff --git a/pystrings/palindrome/README.md b/algorithms/two_pointers/palindrome/README.md similarity index 67% rename from pystrings/palindrome/README.md rename to algorithms/two_pointers/palindrome/README.md index f0c84862..c3d76fac 100755 --- a/pystrings/palindrome/README.md +++ b/algorithms/two_pointers/palindrome/README.md @@ -196,3 +196,82 @@ As only a few variables are used, the space complexity of this solution is O(1). - String - Dynamic Programming - Two Pointers + +--- + +## Valid Palindrome II + +Write a function that takes a string as input and checks whether it can be a valid palindrome by removing at most one +character from it. + +### Constraints + +- 1 <= `s.length` <= 10^5 +- The string only consists of English letters + +### Examples + +Example 1: + +```text +Input: s = "aba" +Output: true +``` + +Example 2: + +```text +Input: s = "abca" +Output: true +Explanation: You could delete the character 'c'. +``` + +Example 3: + +```text +Input: s = "abc" +Output: false +``` + +### Solution + +The algorithm uses a two-pointer technique to determine whether a string is a palindrome or can be transformed into it +by removing at most one character, where one pointer starts at the beginning and the other at the end. As the pointers +move toward each other, they compare the corresponding characters. If all pairs match, the string is a palindrome. +However, if a mismatch is detected, the algorithm explores two possibilities: removing the character at either pointer +and checking if the resulting substring forms a palindrome. If either check passes, the function returns TRUE; otherwise, +it returns FALSE. + +The algorithm consists of two functions: + +- `is_substring_palindrome(left, right)`: This helper function checks if a substring of the input string, defined by + `left` and `right` indexes, is a palindrome. It uses a two-pointer approach, comparing characters from both ends + inward. If any mismatch is found, it returns FALSE; otherwise, it returns TRUE. +- `is_valid_palindrome_with_one_char_removal(string)`: This function checks if the entire string is a palindrome or can + become one by removing one character. It initializes two pointers, `left_pointer` at the start and `right_pointer` at + end of the string: + - If the characters at the `left_pointer` and `right_pointer` are the same, it moves both pointers inward. + - If the characters differ, it checks two cases by calling is_substring_palindrome: + - The substring from `left_pointer + 1` to `right_pointer` + - The substring from `left_pointer` to `right_pointer - 1` + - If either case returns TRUE, the function returns TRUE; otherwise, it returns FALSE. + +If the traversal completes without finding a mismatch, the string is a palindrome, and the function returns TRUE. + +![Solution 1](./images/solutions/is_valid_palindrome_with_one_char_removal_solution_1.png) +![Solution 2](./images/solutions/is_valid_palindrome_with_one_char_removal_solution_2.png) +![Solution 3](./images/solutions/is_valid_palindrome_with_one_char_removal_solution_3.png) +![Solution 4](./images/solutions/is_valid_palindrome_with_one_char_removal_solution_4.png) +![Solution 5](./images/solutions/is_valid_palindrome_with_one_char_removal_solution_5.png) +![Solution 6](./images/solutions/is_valid_palindrome_with_one_char_removal_solution_6.png) +![Solution 7](./images/solutions/is_valid_palindrome_with_one_char_removal_solution_7.png) +![Solution 8](./images/solutions/is_valid_palindrome_with_one_char_removal_solution_8.png) +![Solution 9](./images/solutions/is_valid_palindrome_with_one_char_removal_solution_9.png) + +#### Time Complexity + +The time complexity of the solution above is O(n), where n is the length of the string + +#### Space Complexity + +The space complexity of solution above is O(1). diff --git a/pystrings/palindrome/__init__.py b/algorithms/two_pointers/palindrome/__init__.py similarity index 81% rename from pystrings/palindrome/__init__.py rename to algorithms/two_pointers/palindrome/__init__.py index 97d0cbb8..50447062 100755 --- a/pystrings/palindrome/__init__.py +++ b/algorithms/two_pointers/palindrome/__init__.py @@ -162,3 +162,45 @@ def is_palindrome_number_2(x: int) -> bool: left += 1 right -= 1 return True + + +def is_valid_palindrome_with_one_char_removal(s: str) -> bool: + """ + Checks if a string s can become a valid palindrome with at most one character removal. Returns True if it can, False + otherwise + Args: + s (str): string to check + Returns: + bool: True if after removal of at most one character, the string becomes a valid palindrome, otherwise False + """ + left_pointer = 0 + right_pointer = len(s) - 1 + + def is_substring_palindrome(left: int, right: int) -> bool: + """ + Checks if a given substring is a valid palindrome + Args: + left(int): the left index on the string to check + right(int): the right index on the string to check + Returns: + bool: True if the substring is a valid palindrome, else False + """ + while left < right: + if s[left] != s[right]: + return False + left += 1 + right -= 1 + return True + + while left_pointer < right_pointer: + if s[left_pointer] != s[right_pointer]: + # validate that the substrings are valid palindromes + return is_substring_palindrome( + left_pointer + 1, right_pointer + ) or is_substring_palindrome(left_pointer, right_pointer - 1) + + # move the pointers as the characters are equal + left_pointer += 1 + right_pointer -= 1 + + return True diff --git a/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_1.png b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_1.png new file mode 100644 index 00000000..d2da609c Binary files /dev/null and b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_1.png differ diff --git a/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_2.png b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_2.png new file mode 100644 index 00000000..696097aa Binary files /dev/null and b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_2.png differ diff --git a/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_3.png b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_3.png new file mode 100644 index 00000000..2fde190c Binary files /dev/null and b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_3.png differ diff --git a/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_4.png b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_4.png new file mode 100644 index 00000000..6bf1b3ab Binary files /dev/null and b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_4.png differ diff --git a/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_5.png b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_5.png new file mode 100644 index 00000000..64e6720a Binary files /dev/null and b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_5.png differ diff --git a/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_6.png b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_6.png new file mode 100644 index 00000000..8df885bf Binary files /dev/null and b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_6.png differ diff --git a/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_7.png b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_7.png new file mode 100644 index 00000000..120cdbee Binary files /dev/null and b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_7.png differ diff --git a/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_8.png b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_8.png new file mode 100644 index 00000000..13efa705 Binary files /dev/null and b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_8.png differ diff --git a/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_9.png b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_9.png new file mode 100644 index 00000000..5d035202 Binary files /dev/null and b/algorithms/two_pointers/palindrome/images/solutions/is_valid_palindrome_with_one_char_removal_solution_9.png differ diff --git a/pystrings/palindrome/largest_palindrome_product/README.md b/algorithms/two_pointers/palindrome/largest_palindrome_product/README.md similarity index 100% rename from pystrings/palindrome/largest_palindrome_product/README.md rename to algorithms/two_pointers/palindrome/largest_palindrome_product/README.md diff --git a/pystrings/palindrome/largest_palindrome_product/__init__.py b/algorithms/two_pointers/palindrome/largest_palindrome_product/__init__.py similarity index 100% rename from pystrings/palindrome/largest_palindrome_product/__init__.py rename to algorithms/two_pointers/palindrome/largest_palindrome_product/__init__.py diff --git a/pystrings/palindrome/largest_palindrome_product/test_largest_palindrome_product.py b/algorithms/two_pointers/palindrome/largest_palindrome_product/test_largest_palindrome_product.py similarity index 100% rename from pystrings/palindrome/largest_palindrome_product/test_largest_palindrome_product.py rename to algorithms/two_pointers/palindrome/largest_palindrome_product/test_largest_palindrome_product.py diff --git a/pystrings/palindrome/longest_palindrome.py b/algorithms/two_pointers/palindrome/longest_palindrome.py similarity index 100% rename from pystrings/palindrome/longest_palindrome.py rename to algorithms/two_pointers/palindrome/longest_palindrome.py diff --git a/pystrings/palindrome/longest_palindromic_substring.py b/algorithms/two_pointers/palindrome/longest_palindromic_substring.py similarity index 100% rename from pystrings/palindrome/longest_palindromic_substring.py rename to algorithms/two_pointers/palindrome/longest_palindromic_substring.py diff --git a/pystrings/palindrome/palindrome_index.py b/algorithms/two_pointers/palindrome/palindrome_index.py similarity index 100% rename from pystrings/palindrome/palindrome_index.py rename to algorithms/two_pointers/palindrome/palindrome_index.py diff --git a/pystrings/palindrome/palindrome_pairs.py b/algorithms/two_pointers/palindrome/palindrome_pairs.py similarity index 100% rename from pystrings/palindrome/palindrome_pairs.py rename to algorithms/two_pointers/palindrome/palindrome_pairs.py diff --git a/pystrings/palindrome/permutation_palindrome/README.md b/algorithms/two_pointers/palindrome/permutation_palindrome/README.md similarity index 100% rename from pystrings/palindrome/permutation_palindrome/README.md rename to algorithms/two_pointers/palindrome/permutation_palindrome/README.md diff --git a/pystrings/palindrome/permutation_palindrome/__init__.py b/algorithms/two_pointers/palindrome/permutation_palindrome/__init__.py similarity index 100% rename from pystrings/palindrome/permutation_palindrome/__init__.py rename to algorithms/two_pointers/palindrome/permutation_palindrome/__init__.py diff --git a/pystrings/palindrome/permutation_palindrome/test_permutation_palindrome.py b/algorithms/two_pointers/palindrome/permutation_palindrome/test_permutation_palindrome.py similarity index 100% rename from pystrings/palindrome/permutation_palindrome/test_permutation_palindrome.py rename to algorithms/two_pointers/palindrome/permutation_palindrome/test_permutation_palindrome.py diff --git a/pystrings/palindrome/test_palindrome.py b/algorithms/two_pointers/palindrome/test_palindrome.py similarity index 85% rename from pystrings/palindrome/test_palindrome.py rename to algorithms/two_pointers/palindrome/test_palindrome.py index c14a601a..efc1b283 100755 --- a/pystrings/palindrome/test_palindrome.py +++ b/algorithms/two_pointers/palindrome/test_palindrome.py @@ -1,17 +1,17 @@ -# -*- coding: utf-8 -*- import unittest from random import choice, randint from string import ascii_letters from typing import Union from parameterized import parameterized -from pystrings.palindrome import ( +from algorithms.two_pointers.palindrome import ( is_palindrome, smallest_palindrome, largest_palindrome, is_palindrome_number, is_palindrome_number_2, + is_valid_palindrome_with_one_char_removal, ) -from pystrings.palindrome.longest_palindrome import longest_palindrome +from algorithms.two_pointers.palindrome.longest_palindrome import longest_palindrome class LongestPalindromeTests(unittest.TestCase): @@ -141,5 +141,24 @@ def test_largest_palindrome_from_triple_digit_factors(self): self.assertEqual({913, 993}, set(factors)) +IS_PALINDROME_WITH_ONE_CHAR_REMOVAL_TEST_CASES = [ + ("aba", True), + ("abca", True), + ("abc", False), + ("abccbxa", True), + ("madame", True), + ("dead", True), + ("tebbem", False), + ("eeccccbebaeeabebccceea", False), +] + + +class IsPalindromeWithOneCharRemoval(unittest.TestCase): + @parameterized.expand(IS_PALINDROME_WITH_ONE_CHAR_REMOVAL_TEST_CASES) + def test_is_palindrome_with_one_char_removal(self, s: str, expected: bool): + actual = is_valid_palindrome_with_one_char_removal(s) + self.assertEqual(expected, actual) + + if __name__ == "__main__": unittest.main() diff --git a/pystrings/palindrome/test_palindrome_index.py b/algorithms/two_pointers/palindrome/test_palindrome_index.py similarity index 100% rename from pystrings/palindrome/test_palindrome_index.py rename to algorithms/two_pointers/palindrome/test_palindrome_index.py diff --git a/pystrings/palindrome/test_palindrome_pairs.py b/algorithms/two_pointers/palindrome/test_palindrome_pairs.py similarity index 100% rename from pystrings/palindrome/test_palindrome_pairs.py rename to algorithms/two_pointers/palindrome/test_palindrome_pairs.py diff --git a/datastructures/trees/binary/README.md b/datastructures/trees/binary/README.md index 14a5aec6..c702c89a 100644 --- a/datastructures/trees/binary/README.md +++ b/datastructures/trees/binary/README.md @@ -36,12 +36,12 @@ When they meet, that meeting point is their lowest common ancestor. The steps of the algorithm are as follows: -1. Initialize two pointers: ptr1 starting at p and ptr2 starting at q. -2. While ptr1 and ptr2 are not pointing to the same node: - - If ptr1 has a parent, move ptr1 to ptr1.parent; otherwise, set ptr1 = q. - - If ptr2 has a parent, move ptr2 to ptr2.parent; otherwise, set ptr2 = p. +1. Initialize two pointers: `ptr1` starting at `p` and `ptr2` starting at `q`. +2. While `ptr1` and `ptr2` are not pointing to the same node: + - If `ptr1` has a parent, move `ptr1` to `ptr1.parent;` otherwise, set `ptr1 = q`. + - If `ptr2` has a parent, move `ptr2` to `ptr2.parent`; otherwise, set `ptr2 = p`. -3. When ptr1 == ptr2, return ptr1. This node is the lowest common ancestor (LCA) of p and q. +3. When `ptr1 == ptr2`, return `ptr1`. This node is the lowest common ancestor (LCA) of p and q. Let’s look at the following illustration to get a better understanding of the solution: diff --git a/tests/pystrings/test_longest_palindrome.py b/tests/pystrings/test_longest_palindrome.py index 3515eb85..6242dd20 100644 --- a/tests/pystrings/test_longest_palindrome.py +++ b/tests/pystrings/test_longest_palindrome.py @@ -1,6 +1,9 @@ import unittest -from pystrings.palindrome import longest_palindrome_one, longest_palindrome_two +from algorithms.two_pointers.palindrome import ( + longest_palindrome_one, + longest_palindrome_two, +) class LongestPalindromeV1TestCases(unittest.TestCase):