diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 171afc3..13c351d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: check-added-large-files args: ["--maxkb=20000"] - repo: https://github.com/gitleaks/gitleaks - rev: v8.29.1 + rev: v8.30.0 hooks: - name: gitleaks id: gitleaks diff --git a/leetcode/palindrome_partitioning/README.md b/leetcode/palindrome_partitioning/README.md new file mode 100644 index 0000000..5b5c0f5 --- /dev/null +++ b/leetcode/palindrome_partitioning/README.md @@ -0,0 +1,32 @@ +# Palindrome Partitioning + +**Difficulty:** Medium +**Topics:** String, Dynamic Programming, Backtracking +**Tags:** grind-75 + +**LeetCode:** [Problem 131](https://leetcode.com/problems/palindrome-partitioning/description/) + +## Problem Description + +Given a string `s`, partition `s` such that every substring of the partition is a **palindrome**. Return _all possible palindrome partitioning of `s`_. + +## Examples + +### Example 1: + +``` +Input: s = "aab" +Output: [["a","a","b"],["aa","b"]] +``` + +### Example 2: + +``` +Input: s = "a" +Output: [["a"]] +``` + +## Constraints + +- `1 <= s.length <= 16` +- `s` contains only lowercase English letters. diff --git a/leetcode/palindrome_partitioning/__init__.py b/leetcode/palindrome_partitioning/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/palindrome_partitioning/helpers.py b/leetcode/palindrome_partitioning/helpers.py new file mode 100644 index 0000000..c488424 --- /dev/null +++ b/leetcode/palindrome_partitioning/helpers.py @@ -0,0 +1,15 @@ +def run_partition(solution_class: type, s: str): + implementation = solution_class() + return implementation.partition(s) + + +def assert_partition(result: list[list[str]], expected: list[list[str]]) -> bool: + # Sort inner lists and outer list for comparison + # Note: Inner lists are partitions (lists of strings), order of partitions doesn't matter + # Order of strings within a partition DOES matter (it must reconstruct s) + # But wait, the problem says "partition s", so the order of substrings must match the order in s. + # So we only need to sort the outer list of partitions. + result_sorted = sorted(result) + expected_sorted = sorted(expected) + assert result_sorted == expected_sorted + return True diff --git a/leetcode/palindrome_partitioning/playground.py b/leetcode/palindrome_partitioning/playground.py new file mode 100644 index 0000000..a89cd84 --- /dev/null +++ b/leetcode/palindrome_partitioning/playground.py @@ -0,0 +1,29 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.18.1 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_partition, run_partition +from solution import Solution + +# %% +# Example test case +s = "aab" +expected = [["a", "a", "b"], ["aa", "b"]] + +# %% +result = run_partition(Solution, s) +result + +# %% +assert_partition(result, expected) diff --git a/leetcode/palindrome_partitioning/solution.py b/leetcode/palindrome_partitioning/solution.py new file mode 100644 index 0000000..76f56d2 --- /dev/null +++ b/leetcode/palindrome_partitioning/solution.py @@ -0,0 +1,22 @@ +class Solution: + # Time: O(N * 2^N) + # Space: O(N) + def partition(self, s: str) -> list[list[str]]: + result: list[list[str]] = [] + self._backtrack(s, 0, [], result) + return result + + def _backtrack(self, s: str, start: int, path: list[str], result: list[list[str]]) -> None: + if start == len(s): + result.append(path[:]) + return + + for end in range(start + 1, len(s) + 1): + substring = s[start:end] + if self._is_palindrome(substring): + path.append(substring) + self._backtrack(s, end, path, result) + path.pop() + + def _is_palindrome(self, s: str) -> bool: + return s == s[::-1] diff --git a/leetcode/palindrome_partitioning/test_solution.py b/leetcode/palindrome_partitioning/test_solution.py new file mode 100644 index 0000000..bd56078 --- /dev/null +++ b/leetcode/palindrome_partitioning/test_solution.py @@ -0,0 +1,33 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_partition, run_partition +from .solution import Solution + + +class TestPalindromePartitioning: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, expected", + [ + ("aab", [["a", "a", "b"], ["aa", "b"]]), + ("a", [["a"]]), + ("ab", [["a", "b"]]), + ("aa", [["a", "a"], ["aa"]]), + ("abc", [["a", "b", "c"]]), + ("aba", [["a", "b", "a"], ["aba"]]), + ("aaa", [["a", "a", "a"], ["a", "aa"], ["aa", "a"], ["aaa"]]), + ("abba", [["a", "b", "b", "a"], ["a", "bb", "a"], ["abba"]]), + ("zz", [["z", "z"], ["zz"]]), + ("efe", [["e", "f", "e"], ["efe"]]), + ("xyx", [["x", "y", "x"], ["xyx"]]), + ("noon", [["n", "o", "o", "n"], ["n", "oo", "n"], ["noon"]]), + ], + ) + def test_partition(self, s: str, expected: list[list[str]]): + result = run_partition(Solution, s) + assert_partition(result, expected) diff --git a/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json b/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json new file mode 100644 index 0000000..45e0a40 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/palindrome_partitioning.json @@ -0,0 +1,84 @@ +{ + "problem_name": "palindrome_partitioning", + "solution_class_name": "Solution", + "problem_number": "131", + "problem_title": "Palindrome Partitioning", + "difficulty": "Medium", + "topics": "String, Dynamic Programming, Backtracking", + "readme_description": "Given a string `s`, partition `s` such that every substring of the partition is a **palindrome**. Return *all possible palindrome partitioning of `s`*.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: s = \"aab\"\nOutput: [[\"a\",\"a\",\"b\"],[\"aa\",\"b\"]]\n```" + }, + { + "content": "```\nInput: s = \"a\"\nOutput: [[\"a\"]]\n```" + } + ] + }, + "readme_constraints": "- `1 <= s.length <= 16`\n- `s` contains only lowercase English letters.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "partition", + "helpers_run_signature": "(solution_class: type, s: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.partition(s)", + "helpers_assert_name": "partition", + "helpers_assert_signature": "(result: list[list[str]], expected: list[list[str]]) -> bool", + "helpers_assert_body": " # Sort inner lists and outer list for comparison\n # Note: Inner lists are partitions (lists of strings), order of partitions doesn't matter\n # Order of strings within a partition DOES matter (it must reconstruct s)\n # But wait, the problem says \"partition s\", so the order of substrings must match the order in s.\n # So we only need to sort the outer list of partitions.\n result_sorted = sorted(result)\n expected_sorted = sorted(expected)\n assert result_sorted == expected_sorted\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_partition, run_partition\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "PalindromePartitioning", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "partition", + "signature": "(self, s: str) -> list[list[str]]", + "body": " # TODO: Implement partition\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [ + { + "name": "setup_method", + "parameters": "", + "body": "self.solution = Solution()" + } + ] + }, + "_test_methods": { + "list": [ + { + "name": "test_partition", + "signature": "(self, s: str, expected: list[list[str]])", + "parametrize": "s, expected", + "test_cases": { + "list": [ + "('aab', [['a', 'a', 'b'], ['aa', 'b']])", + "('a', [['a']])", + "('ab', [['a', 'b']])", + "('aa', [['a', 'a'], ['aa']])", + "('abc', [['a', 'b', 'c']])", + "('aba', [['a', 'b', 'a'], ['aba']])", + "('aaa', [['a', 'a', 'a'], ['a', 'aa'], ['aa', 'a'], ['aaa']])", + "('abba', [['a', 'b', 'b', 'a'], ['a', 'bb', 'a'], ['abba']])", + "('zz', [['z', 'z'], ['zz']])", + "('efe', [['e', 'f', 'e'], ['efe']])", + "('xyx', [['x', 'y', 'x'], ['xyx']])", + "('noon', [['n', 'o', 'o', 'n'], ['n', 'oo', 'n'], ['noon']])" + ] + }, + "body": " result = run_partition(Solution, s)\n assert_partition(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_partition, assert_partition\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = 'aab'\nexpected = [['a', 'a', 'b'], ['aa', 'b']]", + "playground_run": "result = run_partition(Solution, s)\nresult", + "playground_assert": "assert_partition(result, expected)" +}