From 6b1dc24dd97118df690c99d104135f2ce7b98a70 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Wed, 10 Sep 2025 20:34:46 +0700 Subject: [PATCH 01/39] feat: add partition_equal_subset_sum --- .../json/partition_equal_subset_sum.json | 47 +++ Makefile | 2 +- leetcode/partition_equal_subset_sum/README.md | 36 ++ .../partition_equal_subset_sum/__init__.py | 0 .../playground.ipynb | 79 ++++ .../partition_equal_subset_sum/solution.py | 92 +++++ leetcode/partition_equal_subset_sum/tests.py | 56 +++ poetry.lock | 361 +++++++++--------- 8 files changed, 500 insertions(+), 173 deletions(-) create mode 100644 .templates/leetcode/json/partition_equal_subset_sum.json create mode 100644 leetcode/partition_equal_subset_sum/README.md create mode 100644 leetcode/partition_equal_subset_sum/__init__.py create mode 100644 leetcode/partition_equal_subset_sum/playground.ipynb create mode 100644 leetcode/partition_equal_subset_sum/solution.py create mode 100644 leetcode/partition_equal_subset_sum/tests.py diff --git a/.templates/leetcode/json/partition_equal_subset_sum.json b/.templates/leetcode/json/partition_equal_subset_sum.json new file mode 100644 index 0000000..4f91312 --- /dev/null +++ b/.templates/leetcode/json/partition_equal_subset_sum.json @@ -0,0 +1,47 @@ +{ + "problem_name": "partition_equal_subset_sum", + "solution_class_name": "Solution", + "problem_number": "416", + "problem_title": "Partition Equal Subset Sum", + "difficulty": "Medium", + "topics": "Array, Dynamic Programming", + "tags": ["grind-75"], + "readme_description": "Given an integer array `nums`, return `true` if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or `false` otherwise.", + "readme_examples": [ + { + "content": "```\nInput: nums = [1,5,11,5]\nOutput: true\n```\n**Explanation:** The array can be partitioned as [1, 5, 5] and [11]." + }, + { + "content": "```\nInput: nums = [1,2,3,5]\nOutput: false\n```\n**Explanation:** The array cannot be partitioned into equal sum subsets." + } + ], + "readme_constraints": "- 1 <= nums.length <= 200\n- 1 <= nums[i] <= 100", + "readme_additional": "", + "solution_imports": "", + "solution_methods": [ + { + "name": "can_partition", + "parameters": "nums: list[int]", + "return_type": "bool", + "dummy_return": "False" + } + ], + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", + "test_class_name": "PartitionEqualSubsetSum", + "test_helper_methods": [ + { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } + ], + "test_methods": [ + { + "name": "test_can_partition", + "parametrize": "nums, expected", + "parametrize_typed": "nums: list[int], expected: bool", + "test_cases": "[([1, 5, 11, 5], True), ([1, 2, 3, 5], False), ([1, 1], True), ([1], False), ([2, 2, 1, 1], True)]", + "body": "result = self.solution.can_partition(nums)\nassert result == expected" + } + ], + "playground_imports": "from solution import Solution", + "playground_test_case": "# Example test case\nnums = [1, 5, 11, 5]\nexpected = True", + "playground_execution": "result = Solution().can_partition(nums)\nresult", + "playground_assertion": "assert result == expected" +} diff --git a/Makefile b/Makefile index ce78169..35646c6 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VERSION = 3.13 -PROBLEM ?= word_break +PROBLEM ?= partition_equal_subset_sum FORCE ?= 0 COMMA := , diff --git a/leetcode/partition_equal_subset_sum/README.md b/leetcode/partition_equal_subset_sum/README.md new file mode 100644 index 0000000..82f48a5 --- /dev/null +++ b/leetcode/partition_equal_subset_sum/README.md @@ -0,0 +1,36 @@ +# Partition Equal Subset Sum + +**Difficulty:** Medium +**Topics:** Array, Dynamic Programming +**Tags:** grind-75 + +**LeetCode:** [Problem 416](https://leetcode.com/problems/partition-equal-subset-sum/description/) + +## Problem Description + +Given an integer array `nums`, return `true` if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or `false` otherwise. + +## Examples + +### Example 1: + +``` +Input: nums = [1,5,11,5] +Output: true +``` + +**Explanation:** The array can be partitioned as [1, 5, 5] and [11]. + +### Example 2: + +``` +Input: nums = [1,2,3,5] +Output: false +``` + +**Explanation:** The array cannot be partitioned into equal sum subsets. + +## Constraints + +- 1 <= nums.length <= 200 +- 1 <= nums[i] <= 100 diff --git a/leetcode/partition_equal_subset_sum/__init__.py b/leetcode/partition_equal_subset_sum/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/partition_equal_subset_sum/playground.ipynb b/leetcode/partition_equal_subset_sum/playground.ipynb new file mode 100644 index 0000000..388fafc --- /dev/null +++ b/leetcode/partition_equal_subset_sum/playground.ipynb @@ -0,0 +1,79 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [1, 5, 11, 5]\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = Solution().can_partition(nums)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [], + "source": [ + "assert result == expected" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/partition_equal_subset_sum/solution.py b/leetcode/partition_equal_subset_sum/solution.py new file mode 100644 index 0000000..9a6bde9 --- /dev/null +++ b/leetcode/partition_equal_subset_sum/solution.py @@ -0,0 +1,92 @@ +class Solution: + # Time: O(n * sum) + # Space: O(sum) + def can_partition(self, nums: list[int]) -> bool: + """ + Example: nums = [1, 5, 11, 5], target = 11 + + Initial: dp = [T, F, F, F, F, F, F, F, F, F, F, F] + 0 1 2 3 4 5 6 7 8 9 10 11 + + After num=1: [T, T, F, F, F, F, F, F, F, F, F, F] + └─┘ (can make sum 1) + + After num=5: [T, T, F, F, F, T, T, F, F, F, F, F] + └─┘ └─┘ └─┘ (can make sums 5,6) + + After num=11:[T, T, F, F, F, T, T, F, F, F, F, T] + └─┘ (target!) + + Backward iteration prevents using same number twice + """ + total = sum(nums) + if total % 2: + return False + + target = total // 2 + dp = [False] * (target + 1) + dp[0] = True + + for num in nums: + for j in range(target, num - 1, -1): + dp[j] = dp[j] or dp[j - num] + + # Early termination: found target sum! + if dp[target]: + return True + + return False + + +class SolutionBitset: + # Time: O(n * sum) + # Space: O(1) + def can_partition(self, nums: list[int]) -> bool: + """ + Example: nums = [1, 5, 11, 5], target = 11 + + Bitset representation (bit position = achievable sum): + + Initial: dp = 1 (binary: 1) + Bits: ...0001 + Sums: {0} + + After num=1: dp |= dp << 1 + a = dp = 1 (bin: 0001) + b = dp << 1 = 2 (bin: 0010) + c = a | b = 3 (bin: 0011) + Sums: {0, 1} + + After num=5: dp |= dp << 5 + a = dp = 3 (bin: 0000011) + b = dp << 5 = 96 (bin: 1100000) + c = a | b = 99 (bin: 1100011) + Sums: {0, 1, 5, 6} + + After num=11: dp |= dp << 11 + a = dp = 99 (bin: 00000001100011) + b = dp << 11 = 202752 (bin: 110001100000000) + c = a | b = 202851 (bin: 110001101100011) + Sums: {0, 1, 5, 6, 11, 12, 16, 17} + + Check: (dp & (1 << 11)) != 0 + a = dp = 202851 (bin: 110001101100011) + b = 1 << 11 = 2048 (bin: 100000000000) + c = a & b = 2048 (bin: 100000000000) + c != 0 → bit 11 is set → True! + """ + total = sum(nums) + if total % 2 != 0: + return False + + target = total // 2 + dp = 1 + + for num in nums: + dp |= dp << num + + # Early termination: found target sum! + if (dp & (1 << target)) != 0: + return True + + return False diff --git a/leetcode/partition_equal_subset_sum/tests.py b/leetcode/partition_equal_subset_sum/tests.py new file mode 100644 index 0000000..303f5f2 --- /dev/null +++ b/leetcode/partition_equal_subset_sum/tests.py @@ -0,0 +1,56 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .solution import Solution, SolutionBitset + + +class TestPartitionEqualSubsetSum: + @pytest.mark.parametrize("solution_class", [Solution, SolutionBitset]) + @pytest.mark.parametrize( + "nums, expected", + [ + ([1, 5, 11, 5], True), + ([1, 2, 3, 5], False), + ([1, 1], True), + ([1], False), + ([2, 2, 1, 1], True), + # Edge cases + ([100], False), + ([1, 1, 1, 1], True), + ([2, 3, 5], True), + # Large numbers + ([50, 50], True), + ([99, 1], False), + ([100, 100], True), + # Multiple solutions + ([1, 2, 3, 4], True), + ([3, 3, 3, 4, 5], True), + # No solution cases + ([1, 3, 5], False), + ([7, 11, 13], False), + # Larger arrays + ([1, 1, 1, 1, 1, 1, 1, 1], True), + ([2, 4, 6, 8, 10], False), + ([5, 10, 15, 20], True), + # Very large numbers (testing Python's arbitrary precision) + ([1000, 1000], True), + ([5000, 5000], True), + ([9999, 1], False), + ([10000, 10000], True), + # Extremely large numbers (beyond 64-bit) + ([2**16, 2**16], True), # 65536 each, target = 65536 + ([2**20, 2**20], True), # 1048576 each, target = 1048576 + ([2**20 + 1, 2**20 - 1], False), # sum = 2^21, target = 2^20, cannot partition + ], + ) + @logged_test + def test_can_partition( + self, + nums: list[int], + expected: bool, + solution_class: type[Solution | SolutionBitset], + ): + solution = solution_class() + result = solution.can_partition(nums) + assert result == expected diff --git a/poetry.lock b/poetry.lock index 3cc5f73..1ee7322 100644 --- a/poetry.lock +++ b/poetry.lock @@ -150,84 +150,101 @@ files = [ [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] markers = "implementation_name == \"pypy\"" files = [ - {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, - {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, + {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, + {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, + {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, + {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, + {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, + {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, + {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, + {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, + {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, + {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, + {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, + {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, + {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, + {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, + {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, + {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, + {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, + {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, + {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, + {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] [package.dependencies] -pycparser = "*" +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} [[package]] name = "cfgv" @@ -614,14 +631,14 @@ test = ["coverage", "pytest (>=7,<8.1)", "pytest-cov", "pytest-mock (>=3)"] [[package]] name = "identify" -version = "2.6.13" +version = "2.6.14" description = "File identification library for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b"}, - {file = "identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32"}, + {file = "identify-2.6.14-py2.py3-none-any.whl", hash = "sha256:11a073da82212c6646b1f39bb20d4483bfb9543bd5566fec60053c4bb309bf2e"}, + {file = "identify-2.6.14.tar.gz", hash = "sha256:663494103b4f717cb26921c52f8751363dc89db64364cd836a9bf1535f53cd6a"}, ] [package.extras] @@ -1280,15 +1297,15 @@ files = [ [[package]] name = "pycparser" -version = "2.22" +version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" groups = ["dev"] markers = "implementation_name == \"pypy\"" files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, + {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, + {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] [[package]] @@ -1330,14 +1347,14 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests [[package]] name = "pytest-cov" -version = "6.2.1" +version = "6.3.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"}, - {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"}, + {file = "pytest_cov-6.3.0-py3-none-any.whl", hash = "sha256:440db28156d2468cafc0415b4f8e50856a0d11faefa38f30906048fe490f1749"}, + {file = "pytest_cov-6.3.0.tar.gz", hash = "sha256:35c580e7800f87ce892e687461166e1ac2bcb8fb9e13aea79032518d6e503ff2"}, ] [package.dependencies] @@ -1477,104 +1494,104 @@ files = [ [[package]] name = "pyzmq" -version = "27.0.2" +version = "27.1.0" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pyzmq-27.0.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:8b32c4636ced87dce0ac3d671e578b3400215efab372f1b4be242e8cf0b11384"}, - {file = "pyzmq-27.0.2-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f9528a4b3e24189cb333a9850fddbbafaa81df187297cfbddee50447cdb042cf"}, - {file = "pyzmq-27.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b02ba0c0b2b9ebe74688002e6c56c903429924a25630804b9ede1f178aa5a3f"}, - {file = "pyzmq-27.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4dc5c9a6167617251dea0d024d67559795761aabb4b7ea015518be898be076"}, - {file = "pyzmq-27.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f1151b33aaf3b4fa9da26f4d696e38eebab67d1b43c446184d733c700b3ff8ce"}, - {file = "pyzmq-27.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4ecfc7999ac44c9ef92b5ae8f0b44fb935297977df54d8756b195a3cd12f38f0"}, - {file = "pyzmq-27.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:31c26a5d0b00befcaeeb600d8b15ad09f5604b6f44e2057ec5e521a9e18dcd9a"}, - {file = "pyzmq-27.0.2-cp310-cp310-win32.whl", hash = "sha256:25a100d2de2ac0c644ecf4ce0b509a720d12e559c77aff7e7e73aa684f0375bc"}, - {file = "pyzmq-27.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a1acf091f53bb406e9e5e7383e467d1dd1b94488b8415b890917d30111a1fef3"}, - {file = "pyzmq-27.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:b38e01f11e9e95f6668dc8a62dccf9483f454fed78a77447507a0e8dcbd19a63"}, - {file = "pyzmq-27.0.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:063845960df76599ad4fad69fa4d884b3ba38304272104fdcd7e3af33faeeb1d"}, - {file = "pyzmq-27.0.2-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:845a35fb21b88786aeb38af8b271d41ab0967985410f35411a27eebdc578a076"}, - {file = "pyzmq-27.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:515d20b5c3c86db95503faa989853a8ab692aab1e5336db011cd6d35626c4cb1"}, - {file = "pyzmq-27.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:862aedec0b0684a5050cdb5ec13c2da96d2f8dffda48657ed35e312a4e31553b"}, - {file = "pyzmq-27.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cb5bcfc51c7a4fce335d3bc974fd1d6a916abbcdd2b25f6e89d37b8def25f57"}, - {file = "pyzmq-27.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:38ff75b2a36e3a032e9fef29a5871e3e1301a37464e09ba364e3c3193f62982a"}, - {file = "pyzmq-27.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a5709abe8d23ca158a9d0a18c037f4193f5b6afeb53be37173a41e9fb885792"}, - {file = "pyzmq-27.0.2-cp311-cp311-win32.whl", hash = "sha256:47c5dda2018c35d87be9b83de0890cb92ac0791fd59498847fc4eca6ff56671d"}, - {file = "pyzmq-27.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:f54ca3e98f8f4d23e989c7d0edcf9da7a514ff261edaf64d1d8653dd5feb0a8b"}, - {file = "pyzmq-27.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:2ef3067cb5b51b090fb853f423ad7ed63836ec154374282780a62eb866bf5768"}, - {file = "pyzmq-27.0.2-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:5da05e3c22c95e23bfc4afeee6ff7d4be9ff2233ad6cb171a0e8257cd46b169a"}, - {file = "pyzmq-27.0.2-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e4520577971d01d47e2559bb3175fce1be9103b18621bf0b241abe0a933d040"}, - {file = "pyzmq-27.0.2-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d7de7bf73165b90bd25a8668659ccb134dd28449116bf3c7e9bab5cf8a8ec9"}, - {file = "pyzmq-27.0.2-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340e7cddc32f147c6c00d116a3f284ab07ee63dbd26c52be13b590520434533c"}, - {file = "pyzmq-27.0.2-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba95693f9df8bb4a9826464fb0fe89033936f35fd4a8ff1edff09a473570afa0"}, - {file = "pyzmq-27.0.2-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:ca42a6ce2d697537da34f77a1960d21476c6a4af3e539eddb2b114c3cf65a78c"}, - {file = "pyzmq-27.0.2-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3e44e665d78a07214b2772ccbd4b9bcc6d848d7895f1b2d7653f047b6318a4f6"}, - {file = "pyzmq-27.0.2-cp312-abi3-win32.whl", hash = "sha256:272d772d116615397d2be2b1417b3b8c8bc8671f93728c2f2c25002a4530e8f6"}, - {file = "pyzmq-27.0.2-cp312-abi3-win_amd64.whl", hash = "sha256:734be4f44efba0aa69bf5f015ed13eb69ff29bf0d17ea1e21588b095a3147b8e"}, - {file = "pyzmq-27.0.2-cp312-abi3-win_arm64.whl", hash = "sha256:41f0bd56d9279392810950feb2785a419c2920bbf007fdaaa7f4a07332ae492d"}, - {file = "pyzmq-27.0.2-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:7f01118133427cd7f34ee133b5098e2af5f70303fa7519785c007bca5aa6f96a"}, - {file = "pyzmq-27.0.2-cp313-cp313-android_24_x86_64.whl", hash = "sha256:e4b860edf6379a7234ccbb19b4ed2c57e3ff569c3414fadfb49ae72b61a8ef07"}, - {file = "pyzmq-27.0.2-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:cb77923ea163156da14295c941930bd525df0d29c96c1ec2fe3c3806b1e17cb3"}, - {file = "pyzmq-27.0.2-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:61678b7407b04df8f9423f188156355dc94d0fb52d360ae79d02ed7e0d431eea"}, - {file = "pyzmq-27.0.2-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3c824b70925963bdc8e39a642672c15ffaa67e7d4b491f64662dd56d6271263"}, - {file = "pyzmq-27.0.2-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4833e02fcf2751975457be1dfa2f744d4d09901a8cc106acaa519d868232175"}, - {file = "pyzmq-27.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b18045668d09cf0faa44918af2a67f0dbbef738c96f61c2f1b975b1ddb92ccfc"}, - {file = "pyzmq-27.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bbbb7e2f3ac5a22901324e7b086f398b8e16d343879a77b15ca3312e8cd8e6d5"}, - {file = "pyzmq-27.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b751914a73604d40d88a061bab042a11d4511b3ddbb7624cd83c39c8a498564c"}, - {file = "pyzmq-27.0.2-cp313-cp313t-win32.whl", hash = "sha256:3e8f833dd82af11db5321c414638045c70f61009f72dd61c88db4a713c1fb1d2"}, - {file = "pyzmq-27.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5b45153cb8eadcab14139970643a84f7a7b08dda541fbc1f6f4855c49334b549"}, - {file = "pyzmq-27.0.2-cp313-cp313t-win_arm64.whl", hash = "sha256:86898f5c9730df23427c1ee0097d8aa41aa5f89539a79e48cd0d2c22d059f1b7"}, - {file = "pyzmq-27.0.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d2b4b261dce10762be5c116b6ad1f267a9429765b493c454f049f33791dd8b8a"}, - {file = "pyzmq-27.0.2-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e4d88b6cff156fed468903006b24bbd85322612f9c2f7b96e72d5016fd3f543"}, - {file = "pyzmq-27.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8426c0ebbc11ed8416a6e9409c194142d677c2c5c688595f2743664e356d9e9b"}, - {file = "pyzmq-27.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565bee96a155fe6452caed5fb5f60c9862038e6b51a59f4f632562081cdb4004"}, - {file = "pyzmq-27.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5de735c745ca5cefe9c2d1547d8f28cfe1b1926aecb7483ab1102fd0a746c093"}, - {file = "pyzmq-27.0.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ea4f498f8115fd90d7bf03a3e83ae3e9898e43362f8e8e8faec93597206e15cc"}, - {file = "pyzmq-27.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d00e81cb0afd672915257a3927124ee2ad117ace3c256d39cd97ca3f190152ad"}, - {file = "pyzmq-27.0.2-cp314-cp314t-win32.whl", hash = "sha256:0f6e9b00d81b58f859fffc112365d50413954e02aefe36c5b4c8fb4af79f8cc3"}, - {file = "pyzmq-27.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:2e73cf3b127a437fef4100eb3ac2ebe6b49e655bb721329f667f59eca0a26221"}, - {file = "pyzmq-27.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:4108785f2e5ac865d06f678a07a1901e3465611356df21a545eeea8b45f56265"}, - {file = "pyzmq-27.0.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:59a50f5eedf8ed20b7dbd57f1c29b2de003940dea3eedfbf0fbfea05ee7f9f61"}, - {file = "pyzmq-27.0.2-cp38-cp38-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:a00e6390e52770ba1ec753b2610f90b4f00e74c71cfc5405b917adf3cc39565e"}, - {file = "pyzmq-27.0.2-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:49d8d05d9844d83cddfbc86a82ac0cafe7ab694fcc9c9618de8d015c318347c3"}, - {file = "pyzmq-27.0.2-cp38-cp38-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3660d85e2b6a28eb2d586dedab9c61a7b7c64ab0d89a35d2973c7be336f12b0d"}, - {file = "pyzmq-27.0.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:bccfee44b392f4d13bbf05aa88d8f7709271b940a8c398d4216fde6b717624ae"}, - {file = "pyzmq-27.0.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:989066d51686415f1da646d6e2c5364a9b084777c29d9d1720aa5baf192366ef"}, - {file = "pyzmq-27.0.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cc283595b82f0db155a52f6462945c7b6b47ecaae2f681746eeea537c95cf8c9"}, - {file = "pyzmq-27.0.2-cp38-cp38-win32.whl", hash = "sha256:ad38daf57495beadc0d929e8901b2aa46ff474239b5a8a46ccc7f67dc01d2335"}, - {file = "pyzmq-27.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:36508466a266cf78bba2f56529ad06eb38ba827f443b47388d420bec14d331ba"}, - {file = "pyzmq-27.0.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:aa9c1c208c263b84386ac25bed6af5672397dc3c232638114fc09bca5c7addf9"}, - {file = "pyzmq-27.0.2-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:795c4884cfe7ea59f2b67d82b417e899afab889d332bfda13b02f8e0c155b2e4"}, - {file = "pyzmq-27.0.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47eb65bb25478358ba3113dd9a08344f616f417ad3ffcbb190cd874fae72b1b1"}, - {file = "pyzmq-27.0.2-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6fc24f00293f10aff04d55ca37029b280474c91f4de2cad5e911e5e10d733b7"}, - {file = "pyzmq-27.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58d4cc9b6b768478adfc40a5cbee545303db8dbc81ba688474e0f499cc581028"}, - {file = "pyzmq-27.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea2f26c5972796e02b222968a21a378d09eb4ff590eb3c5fafa8913f8c2bdf5"}, - {file = "pyzmq-27.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a0621ec020c49fc1b6e31304f1a820900d54e7d9afa03ea1634264bf9387519e"}, - {file = "pyzmq-27.0.2-cp39-cp39-win32.whl", hash = "sha256:1326500792a9cb0992db06bbaf5d0098459133868932b81a6e90d45c39eca99d"}, - {file = "pyzmq-27.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:5ee9560cb1e3094ef01fc071b361121a57ebb8d4232912b6607a6d7d2d0a97b4"}, - {file = "pyzmq-27.0.2-cp39-cp39-win_arm64.whl", hash = "sha256:85e3c6fb0d25ea046ebcfdc2bcb9683d663dc0280645c79a616ff5077962a15b"}, - {file = "pyzmq-27.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d67a0960803a37b60f51b460c58444bc7033a804c662f5735172e21e74ee4902"}, - {file = "pyzmq-27.0.2-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:dd4d3e6a567ffd0d232cfc667c49d0852d0ee7481458a2a1593b9b1bc5acba88"}, - {file = "pyzmq-27.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e558be423631704803bc6a642e2caa96083df759e25fe6eb01f2d28725f80bd"}, - {file = "pyzmq-27.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4c20ba8389f495c7b4f6b896bb1ca1e109a157d4f189267a902079699aaf787"}, - {file = "pyzmq-27.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c5be232f7219414ff672ff7ab8c5a7e8632177735186d8a42b57b491fafdd64e"}, - {file = "pyzmq-27.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e297784aea724294fe95e442e39a4376c2f08aa4fae4161c669f047051e31b02"}, - {file = "pyzmq-27.0.2-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e3659a79ded9745bc9c2aef5b444ac8805606e7bc50d2d2eb16dc3ab5483d91f"}, - {file = "pyzmq-27.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3dba49ff037d02373a9306b58d6c1e0be031438f822044e8767afccfdac4c6b"}, - {file = "pyzmq-27.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de84e1694f9507b29e7b263453a2255a73e3d099d258db0f14539bad258abe41"}, - {file = "pyzmq-27.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f0944d65ba2b872b9fcece08411d6347f15a874c775b4c3baae7f278550da0fb"}, - {file = "pyzmq-27.0.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:05288947797dcd6724702db2056972dceef9963a83041eb734aea504416094ec"}, - {file = "pyzmq-27.0.2-pp38-pypy38_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:dff9198adbb6810ad857f3bfa59b4859c45acb02b0d198b39abeafb9148474f3"}, - {file = "pyzmq-27.0.2-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:849123fd9982c7f63911fdceba9870f203f0f32c953a3bab48e7f27803a0e3ec"}, - {file = "pyzmq-27.0.2-pp38-pypy38_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5ee06945f3069e3609819890a01958c4bbfea7a2b31ae87107c6478838d309e"}, - {file = "pyzmq-27.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6156ad5e8bbe8a78a3f5b5757c9a883b0012325c83f98ce6d58fcec81e8b3d06"}, - {file = "pyzmq-27.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:400f34321e3bd89b1165b91ea6b18ad26042ba9ad0dfed8b35049e2e24eeab9b"}, - {file = "pyzmq-27.0.2-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9cbad4ef12e4c15c94d2c24ecd15a8ed56bf091c62f121a2b0c618ddd4b7402b"}, - {file = "pyzmq-27.0.2-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6b2b74aac3392b8cf508ccb68c980a8555298cd378434a2d065d6ce0f4211dff"}, - {file = "pyzmq-27.0.2-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7db5db88c24cf9253065d69229a148ff60821e5d6f8ff72579b1f80f8f348bab"}, - {file = "pyzmq-27.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8ffe40c216c41756ca05188c3e24a23142334b304f7aebd75c24210385e35573"}, - {file = "pyzmq-27.0.2.tar.gz", hash = "sha256:b398dd713b18de89730447347e96a0240225e154db56e35b6bb8447ffdb07798"}, + {file = "pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4"}, + {file = "pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556"}, + {file = "pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b"}, + {file = "pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e"}, + {file = "pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526"}, + {file = "pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1"}, + {file = "pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386"}, + {file = "pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda"}, + {file = "pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f"}, + {file = "pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32"}, + {file = "pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86"}, + {file = "pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581"}, + {file = "pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f"}, + {file = "pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e"}, + {file = "pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e"}, + {file = "pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2"}, + {file = "pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394"}, + {file = "pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f"}, + {file = "pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97"}, + {file = "pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07"}, + {file = "pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc"}, + {file = "pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113"}, + {file = "pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233"}, + {file = "pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31"}, + {file = "pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28"}, + {file = "pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856"}, + {file = "pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496"}, + {file = "pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd"}, + {file = "pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf"}, + {file = "pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f"}, + {file = "pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5"}, + {file = "pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6"}, + {file = "pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7"}, + {file = "pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05"}, + {file = "pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9"}, + {file = "pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128"}, + {file = "pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39"}, + {file = "pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97"}, + {file = "pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db"}, + {file = "pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c"}, + {file = "pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2"}, + {file = "pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e"}, + {file = "pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a"}, + {file = "pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea"}, + {file = "pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96"}, + {file = "pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d"}, + {file = "pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146"}, + {file = "pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd"}, + {file = "pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a"}, + {file = "pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92"}, + {file = "pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0"}, + {file = "pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7"}, + {file = "pyzmq-27.1.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:18339186c0ed0ce5835f2656cdfb32203125917711af64da64dbaa3d949e5a1b"}, + {file = "pyzmq-27.1.0-cp38-cp38-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:753d56fba8f70962cd8295fb3edb40b9b16deaa882dd2b5a3a2039f9ff7625aa"}, + {file = "pyzmq-27.1.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b721c05d932e5ad9ff9344f708c96b9e1a485418c6618d765fca95d4daacfbef"}, + {file = "pyzmq-27.1.0-cp38-cp38-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be883ff3d722e6085ee3f4afc057a50f7f2e0c72d289fd54df5706b4e3d3a50"}, + {file = "pyzmq-27.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:b2e592db3a93128daf567de9650a2f3859017b3f7a66bc4ed6e4779d6034976f"}, + {file = "pyzmq-27.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ad68808a61cbfbbae7ba26d6233f2a4aa3b221de379ce9ee468aa7a83b9c36b0"}, + {file = "pyzmq-27.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e2687c2d230e8d8584fbea433c24382edfeda0c60627aca3446aa5e58d5d1831"}, + {file = "pyzmq-27.1.0-cp38-cp38-win32.whl", hash = "sha256:a1aa0ee920fb3825d6c825ae3f6c508403b905b698b6460408ebd5bb04bbb312"}, + {file = "pyzmq-27.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:df7cd397ece96cf20a76fae705d40efbab217d217897a5053267cd88a700c266"}, + {file = "pyzmq-27.1.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:96c71c32fff75957db6ae33cd961439f386505c6e6b377370af9b24a1ef9eafb"}, + {file = "pyzmq-27.1.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:49d3980544447f6bd2968b6ac913ab963a49dcaa2d4a2990041f16057b04c429"}, + {file = "pyzmq-27.1.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:849ca054d81aa1c175c49484afaaa5db0622092b5eccb2055f9f3bb8f703782d"}, + {file = "pyzmq-27.1.0-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3970778e74cb7f85934d2b926b9900e92bfe597e62267d7499acc39c9c28e345"}, + {file = "pyzmq-27.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:da96ecdcf7d3919c3be2de91a8c513c186f6762aa6cf7c01087ed74fad7f0968"}, + {file = "pyzmq-27.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9541c444cfe1b1c0156c5c86ece2bb926c7079a18e7b47b0b1b3b1b875e5d098"}, + {file = "pyzmq-27.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e30a74a39b93e2e1591b58eb1acef4902be27c957a8720b0e368f579b82dc22f"}, + {file = "pyzmq-27.1.0-cp39-cp39-win32.whl", hash = "sha256:b1267823d72d1e40701dcba7edc45fd17f71be1285557b7fe668887150a14b78"}, + {file = "pyzmq-27.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:0c996ded912812a2fcd7ab6574f4ad3edc27cb6510349431e4930d4196ade7db"}, + {file = "pyzmq-27.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:346e9ba4198177a07e7706050f35d733e08c1c1f8ceacd5eb6389d653579ffbc"}, + {file = "pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6"}, + {file = "pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90"}, + {file = "pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62"}, + {file = "pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74"}, + {file = "pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba"}, + {file = "pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066"}, + {file = "pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604"}, + {file = "pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c"}, + {file = "pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271"}, + {file = "pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355"}, + {file = "pyzmq-27.1.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:50081a4e98472ba9f5a02850014b4c9b629da6710f8f14f3b15897c666a28f1b"}, + {file = "pyzmq-27.1.0-pp38-pypy38_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:510869f9df36ab97f89f4cff9d002a89ac554c7ac9cadd87d444aa4cf66abd27"}, + {file = "pyzmq-27.1.0-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f8426a01b1c4098a750973c37131cf585f61c7911d735f729935a0c701b68d3"}, + {file = "pyzmq-27.1.0-pp38-pypy38_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726b6a502f2e34c6d2ada5e702929586d3ac948a4dbbb7fed9854ec8c0466027"}, + {file = "pyzmq-27.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:bd67e7c8f4654bef471c0b1ca6614af0b5202a790723a58b79d9584dc8022a78"}, + {file = "pyzmq-27.1.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:722ea791aa233ac0a819fc2c475e1292c76930b31f1d828cb61073e2fe5e208f"}, + {file = "pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:01f9437501886d3a1dd4b02ef59fb8cc384fa718ce066d52f175ee49dd5b7ed8"}, + {file = "pyzmq-27.1.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4a19387a3dddcc762bfd2f570d14e2395b2c9701329b266f83dd87a2b3cbd381"}, + {file = "pyzmq-27.1.0-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c618fbcd069e3a29dcd221739cacde52edcc681f041907867e0f5cc7e85f172"}, + {file = "pyzmq-27.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff8d114d14ac671d88c89b9224c63d6c4e5a613fe8acd5594ce53d752a3aafe9"}, + {file = "pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540"}, ] [package.dependencies] From 6f2e6562ea329def8eb92ae7a5d3cd815cb384b1 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Wed, 10 Sep 2025 22:34:51 +0700 Subject: [PATCH 02/39] feat: add ideal examples for ideal template --- ...01-cookiecutter-template-generalization.md | 306 ++++++++++++++++++ Makefile | 2 +- leetcode/combination_sum/playground.ipynb | 57 ---- leetcode/contains_duplicate/playground.ipynb | 57 ---- leetcode/permutations/playground.ipynb | 57 ---- leetcode/three_sum/playground.ipynb | 57 ---- leetcode/zero_one_matrix/playground.ipynb | 57 ---- .../clone_graph/README.md | 0 .../clone_graph}/__init__.py | 0 leetcode_ideal/clone_graph/helpers.py | 22 ++ leetcode_ideal/clone_graph/playground.ipynb | 149 +++++++++ .../clone_graph/solution.py | 0 leetcode_ideal/clone_graph/test_solution.py | 55 ++++ .../contains_duplicate/README.md | 0 .../contains_duplicate}/__init__.py | 0 leetcode_ideal/contains_duplicate/helpers.py | 7 + .../contains_duplicate/playground.ipynb | 91 ++++++ .../contains_duplicate/solution.py | 0 .../contains_duplicate/test_solution.py | 31 ++ .../first_bad_version/README.md | 0 .../first_bad_version}/__init__.py | 0 leetcode_ideal/first_bad_version/helpers.py | 8 + .../first_bad_version/playground.ipynb | 92 ++++++ .../first_bad_version/solution.py | 0 .../first_bad_version/test_solution.py | 18 ++ .../implement_trie_prefix_tree/README.md | 0 .../implement_trie_prefix_tree}/__init__.py | 0 .../implement_trie_prefix_tree/helpers.py | 25 ++ .../playground.ipynb | 204 ++++++++++++ .../implement_trie_prefix_tree/solution.py | 0 .../test_solution.py | 42 +++ .../invert_binary_tree/README.md | 0 .../invert_binary_tree}/__init__.py | 0 leetcode_ideal/invert_binary_tree/helpers.py | 15 + .../invert_binary_tree/playground.ipynb | 185 +++++++++++ .../invert_binary_tree/solution.py | 0 .../invert_binary_tree/test_solution.py | 58 ++++ .../linked_list_cycle/README.md | 0 .../linked_list_cycle}/__init__.py | 0 leetcode_ideal/linked_list_cycle/helpers.py | 27 ++ .../linked_list_cycle/playground.ipynb | 92 ++++++ .../linked_list_cycle/solution.py | 0 .../linked_list_cycle/test_solution.py | 38 +++ .../lru_cache/README.md | 0 .../lru_cache}/__init__.py | 0 leetcode_ideal/lru_cache/helpers.py | 23 ++ leetcode_ideal/lru_cache/playground.ipynb | 100 ++++++ leetcode_ideal/lru_cache/solution.py | 126 ++++++++ leetcode_ideal/lru_cache/test_solution.py | 61 ++++ .../reverse_linked_list/README.md | 0 .../reverse_linked_list}/__init__.py | 0 leetcode_ideal/reverse_linked_list/helpers.py | 15 + .../reverse_linked_list/playground.ipynb | 159 +++++++++ .../reverse_linked_list/solution.py | 0 .../reverse_linked_list/test_solution.py | 30 ++ .../README.md | 0 .../__init__.py | 0 .../helpers.py | 26 ++ .../playground.ipynb | 159 +++++++++ .../solution.py | 0 .../test_solution.py | 43 +++ .../tests.py | 0 .../valid_parentheses/README.md | 0 .../valid_parentheses}/__init__.py | 0 leetcode_ideal/valid_parentheses/helpers.py | 7 + .../valid_parentheses/playground.ipynb | 91 ++++++ .../valid_parentheses/solution.py | 0 .../valid_parentheses/test_solution.py | 45 +++ .../clone_graph => leetcode_old}/__init__.py | 0 .../accounts_merge/README.md | 0 .../accounts_merge}/__init__.py | 0 .../accounts_merge/playground.ipynb | 0 .../accounts_merge/solution.py | 0 .../accounts_merge/tests.py | 0 .../add_binary/README.md | 0 .../add_binary}/__init__.py | 0 .../add_binary/playground.ipynb | 0 .../add_binary/solution.py | 0 .../add_binary/tests.py | 0 .../balanced_binary_tree/README.md | 0 .../balanced_binary_tree}/__init__.py | 0 .../balanced_binary_tree/playground.ipynb | 0 .../balanced_binary_tree/solution.py | 0 .../balanced_binary_tree/tests.py | 0 .../basic_calculator/README.md | 0 .../basic_calculator}/__init__.py | 0 .../basic_calculator/playground.ipynb | 0 .../basic_calculator/solution.py | 0 .../basic_calculator/tests.py | 0 .../best_time_to_buy_and_sell_stock/README.md | 0 .../__init__.py | 0 .../playground.ipynb | 0 .../solution.py | 0 .../best_time_to_buy_and_sell_stock/tests.py | 0 .../binary_search/README.md | 0 .../binary_search}/__init__.py | 0 .../binary_search/playground.ipynb | 0 .../binary_search/solution.py | 0 .../binary_search/tests.py | 0 .../README.md | 0 .../__init__.py | 0 .../playground.ipynb | 0 .../solution.py | 0 .../tests.py | 0 .../binary_tree_right_side_view/README.md | 0 .../binary_tree_right_side_view}/__init__.py | 0 .../playground.ipynb | 0 .../binary_tree_right_side_view/solution.py | 0 .../binary_tree_right_side_view/tests.py | 0 .../climbing_stairs/README.md | 0 .../climbing_stairs}/__init__.py | 0 .../climbing_stairs/playground.ipynb | 0 .../climbing_stairs/solution.py | 0 .../climbing_stairs/tests.py | 0 leetcode_old/clone_graph/README.md | 72 +++++ .../clone_graph}/__init__.py | 0 .../clone_graph/playground.ipynb | 0 leetcode_old/clone_graph/solution.py | 73 +++++ .../clone_graph/tests.py | 0 .../coin_change/README.md | 0 .../coin_change}/__init__.py | 0 .../coin_change/playground.ipynb | 0 .../coin_change/solution.py | 0 .../coin_change/tests.py | 0 .../combination_sum/README.md | 0 .../combination_sum}/__init__.py | 0 leetcode_old/combination_sum/playground.ipynb | 74 +++++ .../combination_sum/solution.py | 0 .../combination_sum/tests.py | 0 .../container_with_most_water/README.md | 0 .../container_with_most_water}/__init__.py | 0 .../playground.ipynb | 0 .../container_with_most_water/solution.py | 0 .../container_with_most_water/tests.py | 0 leetcode_old/contains_duplicate/README.md | 43 +++ .../contains_duplicate}/__init__.py | 0 .../contains_duplicate}/playground.ipynb | 19 +- leetcode_old/contains_duplicate/solution.py | 10 + .../contains_duplicate/tests.py | 0 .../course_schedule/README.md | 0 .../course_schedule}/__init__.py | 0 leetcode_old/course_schedule/playground.ipynb | 69 ++++ .../course_schedule/solution.py | 0 .../course_schedule/tests.py | 0 .../diameter_of_binary_tree/README.md | 0 .../diameter_of_binary_tree}/__init__.py | 0 .../diameter_of_binary_tree/playground.ipynb | 0 .../diameter_of_binary_tree/solution.py | 0 .../diameter_of_binary_tree/tests.py | 0 .../README.md | 0 .../__init__.py | 0 .../playground.ipynb | 0 .../solution.py | 0 .../evaluate_reverse_polish_notation/tests.py | 0 .../find_median_from_data_stream/README.md | 0 .../find_median_from_data_stream}/__init__.py | 0 .../playground.ipynb | 0 .../find_median_from_data_stream/solution.py | 0 .../find_median_from_data_stream/tests.py | 0 leetcode_old/first_bad_version/README.md | 47 +++ .../first_bad_version}/__init__.py | 0 .../first_bad_version/playground.ipynb | 0 leetcode_old/first_bad_version/solution.py | 45 +++ .../first_bad_version/tests.py | 0 .../flood_fill/README.md | 0 .../flood_fill}/__init__.py | 0 leetcode_old/flood_fill/playground.ipynb | 71 ++++ .../flood_fill/solution.py | 0 .../flood_fill/tests.py | 0 .../implement_queue_using_stacks/README.md | 0 .../implement_queue_using_stacks}/__init__.py | 0 .../playground.ipynb | 0 .../implement_queue_using_stacks/solution.py | 0 .../implement_queue_using_stacks/tests.py | 0 .../implement_trie_prefix_tree/README.md | 48 +++ .../implement_trie_prefix_tree}/__init__.py | 0 .../playground.ipynb | 0 .../implement_trie_prefix_tree/solution.py | 40 +++ .../implement_trie_prefix_tree/tests.py | 0 .../insert_interval/README.md | 0 .../insert_interval}/__init__.py | 0 .../insert_interval/playground.ipynb | 0 .../insert_interval/solution.py | 0 .../insert_interval/tests.py | 0 leetcode_old/invert_binary_tree/README.md | 39 +++ .../invert_binary_tree}/__init__.py | 0 .../invert_binary_tree/playground.ipynb | 0 leetcode_old/invert_binary_tree/solution.py | 59 ++++ .../invert_binary_tree/tests.py | 0 .../k_closest_points_to_origin/README.md | 0 .../k_closest_points_to_origin}/__init__.py | 0 .../playground.ipynb | 0 .../k_closest_points_to_origin/solution.py | 0 .../k_closest_points_to_origin/tests.py | 0 .../kth_smallest_element_in_a_bst/README.md | 0 .../__init__.py | 0 .../playground.ipynb | 0 .../kth_smallest_element_in_a_bst/solution.py | 0 .../kth_smallest_element_in_a_bst/tests.py | 0 .../largest_rectangle_in_histogram/README.md | 0 .../__init__.py | 0 .../playground.ipynb | 0 .../solution.py | 0 .../largest_rectangle_in_histogram/tests.py | 0 leetcode_old/linked_list_cycle/README.md | 58 ++++ .../linked_list_cycle}/__init__.py | 0 .../linked_list_cycle/playground.ipynb | 0 leetcode_old/linked_list_cycle/solution.py | 18 ++ .../linked_list_cycle/tests.py | 0 .../longest_palindrome/README.md | 0 .../longest_palindrome}/__init__.py | 0 .../longest_palindrome/playground.ipynb | 0 .../longest_palindrome/solution.py | 0 .../longest_palindrome/tests.py | 0 .../longest_palindromic_substring/README.md | 0 .../__init__.py | 0 .../playground.ipynb | 0 .../longest_palindromic_substring/solution.py | 0 .../longest_palindromic_substring/tests.py | 0 .../README.md | 0 .../__init__.py | 0 .../playground.ipynb | 0 .../solution.py | 0 .../tests.py | 0 .../README.md | 0 .../__init__.py | 0 .../playground.ipynb | 0 .../solution.py | 0 .../tests.py | 0 .../README.md | 0 .../__init__.py | 0 .../playground.ipynb | 0 .../solution.py | 0 .../tests.py | 0 leetcode_old/lru_cache/README.md | 50 +++ .../lru_cache}/__init__.py | 0 .../lru_cache/playground.ipynb | 0 .../lru_cache/solution.py | 0 {leetcode => leetcode_old}/lru_cache/tests.py | 0 .../majority_element/README.md | 0 .../majority_element}/__init__.py | 0 .../majority_element/playground.ipynb | 0 .../majority_element/solution.py | 0 .../majority_element/tests.py | 0 .../maximum_depth_of_binary_tree/README.md | 0 .../maximum_depth_of_binary_tree}/__init__.py | 0 .../playground.ipynb | 0 .../maximum_depth_of_binary_tree/solution.py | 0 .../maximum_depth_of_binary_tree/tests.py | 0 .../README.md | 0 .../__init__.py | 0 .../playground.ipynb | 0 .../solution.py | 0 .../maximum_profit_in_job_scheduling/tests.py | 0 .../maximum_subarray/README.md | 0 .../maximum_subarray}/__init__.py | 0 .../maximum_subarray/playground.ipynb | 0 .../maximum_subarray/solution.py | 0 .../maximum_subarray/tests.py | 0 .../merge_intervals/README.md | 0 .../merge_intervals}/__init__.py | 0 .../merge_intervals/playground.ipynb | 0 .../merge_intervals/solution.py | 0 .../merge_intervals/tests.py | 0 .../merge_k_sorted_lists/README.md | 0 .../merge_k_sorted_lists}/__init__.py | 0 .../merge_k_sorted_lists/playground.ipynb | 0 .../merge_k_sorted_lists/solution.py | 0 .../merge_k_sorted_lists/tests.py | 0 .../merge_two_sorted_lists/README.md | 0 .../merge_two_sorted_lists}/__init__.py | 0 .../merge_two_sorted_lists/playground.ipynb | 0 .../merge_two_sorted_lists/solution.py | 0 .../merge_two_sorted_lists/tests.py | 0 .../middle_of_the_linked_list/README.md | 0 .../middle_of_the_linked_list}/__init__.py | 0 .../playground.ipynb | 0 .../middle_of_the_linked_list/solution.py | 0 .../middle_of_the_linked_list/tests.py | 0 .../min_stack/README.md | 0 .../min_stack}/__init__.py | 0 .../min_stack/playground.ipynb | 0 .../min_stack/solution.py | 0 {leetcode => leetcode_old}/min_stack/tests.py | 0 .../minimum_height_trees/README.md | 0 .../minimum_height_trees}/__init__.py | 0 .../minimum_height_trees/playground.ipynb | 0 .../minimum_height_trees/solution.py | 0 .../minimum_height_trees/tests.py | 0 .../minimum_window_substring/README.md | 0 .../minimum_window_substring}/__init__.py | 0 .../minimum_window_substring/playground.ipynb | 0 .../minimum_window_substring/solution.py | 0 .../minimum_window_substring/tests.py | 0 .../number_of_islands/README.md | 0 .../number_of_islands}/__init__.py | 0 .../number_of_islands/playground.ipynb | 3 +- .../number_of_islands/solution.py | 0 .../number_of_islands/tests.py | 0 .../partition_equal_subset_sum/README.md | 0 .../partition_equal_subset_sum}/__init__.py | 0 .../playground.ipynb | 0 .../partition_equal_subset_sum/solution.py | 0 .../partition_equal_subset_sum/tests.py | 0 .../permutations/README.md | 0 .../permutations}/__init__.py | 0 leetcode_old/permutations/playground.ipynb | 75 +++++ .../permutations/solution.py | 0 .../permutations/tests.py | 0 .../product_of_array_except_self/README.md | 0 .../product_of_array_except_self}/__init__.py | 0 .../playground.ipynb | 0 .../product_of_array_except_self/solution.py | 0 .../product_of_array_except_self/tests.py | 0 .../ransom_note/README.md | 0 .../ransom_note}/__init__.py | 0 .../ransom_note/playground.ipynb | 0 .../ransom_note/solution.py | 0 .../ransom_note/tests.py | 0 leetcode_old/reverse_linked_list/README.md | 45 +++ .../reverse_linked_list}/__init__.py | 0 .../reverse_linked_list/playground.ipynb | 0 leetcode_old/reverse_linked_list/solution.py | 46 +++ .../reverse_linked_list/tests.py | 0 .../reverse_linked_list_ii/README.md | 0 .../reverse_linked_list_ii}/__init__.py | 0 .../reverse_linked_list_ii/playground.ipynb | 0 .../reverse_linked_list_ii/solution.py | 0 .../reverse_linked_list_ii/tests.py | 0 .../rotting_oranges/README.md | 0 .../rotting_oranges}/__init__.py | 0 .../rotting_oranges/playground.ipynb | 19 +- .../rotting_oranges/solution.py | 0 .../rotting_oranges/tests.py | 0 .../search_in_rotated_sorted_array/README.md | 0 .../__init__.py | 0 .../playground.ipynb | 0 .../solution.py | 0 .../search_in_rotated_sorted_array/tests.py | 0 .../README.md | 38 +++ .../__init__.py | 0 .../playground.ipynb | 0 .../solution.py | 81 +++++ .../tests.py | 100 ++++++ .../sort_colors/README.md | 0 .../sort_colors}/__init__.py | 0 .../sort_colors/playground.ipynb | 0 .../sort_colors/solution.py | 0 .../sort_colors/tests.py | 0 .../spiral_matrix/README.md | 0 .../spiral_matrix}/__init__.py | 0 .../spiral_matrix/playground.ipynb | 0 .../spiral_matrix/solution.py | 0 .../spiral_matrix/tests.py | 0 .../string_to_integer_atoi/README.md | 0 .../string_to_integer_atoi}/__init__.py | 0 .../string_to_integer_atoi/playground.ipynb | 0 .../string_to_integer_atoi/solution.py | 0 .../string_to_integer_atoi/tests.py | 0 .../task_scheduler/README.md | 0 .../task_scheduler}/__init__.py | 0 .../task_scheduler/playground.ipynb | 0 .../task_scheduler/solution.py | 0 .../task_scheduler/tests.py | 0 .../three_sum/README.md | 0 leetcode_old/three_sum/__init__.py | 0 leetcode_old/three_sum/playground.ipynb | 73 +++++ .../three_sum/solution.py | 0 {leetcode => leetcode_old}/three_sum/tests.py | 0 .../time_based_key_value_store/README.md | 0 .../time_based_key_value_store/__init__.py | 0 .../playground.ipynb | 0 .../time_based_key_value_store/solution.py | 0 .../time_based_key_value_store/tests.py | 0 .../trapping_rain_water/README.md | 0 leetcode_old/trapping_rain_water/__init__.py | 0 .../trapping_rain_water/playground.ipynb | 0 .../trapping_rain_water/solution.py | 0 .../trapping_rain_water/tests.py | 0 .../valid_anagram/README.md | 0 leetcode_old/valid_anagram/__init__.py | 0 .../valid_anagram/playground.ipynb | 0 .../valid_anagram/solution.py | 0 .../valid_anagram/tests.py | 0 .../valid_palindrome/README.md | 0 leetcode_old/valid_palindrome/__init__.py | 0 .../valid_palindrome/playground.ipynb | 0 .../valid_palindrome/solution.py | 0 .../valid_palindrome/tests.py | 0 leetcode_old/valid_parentheses/README.md | 59 ++++ leetcode_old/valid_parentheses/__init__.py | 0 .../valid_parentheses/playground.ipynb | 3 +- leetcode_old/valid_parentheses/solution.py | 14 + .../valid_parentheses/tests.py | 0 .../validate_binary_search_tree/README.md | 0 .../validate_binary_search_tree/__init__.py | 0 .../playground.ipynb | 0 .../validate_binary_search_tree/solution.py | 0 .../validate_binary_search_tree/tests.py | 0 .../word_break/README.md | 0 leetcode_old/word_break/__init__.py | 0 .../word_break/playground.ipynb | 0 .../word_break/solution.py | 0 .../word_break/tests.py | 0 .../word_ladder/README.md | 0 leetcode_old/word_ladder/__init__.py | 0 .../word_ladder/playground.ipynb | 0 .../word_ladder/solution.py | 0 .../word_ladder/tests.py | 0 .../zero_one_matrix/README.md | 0 leetcode_old/zero_one_matrix/__init__.py | 0 .../zero_one_matrix}/playground.ipynb | 19 +- .../zero_one_matrix/solution.py | 0 .../zero_one_matrix/tests.py | 0 414 files changed, 3747 insertions(+), 300 deletions(-) create mode 100644 .amazonq/plans/01-cookiecutter-template-generalization.md delete mode 100644 leetcode/combination_sum/playground.ipynb delete mode 100644 leetcode/contains_duplicate/playground.ipynb delete mode 100644 leetcode/permutations/playground.ipynb delete mode 100644 leetcode/three_sum/playground.ipynb delete mode 100644 leetcode/zero_one_matrix/playground.ipynb rename {leetcode => leetcode_ideal}/clone_graph/README.md (100%) rename {leetcode => leetcode_ideal/clone_graph}/__init__.py (100%) create mode 100644 leetcode_ideal/clone_graph/helpers.py create mode 100644 leetcode_ideal/clone_graph/playground.ipynb rename {leetcode => leetcode_ideal}/clone_graph/solution.py (100%) create mode 100644 leetcode_ideal/clone_graph/test_solution.py rename {leetcode => leetcode_ideal}/contains_duplicate/README.md (100%) rename {leetcode/accounts_merge => leetcode_ideal/contains_duplicate}/__init__.py (100%) create mode 100644 leetcode_ideal/contains_duplicate/helpers.py create mode 100644 leetcode_ideal/contains_duplicate/playground.ipynb rename {leetcode => leetcode_ideal}/contains_duplicate/solution.py (100%) create mode 100644 leetcode_ideal/contains_duplicate/test_solution.py rename {leetcode => leetcode_ideal}/first_bad_version/README.md (100%) rename {leetcode/add_binary => leetcode_ideal/first_bad_version}/__init__.py (100%) create mode 100644 leetcode_ideal/first_bad_version/helpers.py create mode 100644 leetcode_ideal/first_bad_version/playground.ipynb rename {leetcode => leetcode_ideal}/first_bad_version/solution.py (100%) create mode 100644 leetcode_ideal/first_bad_version/test_solution.py rename {leetcode => leetcode_ideal}/implement_trie_prefix_tree/README.md (100%) rename {leetcode/balanced_binary_tree => leetcode_ideal/implement_trie_prefix_tree}/__init__.py (100%) create mode 100644 leetcode_ideal/implement_trie_prefix_tree/helpers.py create mode 100644 leetcode_ideal/implement_trie_prefix_tree/playground.ipynb rename {leetcode => leetcode_ideal}/implement_trie_prefix_tree/solution.py (100%) create mode 100644 leetcode_ideal/implement_trie_prefix_tree/test_solution.py rename {leetcode => leetcode_ideal}/invert_binary_tree/README.md (100%) rename {leetcode/basic_calculator => leetcode_ideal/invert_binary_tree}/__init__.py (100%) create mode 100644 leetcode_ideal/invert_binary_tree/helpers.py create mode 100644 leetcode_ideal/invert_binary_tree/playground.ipynb rename {leetcode => leetcode_ideal}/invert_binary_tree/solution.py (100%) create mode 100644 leetcode_ideal/invert_binary_tree/test_solution.py rename {leetcode => leetcode_ideal}/linked_list_cycle/README.md (100%) rename {leetcode/best_time_to_buy_and_sell_stock => leetcode_ideal/linked_list_cycle}/__init__.py (100%) create mode 100644 leetcode_ideal/linked_list_cycle/helpers.py create mode 100644 leetcode_ideal/linked_list_cycle/playground.ipynb rename {leetcode => leetcode_ideal}/linked_list_cycle/solution.py (100%) create mode 100644 leetcode_ideal/linked_list_cycle/test_solution.py rename {leetcode => leetcode_ideal}/lru_cache/README.md (100%) rename {leetcode/binary_search => leetcode_ideal/lru_cache}/__init__.py (100%) create mode 100644 leetcode_ideal/lru_cache/helpers.py create mode 100644 leetcode_ideal/lru_cache/playground.ipynb create mode 100644 leetcode_ideal/lru_cache/solution.py create mode 100644 leetcode_ideal/lru_cache/test_solution.py rename {leetcode => leetcode_ideal}/reverse_linked_list/README.md (100%) rename {leetcode/binary_tree_level_order_traversal => leetcode_ideal/reverse_linked_list}/__init__.py (100%) create mode 100644 leetcode_ideal/reverse_linked_list/helpers.py create mode 100644 leetcode_ideal/reverse_linked_list/playground.ipynb rename {leetcode => leetcode_ideal}/reverse_linked_list/solution.py (100%) create mode 100644 leetcode_ideal/reverse_linked_list/test_solution.py rename {leetcode => leetcode_ideal}/serialize_and_deserialize_binary_tree/README.md (100%) rename {leetcode/binary_tree_right_side_view => leetcode_ideal/serialize_and_deserialize_binary_tree}/__init__.py (100%) create mode 100644 leetcode_ideal/serialize_and_deserialize_binary_tree/helpers.py create mode 100644 leetcode_ideal/serialize_and_deserialize_binary_tree/playground.ipynb rename {leetcode => leetcode_ideal}/serialize_and_deserialize_binary_tree/solution.py (100%) create mode 100644 leetcode_ideal/serialize_and_deserialize_binary_tree/test_solution.py rename {leetcode => leetcode_ideal}/serialize_and_deserialize_binary_tree/tests.py (100%) rename {leetcode => leetcode_ideal}/valid_parentheses/README.md (100%) rename {leetcode/climbing_stairs => leetcode_ideal/valid_parentheses}/__init__.py (100%) create mode 100644 leetcode_ideal/valid_parentheses/helpers.py create mode 100644 leetcode_ideal/valid_parentheses/playground.ipynb rename {leetcode => leetcode_ideal}/valid_parentheses/solution.py (100%) create mode 100644 leetcode_ideal/valid_parentheses/test_solution.py rename {leetcode/clone_graph => leetcode_old}/__init__.py (100%) rename {leetcode => leetcode_old}/accounts_merge/README.md (100%) rename {leetcode/coin_change => leetcode_old/accounts_merge}/__init__.py (100%) rename {leetcode => leetcode_old}/accounts_merge/playground.ipynb (100%) rename {leetcode => leetcode_old}/accounts_merge/solution.py (100%) rename {leetcode => leetcode_old}/accounts_merge/tests.py (100%) rename {leetcode => leetcode_old}/add_binary/README.md (100%) rename {leetcode/combination_sum => leetcode_old/add_binary}/__init__.py (100%) rename {leetcode => leetcode_old}/add_binary/playground.ipynb (100%) rename {leetcode => leetcode_old}/add_binary/solution.py (100%) rename {leetcode => leetcode_old}/add_binary/tests.py (100%) rename {leetcode => leetcode_old}/balanced_binary_tree/README.md (100%) rename {leetcode/container_with_most_water => leetcode_old/balanced_binary_tree}/__init__.py (100%) rename {leetcode => leetcode_old}/balanced_binary_tree/playground.ipynb (100%) rename {leetcode => leetcode_old}/balanced_binary_tree/solution.py (100%) rename {leetcode => leetcode_old}/balanced_binary_tree/tests.py (100%) rename {leetcode => leetcode_old}/basic_calculator/README.md (100%) rename {leetcode/contains_duplicate => leetcode_old/basic_calculator}/__init__.py (100%) rename {leetcode => leetcode_old}/basic_calculator/playground.ipynb (100%) rename {leetcode => leetcode_old}/basic_calculator/solution.py (100%) rename {leetcode => leetcode_old}/basic_calculator/tests.py (100%) rename {leetcode => leetcode_old}/best_time_to_buy_and_sell_stock/README.md (100%) rename {leetcode/course_schedule => leetcode_old/best_time_to_buy_and_sell_stock}/__init__.py (100%) rename {leetcode => leetcode_old}/best_time_to_buy_and_sell_stock/playground.ipynb (100%) rename {leetcode => leetcode_old}/best_time_to_buy_and_sell_stock/solution.py (100%) rename {leetcode => leetcode_old}/best_time_to_buy_and_sell_stock/tests.py (100%) rename {leetcode => leetcode_old}/binary_search/README.md (100%) rename {leetcode/diameter_of_binary_tree => leetcode_old/binary_search}/__init__.py (100%) rename {leetcode => leetcode_old}/binary_search/playground.ipynb (100%) rename {leetcode => leetcode_old}/binary_search/solution.py (100%) rename {leetcode => leetcode_old}/binary_search/tests.py (100%) rename {leetcode => leetcode_old}/binary_tree_level_order_traversal/README.md (100%) rename {leetcode/evaluate_reverse_polish_notation => leetcode_old/binary_tree_level_order_traversal}/__init__.py (100%) rename {leetcode => leetcode_old}/binary_tree_level_order_traversal/playground.ipynb (100%) rename {leetcode => leetcode_old}/binary_tree_level_order_traversal/solution.py (100%) rename {leetcode => leetcode_old}/binary_tree_level_order_traversal/tests.py (100%) rename {leetcode => leetcode_old}/binary_tree_right_side_view/README.md (100%) rename {leetcode/find_median_from_data_stream => leetcode_old/binary_tree_right_side_view}/__init__.py (100%) rename {leetcode => leetcode_old}/binary_tree_right_side_view/playground.ipynb (100%) rename {leetcode => leetcode_old}/binary_tree_right_side_view/solution.py (100%) rename {leetcode => leetcode_old}/binary_tree_right_side_view/tests.py (100%) rename {leetcode => leetcode_old}/climbing_stairs/README.md (100%) rename {leetcode/first_bad_version => leetcode_old/climbing_stairs}/__init__.py (100%) rename {leetcode => leetcode_old}/climbing_stairs/playground.ipynb (100%) rename {leetcode => leetcode_old}/climbing_stairs/solution.py (100%) rename {leetcode => leetcode_old}/climbing_stairs/tests.py (100%) create mode 100644 leetcode_old/clone_graph/README.md rename {leetcode/flood_fill => leetcode_old/clone_graph}/__init__.py (100%) rename {leetcode => leetcode_old}/clone_graph/playground.ipynb (100%) create mode 100644 leetcode_old/clone_graph/solution.py rename {leetcode => leetcode_old}/clone_graph/tests.py (100%) rename {leetcode => leetcode_old}/coin_change/README.md (100%) rename {leetcode/implement_queue_using_stacks => leetcode_old/coin_change}/__init__.py (100%) rename {leetcode => leetcode_old}/coin_change/playground.ipynb (100%) rename {leetcode => leetcode_old}/coin_change/solution.py (100%) rename {leetcode => leetcode_old}/coin_change/tests.py (100%) rename {leetcode => leetcode_old}/combination_sum/README.md (100%) rename {leetcode/implement_trie_prefix_tree => leetcode_old/combination_sum}/__init__.py (100%) create mode 100644 leetcode_old/combination_sum/playground.ipynb rename {leetcode => leetcode_old}/combination_sum/solution.py (100%) rename {leetcode => leetcode_old}/combination_sum/tests.py (100%) rename {leetcode => leetcode_old}/container_with_most_water/README.md (100%) rename {leetcode/insert_interval => leetcode_old/container_with_most_water}/__init__.py (100%) rename {leetcode => leetcode_old}/container_with_most_water/playground.ipynb (100%) rename {leetcode => leetcode_old}/container_with_most_water/solution.py (100%) rename {leetcode => leetcode_old}/container_with_most_water/tests.py (100%) create mode 100644 leetcode_old/contains_duplicate/README.md rename {leetcode/invert_binary_tree => leetcode_old/contains_duplicate}/__init__.py (100%) rename {leetcode/course_schedule => leetcode_old/contains_duplicate}/playground.ipynb (75%) create mode 100644 leetcode_old/contains_duplicate/solution.py rename {leetcode => leetcode_old}/contains_duplicate/tests.py (100%) rename {leetcode => leetcode_old}/course_schedule/README.md (100%) rename {leetcode/k_closest_points_to_origin => leetcode_old/course_schedule}/__init__.py (100%) create mode 100644 leetcode_old/course_schedule/playground.ipynb rename {leetcode => leetcode_old}/course_schedule/solution.py (100%) rename {leetcode => leetcode_old}/course_schedule/tests.py (100%) rename {leetcode => leetcode_old}/diameter_of_binary_tree/README.md (100%) rename {leetcode/kth_smallest_element_in_a_bst => leetcode_old/diameter_of_binary_tree}/__init__.py (100%) rename {leetcode => leetcode_old}/diameter_of_binary_tree/playground.ipynb (100%) rename {leetcode => leetcode_old}/diameter_of_binary_tree/solution.py (100%) rename {leetcode => leetcode_old}/diameter_of_binary_tree/tests.py (100%) rename {leetcode => leetcode_old}/evaluate_reverse_polish_notation/README.md (100%) rename {leetcode/largest_rectangle_in_histogram => leetcode_old/evaluate_reverse_polish_notation}/__init__.py (100%) rename {leetcode => leetcode_old}/evaluate_reverse_polish_notation/playground.ipynb (100%) rename {leetcode => leetcode_old}/evaluate_reverse_polish_notation/solution.py (100%) rename {leetcode => leetcode_old}/evaluate_reverse_polish_notation/tests.py (100%) rename {leetcode => leetcode_old}/find_median_from_data_stream/README.md (100%) rename {leetcode/linked_list_cycle => leetcode_old/find_median_from_data_stream}/__init__.py (100%) rename {leetcode => leetcode_old}/find_median_from_data_stream/playground.ipynb (100%) rename {leetcode => leetcode_old}/find_median_from_data_stream/solution.py (100%) rename {leetcode => leetcode_old}/find_median_from_data_stream/tests.py (100%) create mode 100644 leetcode_old/first_bad_version/README.md rename {leetcode/longest_palindrome => leetcode_old/first_bad_version}/__init__.py (100%) rename {leetcode => leetcode_old}/first_bad_version/playground.ipynb (100%) create mode 100644 leetcode_old/first_bad_version/solution.py rename {leetcode => leetcode_old}/first_bad_version/tests.py (100%) rename {leetcode => leetcode_old}/flood_fill/README.md (100%) rename {leetcode/longest_palindromic_substring => leetcode_old/flood_fill}/__init__.py (100%) create mode 100644 leetcode_old/flood_fill/playground.ipynb rename {leetcode => leetcode_old}/flood_fill/solution.py (100%) rename {leetcode => leetcode_old}/flood_fill/tests.py (100%) rename {leetcode => leetcode_old}/implement_queue_using_stacks/README.md (100%) rename {leetcode/longest_substring_without_repeating_characters => leetcode_old/implement_queue_using_stacks}/__init__.py (100%) rename {leetcode => leetcode_old}/implement_queue_using_stacks/playground.ipynb (100%) rename {leetcode => leetcode_old}/implement_queue_using_stacks/solution.py (100%) rename {leetcode => leetcode_old}/implement_queue_using_stacks/tests.py (100%) create mode 100644 leetcode_old/implement_trie_prefix_tree/README.md rename {leetcode/lowest_common_ancestor_of_a_binary_search_tree => leetcode_old/implement_trie_prefix_tree}/__init__.py (100%) rename {leetcode => leetcode_old}/implement_trie_prefix_tree/playground.ipynb (100%) create mode 100644 leetcode_old/implement_trie_prefix_tree/solution.py rename {leetcode => leetcode_old}/implement_trie_prefix_tree/tests.py (100%) rename {leetcode => leetcode_old}/insert_interval/README.md (100%) rename {leetcode/lowest_common_ancestor_of_a_binary_tree => leetcode_old/insert_interval}/__init__.py (100%) rename {leetcode => leetcode_old}/insert_interval/playground.ipynb (100%) rename {leetcode => leetcode_old}/insert_interval/solution.py (100%) rename {leetcode => leetcode_old}/insert_interval/tests.py (100%) create mode 100644 leetcode_old/invert_binary_tree/README.md rename {leetcode/lru_cache => leetcode_old/invert_binary_tree}/__init__.py (100%) rename {leetcode => leetcode_old}/invert_binary_tree/playground.ipynb (100%) create mode 100644 leetcode_old/invert_binary_tree/solution.py rename {leetcode => leetcode_old}/invert_binary_tree/tests.py (100%) rename {leetcode => leetcode_old}/k_closest_points_to_origin/README.md (100%) rename {leetcode/majority_element => leetcode_old/k_closest_points_to_origin}/__init__.py (100%) rename {leetcode => leetcode_old}/k_closest_points_to_origin/playground.ipynb (100%) rename {leetcode => leetcode_old}/k_closest_points_to_origin/solution.py (100%) rename {leetcode => leetcode_old}/k_closest_points_to_origin/tests.py (100%) rename {leetcode => leetcode_old}/kth_smallest_element_in_a_bst/README.md (100%) rename {leetcode/maximum_depth_of_binary_tree => leetcode_old/kth_smallest_element_in_a_bst}/__init__.py (100%) rename {leetcode => leetcode_old}/kth_smallest_element_in_a_bst/playground.ipynb (100%) rename {leetcode => leetcode_old}/kth_smallest_element_in_a_bst/solution.py (100%) rename {leetcode => leetcode_old}/kth_smallest_element_in_a_bst/tests.py (100%) rename {leetcode => leetcode_old}/largest_rectangle_in_histogram/README.md (100%) rename {leetcode/maximum_profit_in_job_scheduling => leetcode_old/largest_rectangle_in_histogram}/__init__.py (100%) rename {leetcode => leetcode_old}/largest_rectangle_in_histogram/playground.ipynb (100%) rename {leetcode => leetcode_old}/largest_rectangle_in_histogram/solution.py (100%) rename {leetcode => leetcode_old}/largest_rectangle_in_histogram/tests.py (100%) create mode 100644 leetcode_old/linked_list_cycle/README.md rename {leetcode/maximum_subarray => leetcode_old/linked_list_cycle}/__init__.py (100%) rename {leetcode => leetcode_old}/linked_list_cycle/playground.ipynb (100%) create mode 100644 leetcode_old/linked_list_cycle/solution.py rename {leetcode => leetcode_old}/linked_list_cycle/tests.py (100%) rename {leetcode => leetcode_old}/longest_palindrome/README.md (100%) rename {leetcode/merge_intervals => leetcode_old/longest_palindrome}/__init__.py (100%) rename {leetcode => leetcode_old}/longest_palindrome/playground.ipynb (100%) rename {leetcode => leetcode_old}/longest_palindrome/solution.py (100%) rename {leetcode => leetcode_old}/longest_palindrome/tests.py (100%) rename {leetcode => leetcode_old}/longest_palindromic_substring/README.md (100%) rename {leetcode/merge_k_sorted_lists => leetcode_old/longest_palindromic_substring}/__init__.py (100%) rename {leetcode => leetcode_old}/longest_palindromic_substring/playground.ipynb (100%) rename {leetcode => leetcode_old}/longest_palindromic_substring/solution.py (100%) rename {leetcode => leetcode_old}/longest_palindromic_substring/tests.py (100%) rename {leetcode => leetcode_old}/longest_substring_without_repeating_characters/README.md (100%) rename {leetcode/merge_two_sorted_lists => leetcode_old/longest_substring_without_repeating_characters}/__init__.py (100%) rename {leetcode => leetcode_old}/longest_substring_without_repeating_characters/playground.ipynb (100%) rename {leetcode => leetcode_old}/longest_substring_without_repeating_characters/solution.py (100%) rename {leetcode => leetcode_old}/longest_substring_without_repeating_characters/tests.py (100%) rename {leetcode => leetcode_old}/lowest_common_ancestor_of_a_binary_search_tree/README.md (100%) rename {leetcode/middle_of_the_linked_list => leetcode_old/lowest_common_ancestor_of_a_binary_search_tree}/__init__.py (100%) rename {leetcode => leetcode_old}/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb (100%) rename {leetcode => leetcode_old}/lowest_common_ancestor_of_a_binary_search_tree/solution.py (100%) rename {leetcode => leetcode_old}/lowest_common_ancestor_of_a_binary_search_tree/tests.py (100%) rename {leetcode => leetcode_old}/lowest_common_ancestor_of_a_binary_tree/README.md (100%) rename {leetcode/min_stack => leetcode_old/lowest_common_ancestor_of_a_binary_tree}/__init__.py (100%) rename {leetcode => leetcode_old}/lowest_common_ancestor_of_a_binary_tree/playground.ipynb (100%) rename {leetcode => leetcode_old}/lowest_common_ancestor_of_a_binary_tree/solution.py (100%) rename {leetcode => leetcode_old}/lowest_common_ancestor_of_a_binary_tree/tests.py (100%) create mode 100644 leetcode_old/lru_cache/README.md rename {leetcode/minimum_height_trees => leetcode_old/lru_cache}/__init__.py (100%) rename {leetcode => leetcode_old}/lru_cache/playground.ipynb (100%) rename {leetcode => leetcode_old}/lru_cache/solution.py (100%) rename {leetcode => leetcode_old}/lru_cache/tests.py (100%) rename {leetcode => leetcode_old}/majority_element/README.md (100%) rename {leetcode/minimum_window_substring => leetcode_old/majority_element}/__init__.py (100%) rename {leetcode => leetcode_old}/majority_element/playground.ipynb (100%) rename {leetcode => leetcode_old}/majority_element/solution.py (100%) rename {leetcode => leetcode_old}/majority_element/tests.py (100%) rename {leetcode => leetcode_old}/maximum_depth_of_binary_tree/README.md (100%) rename {leetcode/number_of_islands => leetcode_old/maximum_depth_of_binary_tree}/__init__.py (100%) rename {leetcode => leetcode_old}/maximum_depth_of_binary_tree/playground.ipynb (100%) rename {leetcode => leetcode_old}/maximum_depth_of_binary_tree/solution.py (100%) rename {leetcode => leetcode_old}/maximum_depth_of_binary_tree/tests.py (100%) rename {leetcode => leetcode_old}/maximum_profit_in_job_scheduling/README.md (100%) rename {leetcode/partition_equal_subset_sum => leetcode_old/maximum_profit_in_job_scheduling}/__init__.py (100%) rename {leetcode => leetcode_old}/maximum_profit_in_job_scheduling/playground.ipynb (100%) rename {leetcode => leetcode_old}/maximum_profit_in_job_scheduling/solution.py (100%) rename {leetcode => leetcode_old}/maximum_profit_in_job_scheduling/tests.py (100%) rename {leetcode => leetcode_old}/maximum_subarray/README.md (100%) rename {leetcode/permutations => leetcode_old/maximum_subarray}/__init__.py (100%) rename {leetcode => leetcode_old}/maximum_subarray/playground.ipynb (100%) rename {leetcode => leetcode_old}/maximum_subarray/solution.py (100%) rename {leetcode => leetcode_old}/maximum_subarray/tests.py (100%) rename {leetcode => leetcode_old}/merge_intervals/README.md (100%) rename {leetcode/product_of_array_except_self => leetcode_old/merge_intervals}/__init__.py (100%) rename {leetcode => leetcode_old}/merge_intervals/playground.ipynb (100%) rename {leetcode => leetcode_old}/merge_intervals/solution.py (100%) rename {leetcode => leetcode_old}/merge_intervals/tests.py (100%) rename {leetcode => leetcode_old}/merge_k_sorted_lists/README.md (100%) rename {leetcode/ransom_note => leetcode_old/merge_k_sorted_lists}/__init__.py (100%) rename {leetcode => leetcode_old}/merge_k_sorted_lists/playground.ipynb (100%) rename {leetcode => leetcode_old}/merge_k_sorted_lists/solution.py (100%) rename {leetcode => leetcode_old}/merge_k_sorted_lists/tests.py (100%) rename {leetcode => leetcode_old}/merge_two_sorted_lists/README.md (100%) rename {leetcode/reverse_linked_list => leetcode_old/merge_two_sorted_lists}/__init__.py (100%) rename {leetcode => leetcode_old}/merge_two_sorted_lists/playground.ipynb (100%) rename {leetcode => leetcode_old}/merge_two_sorted_lists/solution.py (100%) rename {leetcode => leetcode_old}/merge_two_sorted_lists/tests.py (100%) rename {leetcode => leetcode_old}/middle_of_the_linked_list/README.md (100%) rename {leetcode/reverse_linked_list_ii => leetcode_old/middle_of_the_linked_list}/__init__.py (100%) rename {leetcode => leetcode_old}/middle_of_the_linked_list/playground.ipynb (100%) rename {leetcode => leetcode_old}/middle_of_the_linked_list/solution.py (100%) rename {leetcode => leetcode_old}/middle_of_the_linked_list/tests.py (100%) rename {leetcode => leetcode_old}/min_stack/README.md (100%) rename {leetcode/rotting_oranges => leetcode_old/min_stack}/__init__.py (100%) rename {leetcode => leetcode_old}/min_stack/playground.ipynb (100%) rename {leetcode => leetcode_old}/min_stack/solution.py (100%) rename {leetcode => leetcode_old}/min_stack/tests.py (100%) rename {leetcode => leetcode_old}/minimum_height_trees/README.md (100%) rename {leetcode/search_in_rotated_sorted_array => leetcode_old/minimum_height_trees}/__init__.py (100%) rename {leetcode => leetcode_old}/minimum_height_trees/playground.ipynb (100%) rename {leetcode => leetcode_old}/minimum_height_trees/solution.py (100%) rename {leetcode => leetcode_old}/minimum_height_trees/tests.py (100%) rename {leetcode => leetcode_old}/minimum_window_substring/README.md (100%) rename {leetcode/serialize_and_deserialize_binary_tree => leetcode_old/minimum_window_substring}/__init__.py (100%) rename {leetcode => leetcode_old}/minimum_window_substring/playground.ipynb (100%) rename {leetcode => leetcode_old}/minimum_window_substring/solution.py (100%) rename {leetcode => leetcode_old}/minimum_window_substring/tests.py (100%) rename {leetcode => leetcode_old}/number_of_islands/README.md (100%) rename {leetcode/sort_colors => leetcode_old/number_of_islands}/__init__.py (100%) rename {leetcode => leetcode_old}/number_of_islands/playground.ipynb (95%) rename {leetcode => leetcode_old}/number_of_islands/solution.py (100%) rename {leetcode => leetcode_old}/number_of_islands/tests.py (100%) rename {leetcode => leetcode_old}/partition_equal_subset_sum/README.md (100%) rename {leetcode/spiral_matrix => leetcode_old/partition_equal_subset_sum}/__init__.py (100%) rename {leetcode => leetcode_old}/partition_equal_subset_sum/playground.ipynb (100%) rename {leetcode => leetcode_old}/partition_equal_subset_sum/solution.py (100%) rename {leetcode => leetcode_old}/partition_equal_subset_sum/tests.py (100%) rename {leetcode => leetcode_old}/permutations/README.md (100%) rename {leetcode/string_to_integer_atoi => leetcode_old/permutations}/__init__.py (100%) create mode 100644 leetcode_old/permutations/playground.ipynb rename {leetcode => leetcode_old}/permutations/solution.py (100%) rename {leetcode => leetcode_old}/permutations/tests.py (100%) rename {leetcode => leetcode_old}/product_of_array_except_self/README.md (100%) rename {leetcode/task_scheduler => leetcode_old/product_of_array_except_self}/__init__.py (100%) rename {leetcode => leetcode_old}/product_of_array_except_self/playground.ipynb (100%) rename {leetcode => leetcode_old}/product_of_array_except_self/solution.py (100%) rename {leetcode => leetcode_old}/product_of_array_except_self/tests.py (100%) rename {leetcode => leetcode_old}/ransom_note/README.md (100%) rename {leetcode/three_sum => leetcode_old/ransom_note}/__init__.py (100%) rename {leetcode => leetcode_old}/ransom_note/playground.ipynb (100%) rename {leetcode => leetcode_old}/ransom_note/solution.py (100%) rename {leetcode => leetcode_old}/ransom_note/tests.py (100%) create mode 100644 leetcode_old/reverse_linked_list/README.md rename {leetcode/time_based_key_value_store => leetcode_old/reverse_linked_list}/__init__.py (100%) rename {leetcode => leetcode_old}/reverse_linked_list/playground.ipynb (100%) create mode 100644 leetcode_old/reverse_linked_list/solution.py rename {leetcode => leetcode_old}/reverse_linked_list/tests.py (100%) rename {leetcode => leetcode_old}/reverse_linked_list_ii/README.md (100%) rename {leetcode/trapping_rain_water => leetcode_old/reverse_linked_list_ii}/__init__.py (100%) rename {leetcode => leetcode_old}/reverse_linked_list_ii/playground.ipynb (100%) rename {leetcode => leetcode_old}/reverse_linked_list_ii/solution.py (100%) rename {leetcode => leetcode_old}/reverse_linked_list_ii/tests.py (100%) rename {leetcode => leetcode_old}/rotting_oranges/README.md (100%) rename {leetcode/valid_anagram => leetcode_old/rotting_oranges}/__init__.py (100%) rename {leetcode => leetcode_old}/rotting_oranges/playground.ipynb (74%) rename {leetcode => leetcode_old}/rotting_oranges/solution.py (100%) rename {leetcode => leetcode_old}/rotting_oranges/tests.py (100%) rename {leetcode => leetcode_old}/search_in_rotated_sorted_array/README.md (100%) rename {leetcode/valid_palindrome => leetcode_old/search_in_rotated_sorted_array}/__init__.py (100%) rename {leetcode => leetcode_old}/search_in_rotated_sorted_array/playground.ipynb (100%) rename {leetcode => leetcode_old}/search_in_rotated_sorted_array/solution.py (100%) rename {leetcode => leetcode_old}/search_in_rotated_sorted_array/tests.py (100%) create mode 100644 leetcode_old/serialize_and_deserialize_binary_tree/README.md rename {leetcode/valid_parentheses => leetcode_old/serialize_and_deserialize_binary_tree}/__init__.py (100%) rename {leetcode => leetcode_old}/serialize_and_deserialize_binary_tree/playground.ipynb (100%) create mode 100644 leetcode_old/serialize_and_deserialize_binary_tree/solution.py create mode 100644 leetcode_old/serialize_and_deserialize_binary_tree/tests.py rename {leetcode => leetcode_old}/sort_colors/README.md (100%) rename {leetcode/validate_binary_search_tree => leetcode_old/sort_colors}/__init__.py (100%) rename {leetcode => leetcode_old}/sort_colors/playground.ipynb (100%) rename {leetcode => leetcode_old}/sort_colors/solution.py (100%) rename {leetcode => leetcode_old}/sort_colors/tests.py (100%) rename {leetcode => leetcode_old}/spiral_matrix/README.md (100%) rename {leetcode/word_break => leetcode_old/spiral_matrix}/__init__.py (100%) rename {leetcode => leetcode_old}/spiral_matrix/playground.ipynb (100%) rename {leetcode => leetcode_old}/spiral_matrix/solution.py (100%) rename {leetcode => leetcode_old}/spiral_matrix/tests.py (100%) rename {leetcode => leetcode_old}/string_to_integer_atoi/README.md (100%) rename {leetcode/word_ladder => leetcode_old/string_to_integer_atoi}/__init__.py (100%) rename {leetcode => leetcode_old}/string_to_integer_atoi/playground.ipynb (100%) rename {leetcode => leetcode_old}/string_to_integer_atoi/solution.py (100%) rename {leetcode => leetcode_old}/string_to_integer_atoi/tests.py (100%) rename {leetcode => leetcode_old}/task_scheduler/README.md (100%) rename {leetcode/zero_one_matrix => leetcode_old/task_scheduler}/__init__.py (100%) rename {leetcode => leetcode_old}/task_scheduler/playground.ipynb (100%) rename {leetcode => leetcode_old}/task_scheduler/solution.py (100%) rename {leetcode => leetcode_old}/task_scheduler/tests.py (100%) rename {leetcode => leetcode_old}/three_sum/README.md (100%) create mode 100644 leetcode_old/three_sum/__init__.py create mode 100644 leetcode_old/three_sum/playground.ipynb rename {leetcode => leetcode_old}/three_sum/solution.py (100%) rename {leetcode => leetcode_old}/three_sum/tests.py (100%) rename {leetcode => leetcode_old}/time_based_key_value_store/README.md (100%) create mode 100644 leetcode_old/time_based_key_value_store/__init__.py rename {leetcode => leetcode_old}/time_based_key_value_store/playground.ipynb (100%) rename {leetcode => leetcode_old}/time_based_key_value_store/solution.py (100%) rename {leetcode => leetcode_old}/time_based_key_value_store/tests.py (100%) rename {leetcode => leetcode_old}/trapping_rain_water/README.md (100%) create mode 100644 leetcode_old/trapping_rain_water/__init__.py rename {leetcode => leetcode_old}/trapping_rain_water/playground.ipynb (100%) rename {leetcode => leetcode_old}/trapping_rain_water/solution.py (100%) rename {leetcode => leetcode_old}/trapping_rain_water/tests.py (100%) rename {leetcode => leetcode_old}/valid_anagram/README.md (100%) create mode 100644 leetcode_old/valid_anagram/__init__.py rename {leetcode => leetcode_old}/valid_anagram/playground.ipynb (100%) rename {leetcode => leetcode_old}/valid_anagram/solution.py (100%) rename {leetcode => leetcode_old}/valid_anagram/tests.py (100%) rename {leetcode => leetcode_old}/valid_palindrome/README.md (100%) create mode 100644 leetcode_old/valid_palindrome/__init__.py rename {leetcode => leetcode_old}/valid_palindrome/playground.ipynb (100%) rename {leetcode => leetcode_old}/valid_palindrome/solution.py (100%) rename {leetcode => leetcode_old}/valid_palindrome/tests.py (100%) create mode 100644 leetcode_old/valid_parentheses/README.md create mode 100644 leetcode_old/valid_parentheses/__init__.py rename {leetcode => leetcode_old}/valid_parentheses/playground.ipynb (95%) create mode 100644 leetcode_old/valid_parentheses/solution.py rename {leetcode => leetcode_old}/valid_parentheses/tests.py (100%) rename {leetcode => leetcode_old}/validate_binary_search_tree/README.md (100%) create mode 100644 leetcode_old/validate_binary_search_tree/__init__.py rename {leetcode => leetcode_old}/validate_binary_search_tree/playground.ipynb (100%) rename {leetcode => leetcode_old}/validate_binary_search_tree/solution.py (100%) rename {leetcode => leetcode_old}/validate_binary_search_tree/tests.py (100%) rename {leetcode => leetcode_old}/word_break/README.md (100%) create mode 100644 leetcode_old/word_break/__init__.py rename {leetcode => leetcode_old}/word_break/playground.ipynb (100%) rename {leetcode => leetcode_old}/word_break/solution.py (100%) rename {leetcode => leetcode_old}/word_break/tests.py (100%) rename {leetcode => leetcode_old}/word_ladder/README.md (100%) create mode 100644 leetcode_old/word_ladder/__init__.py rename {leetcode => leetcode_old}/word_ladder/playground.ipynb (100%) rename {leetcode => leetcode_old}/word_ladder/solution.py (100%) rename {leetcode => leetcode_old}/word_ladder/tests.py (100%) rename {leetcode => leetcode_old}/zero_one_matrix/README.md (100%) create mode 100644 leetcode_old/zero_one_matrix/__init__.py rename {leetcode/flood_fill => leetcode_old/zero_one_matrix}/playground.ipynb (73%) rename {leetcode => leetcode_old}/zero_one_matrix/solution.py (100%) rename {leetcode => leetcode_old}/zero_one_matrix/tests.py (100%) diff --git a/.amazonq/plans/01-cookiecutter-template-generalization.md b/.amazonq/plans/01-cookiecutter-template-generalization.md new file mode 100644 index 0000000..379db5f --- /dev/null +++ b/.amazonq/plans/01-cookiecutter-template-generalization.md @@ -0,0 +1,306 @@ +# Cookiecutter Template Generalization Plan + +## Current Issues Identified + +### 1. **Template Constraint Issues** + +- `first_bad_version/solution.py`: Has `# TODO: template constraint` - needs custom `__init__` method +- Template doesn't support constructor parameters or custom initialization logic +- No support for API mocking patterns (like `isBadVersion`) + +### 2. **Limited Method Flexibility** + +- No support for static methods, class methods, or property decorators +- Missing support for optional parameters with defaults +- No support for method overloading patterns +- Cannot handle methods that don't need `self` parameter + +### 3. **Complex Test Patterns Not Supported** + +- Design problems need operation sequences (LRU Cache pattern) +- Interactive problems need API mocking (First Bad Version) +- Some problems need custom helper methods in tests +- No support for test fixtures or complex setup + +### 4. **Playground Notebook Limitations** + +- Fixed 4-cell structure doesn't fit all problem types +- No support for visualization cells +- Missing support for interactive debugging +- Cannot handle complex setup requirements +- **Import conflicts**: Cannot import `tests.py` due to root-level `tests/` directory conflict +- **Missing test helpers**: Need access to test utility functions like `create_cycle_list` in notebooks +- **Path manipulation required**: Manual `sys.path` modifications needed for imports + +### 5. **Missing Template Variants** + +- No support for multiple solution approaches in one file +- Missing templates for specific problem categories (graph, trie, etc.) +- No support for helper classes or data structures +- Cannot generate algorithm explanation comments + +## Proposed Solutions + +### Phase 1: Template Structure Improvements + +#### 1.1 Enhanced Method Configuration + +```json +"solution_methods": [ + { + "name": "method_name", + "parameters": "param1: type1, param2: type2 = default", + "return_type": "ReturnType", + "dummy_return": "default_value", + "decorators": ["@staticmethod", "@classmethod", "@property"], + "is_constructor": true, + "time_complexity": "O(n)", + "space_complexity": "O(1)", + "algorithm_notes": "Brief explanation" + } +] +``` + +#### 1.2 Flexible Template Structure + +```jinja2 +{%- for method in solution_methods %} +{%- if method.decorators %} +{%- for decorator in method.decorators %} + {{ decorator }} +{%- endfor %} +{%- endif %} + # Time: {{ method.time_complexity or "O(?)" }} + # Space: {{ method.space_complexity or "O(?)" }} +{%- if method.is_constructor %} + def {{ method.name }}(self{% if method.parameters %}, {{ method.parameters }}{% endif %}){% if method.return_type %} -> {{ method.return_type }}{% endif %}: +{%- else %} + def {{ method.name }}({% if not method.decorators or "@staticmethod" not in method.decorators %}self{% if method.parameters %}, {% endif %}{% endif %}{{ method.parameters or "" }}){% if method.return_type %} -> {{ method.return_type }}{% endif %}: +{%- endif %} +{%- if method.algorithm_notes %} + # {{ method.algorithm_notes }} +{%- endif %} + # TODO: Implement {{ method.name }} +{%- if method.dummy_return %} + return {{ method.dummy_return }} +{%- endif %} +{%- endfor %} +``` + +### Phase 2: Test Template Enhancements + +#### 2.1 Flexible Test Patterns + +```json +"test_patterns": { + "type": "basic|design|interactive|tree|graph", + "setup_complexity": "simple|complex|custom", + "assertion_type": "direct|tree_comparison|operation_sequence" +} +``` + +#### 2.2 Enhanced Test Methods + +```json +"test_methods": [ + { + "name": "test_method", + "pattern": "parametrized|custom|fixture", + "setup_code": "custom setup if needed", + "parametrize": "params", + "test_cases": "test data", + "body": "test logic", + "cleanup_code": "optional cleanup" + } +] +``` + +### Phase 3: Notebook Template Flexibility + +#### 3.1 Dynamic Cell Structure + +```json +"notebook_cells": [ + { + "id": "imports", + "type": "code", + "content": "{{ playground_imports }}" + }, + { + "id": "setup", + "type": "code", + "content": "{{ playground_setup }}" + }, + { + "id": "visualization", + "type": "code", + "content": "{{ playground_visualization }}", + "optional": true + }, + { + "id": "execute", + "type": "code", + "content": "{{ playground_execution }}" + }, + { + "id": "test", + "type": "code", + "content": "{{ playground_assertion }}" + } +] +``` + +#### 3.2 Test Helper Integration + +```json +"notebook_config": { + "needs_test_helpers": true, + "helper_functions": ["create_cycle_list", "build_tree", "create_graph"], + "import_strategy": "helpers_file|inline_helpers|test_utils" +} +``` + +### Phase 4: Problem-Specific Templates + +#### 4.1 Template Categories + +- **Basic**: Array, string, number problems +- **Design**: Data structure implementation (LRU Cache, Trie) +- **Interactive**: Problems with external APIs (First Bad Version) +- **Tree**: Binary tree problems with visualization +- **Graph**: Graph problems with node structures +- **Algorithm**: Complex algorithms with step-by-step breakdown + +#### 4.2 Category-Specific Fields + +```json +"template_category": "design", +"design_config": { + "operation_methods": ["get", "put", "delete"], + "test_operation_sequence": true, + "supports_multiple_instances": true +}, +"interactive_config": { + "external_apis": ["isBadVersion"], + "mock_setup_required": true +}, +"tree_config": { + "visualization_enabled": true, + "supports_null_nodes": true +} +``` + +## Implementation Plan + +### Step 1: Backup and Analysis (Week 1) + +- [ ] Create backup of current template +- [ ] Analyze all 70+ existing problems for patterns +- [ ] Document current template limitations +- [ ] Create test cases for new template features + +### Step 2: Core Template Enhancement (Week 2) + +- [ ] Implement enhanced method configuration +- [ ] Add support for decorators and special methods +- [ ] Update solution.py template with flexibility +- [ ] Add algorithm complexity and notes support + +### Step 3: Test Template Improvements (Week 3) + +- [ ] Implement flexible test patterns +- [ ] Add support for design problem test sequences +- [ ] Add interactive problem mocking support +- [ ] Update tests.py template + +### Step 4: Notebook Template Enhancement (Week 4) + +- [ ] Implement dynamic cell structure +- [ ] Add visualization cell support +- [ ] Add debugging and exploration cells +- [ ] **Solve import conflicts**: Create `helpers.py` pattern for test utilities +- [ ] **Clean imports**: Remove need for manual `sys.path` manipulation +- [ ] **Test helper access**: Provide clean access to test utility functions +- [ ] Update playground.ipynb template + +### Step 5: Category-Specific Templates (Week 5) + +- [ ] Create template variants for each category +- [ ] Implement category-specific configuration +- [ ] Add template selection logic to generator +- [ ] Update JSON schema validation + +### Step 6: Migration and Testing (Week 6) + +- [ ] Test new template with existing problems +- [ ] Create migration script for existing JSONs +- [ ] Update documentation and examples +- [ ] Validate all generated problems pass linting + +## Success Criteria + +1. **Template Flexibility**: Support all current problem types without manual fixes +2. **Zero Manual Intervention**: Generated code should pass `make p-lint` immediately +3. **Backward Compatibility**: Existing JSON files should work with new template +4. **Enhanced Features**: Support for decorators, complex tests, and visualizations +5. **Category Support**: Automatic template selection based on problem type + +## Risk Mitigation + +1. **Breaking Changes**: Maintain backward compatibility with existing JSONs +2. **Complexity**: Keep template readable with clear documentation +3. **Performance**: Ensure generation time remains fast +4. **Maintenance**: Create comprehensive test suite for template validation + +## Files to Modify + +### Core Template Files + +- `.templates/leetcode/{{cookiecutter.problem_name}}/solution.py` +- `.templates/leetcode/{{cookiecutter.problem_name}}/tests.py` +- `.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb` +- **Add**: `.templates/leetcode/{{cookiecutter.problem_name}}/helpers.py` (conditional) +- `.templates/leetcode/cookiecutter.json` + +### Generator Logic + +- `leetcode_py/tools/generator.py` +- `.templates/leetcode/gen.py` + +### Examples and Documentation + +- `.templates/leetcode/examples/basic.json5` +- `.templates/leetcode/examples/design.json5` +- Add: `.templates/leetcode/examples/interactive.json5` +- Add: `.templates/leetcode/examples/tree.json5` +- Add: `.templates/leetcode/examples/graph.json5` + +### Validation and Testing + +- Add: `.templates/leetcode/validate_template.py` +- Add: `tests/templates/test_template_generation.py` + +## Notebook Import Issue Solutions + +### Option 1: Dedicated `helpers.py` File (Recommended) + +- Generate `helpers.py` when `needs_test_helpers: true` +- Extract reusable functions from test class +- Clean imports: `from helpers import create_cycle_list` +- No path manipulation needed + +### Option 2: Enhanced Test Utils + +- Add problem-specific helpers to `leetcode_py.test_utils` +- Import: `from leetcode_py.test_utils import create_cycle_list` +- Centralized but may become bloated + +### Option 3: Inline Helper Functions + +- Generate helper functions directly in notebook setup cell +- Self-contained but duplicates code +- Good for simple helpers + +**Chosen Approach**: Option 1 with conditional generation based on problem type. + +This plan addresses all identified template limitations while maintaining backward compatibility and adding powerful new features for different problem categories. diff --git a/Makefile b/Makefile index 35646c6..b32ae1c 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ lint: test: - poetry run pytest leetcode/ tests/ \ + poetry run pytest leetcode_old/ leetcode_ideal/ tests/ \ -v --cov=leetcode --cov=leetcode_py \ --cov-report=term-missing \ --cov-report=xml \ diff --git a/leetcode/combination_sum/playground.ipynb b/leetcode/combination_sum/playground.ipynb deleted file mode 100644 index 2bd1081..0000000 --- a/leetcode/combination_sum/playground.ipynb +++ /dev/null @@ -1,57 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": ["from solution import Solution"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": ["# Example test case\ncandidates = [2, 3, 6, 7]\ntarget = 7\nexpected = [[2, 2, 3], [7]]"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": ["result = Solution().combination_sum(candidates, target)\nresult"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": ["# Sort for comparison\nresult_sorted = [sorted(combo) for combo in result]\nexpected_sorted = [sorted(combo) for combo in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted"] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode/contains_duplicate/playground.ipynb b/leetcode/contains_duplicate/playground.ipynb deleted file mode 100644 index 3a84993..0000000 --- a/leetcode/contains_duplicate/playground.ipynb +++ /dev/null @@ -1,57 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": ["from solution import Solution"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": ["# Example test case\nnums = [1, 2, 3, 1]\nexpected = True"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": ["result = Solution().contains_duplicate(nums)\nresult"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": ["assert result == expected"] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode/permutations/playground.ipynb b/leetcode/permutations/playground.ipynb deleted file mode 100644 index a90984e..0000000 --- a/leetcode/permutations/playground.ipynb +++ /dev/null @@ -1,57 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": ["from solution import Solution"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": ["# Example test case\nnums = [1, 2, 3]\nexpected = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": ["result = Solution().permute(nums)\nresult"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": ["# Check that we have the right number of permutations\nassert len(result) == len(expected)\n# Sort for comparison since order doesn't matter\nresult_sorted = [sorted(perm) for perm in result]\nexpected_sorted = [sorted(perm) for perm in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted"] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode/three_sum/playground.ipynb b/leetcode/three_sum/playground.ipynb deleted file mode 100644 index 007270c..0000000 --- a/leetcode/three_sum/playground.ipynb +++ /dev/null @@ -1,57 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": ["from solution import Solution"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": ["# Example test case\nnums = [-1, 0, 1, 2, -1, -4]\nexpected = [[-1, -1, 2], [-1, 0, 1]]"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": ["result = Solution().three_sum(nums)\nresult"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": ["# Sort for comparison since order doesn't matter\nresult_sorted = [sorted(triplet) for triplet in result]\nexpected_sorted = [sorted(triplet) for triplet in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted"] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode/zero_one_matrix/playground.ipynb b/leetcode/zero_one_matrix/playground.ipynb deleted file mode 100644 index 659bd5e..0000000 --- a/leetcode/zero_one_matrix/playground.ipynb +++ /dev/null @@ -1,57 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": ["from solution import Solution"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": ["# Example test case\nmat = [[0, 0, 0], [0, 1, 0], [1, 1, 1]]\nexpected = [[0, 0, 0], [0, 1, 0], [1, 2, 1]]"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": ["result = Solution().update_matrix(mat)\nresult"] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": ["assert result == expected"] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode/clone_graph/README.md b/leetcode_ideal/clone_graph/README.md similarity index 100% rename from leetcode/clone_graph/README.md rename to leetcode_ideal/clone_graph/README.md diff --git a/leetcode/__init__.py b/leetcode_ideal/clone_graph/__init__.py similarity index 100% rename from leetcode/__init__.py rename to leetcode_ideal/clone_graph/__init__.py diff --git a/leetcode_ideal/clone_graph/helpers.py b/leetcode_ideal/clone_graph/helpers.py new file mode 100644 index 0000000..82ade9d --- /dev/null +++ b/leetcode_ideal/clone_graph/helpers.py @@ -0,0 +1,22 @@ +from collections.abc import Sequence + +from leetcode_py import GraphNode + + +def create_graph(adj_list: Sequence[Sequence[int]]) -> GraphNode | None: + return GraphNode.from_adjacency_list(list(list(inner) for inner in adj_list)) + + +def run_clone_graph(solution_class: type, adj_list: Sequence[Sequence[int]]) -> GraphNode | None: + node = create_graph(adj_list) + return solution_class().clone_graph(node) + + +def assert_clone_graph(result: GraphNode | None, expected: GraphNode | None) -> bool: + if result is None and expected is None: + assert True + elif result is not None and expected is not None: + assert result.is_clone(expected) + else: + assert False + return True diff --git a/leetcode_ideal/clone_graph/playground.ipynb b/leetcode_ideal/clone_graph/playground.ipynb new file mode 100644 index 0000000..a6c7d64 --- /dev/null +++ b/leetcode_ideal/clone_graph/playground.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_clone_graph, create_graph, run_clone_graph\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "adj_list = [[2, 4], [1, 3], [2, 4], [1, 3]]\n", + "expected = create_graph(adj_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "2\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "1--2\n", + "\n", + "\n", + "\n", + "\n", + "4\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "1--4\n", + "\n", + "\n", + "\n", + "\n", + "3\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "2--3\n", + "\n", + "\n", + "\n", + "\n", + "3--4\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "GraphNode({1: [2, 4], 2: [1, 3], 3: [2, 4], 4: [1, 3]})" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_clone_graph(Solution, adj_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_clone_graph(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/clone_graph/solution.py b/leetcode_ideal/clone_graph/solution.py similarity index 100% rename from leetcode/clone_graph/solution.py rename to leetcode_ideal/clone_graph/solution.py diff --git a/leetcode_ideal/clone_graph/test_solution.py b/leetcode_ideal/clone_graph/test_solution.py new file mode 100644 index 0000000..30068c7 --- /dev/null +++ b/leetcode_ideal/clone_graph/test_solution.py @@ -0,0 +1,55 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_clone_graph, create_graph, run_clone_graph + + +class TestCloneGraph: + @pytest.mark.parametrize( + "adj_list", + [ + [[2, 4], [1, 3], [2, 4], [1, 3]], + [[]], + [], + ], + ) + @logged_test + def test_clone_graph_recursive(self, adj_list: list[list[int]]): + from .solution import Solution + + result = run_clone_graph(Solution, adj_list) + expected = create_graph(adj_list) + assert_clone_graph(result, expected) + + @pytest.mark.parametrize( + "adj_list", + [ + [[2, 4], [1, 3], [2, 4], [1, 3]], + [[]], + [], + ], + ) + @logged_test + def test_clone_graph_dfs(self, adj_list: list[list[int]]): + from .solution import SolutionDFS + + result = run_clone_graph(SolutionDFS, adj_list) + expected = create_graph(adj_list) + assert_clone_graph(result, expected) + + @pytest.mark.parametrize( + "adj_list", + [ + [[2, 4], [1, 3], [2, 4], [1, 3]], + [[]], + [], + ], + ) + @logged_test + def test_clone_graph_bfs(self, adj_list: list[list[int]]): + from .solution import SolutionBFS + + result = run_clone_graph(SolutionBFS, adj_list) + expected = create_graph(adj_list) + assert_clone_graph(result, expected) diff --git a/leetcode/contains_duplicate/README.md b/leetcode_ideal/contains_duplicate/README.md similarity index 100% rename from leetcode/contains_duplicate/README.md rename to leetcode_ideal/contains_duplicate/README.md diff --git a/leetcode/accounts_merge/__init__.py b/leetcode_ideal/contains_duplicate/__init__.py similarity index 100% rename from leetcode/accounts_merge/__init__.py rename to leetcode_ideal/contains_duplicate/__init__.py diff --git a/leetcode_ideal/contains_duplicate/helpers.py b/leetcode_ideal/contains_duplicate/helpers.py new file mode 100644 index 0000000..44b4503 --- /dev/null +++ b/leetcode_ideal/contains_duplicate/helpers.py @@ -0,0 +1,7 @@ +def run_contains_duplicate(solution_class: type, nums: list[int]) -> bool: + return solution_class().contains_duplicate(nums) + + +def assert_contains_duplicate(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode_ideal/contains_duplicate/playground.ipynb b/leetcode_ideal/contains_duplicate/playground.ipynb new file mode 100644 index 0000000..4c451ba --- /dev/null +++ b/leetcode_ideal/contains_duplicate/playground.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_contains_duplicate, run_contains_duplicate\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [1, 2, 3, 1]\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_contains_duplicate(Solution, nums)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_contains_duplicate(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/contains_duplicate/solution.py b/leetcode_ideal/contains_duplicate/solution.py similarity index 100% rename from leetcode/contains_duplicate/solution.py rename to leetcode_ideal/contains_duplicate/solution.py diff --git a/leetcode_ideal/contains_duplicate/test_solution.py b/leetcode_ideal/contains_duplicate/test_solution.py new file mode 100644 index 0000000..ceb3471 --- /dev/null +++ b/leetcode_ideal/contains_duplicate/test_solution.py @@ -0,0 +1,31 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_contains_duplicate, run_contains_duplicate + + +class TestContainsDuplicate: + @pytest.mark.parametrize( + "nums, expected", + [ + ([1, 2, 3, 1], True), + ([1, 2, 3, 4], False), + ([1, 1, 1, 3, 3, 4, 3, 2, 4, 2], True), + ([], False), + ([1], False), + ([1, 1], True), + ([-1, -2, -3, -1], True), + ([-1, -2, -3, -4], False), + ([0, 0], True), + ([1000000, 999999, 1000000], True), + (list(range(1000)), False), + ([1] * 1000, True), + ], + ) + @logged_test + def test_contains_duplicate(self, nums: list[int], expected: bool): + from .solution import Solution + + result = run_contains_duplicate(Solution, nums) + assert_contains_duplicate(result, expected) diff --git a/leetcode/first_bad_version/README.md b/leetcode_ideal/first_bad_version/README.md similarity index 100% rename from leetcode/first_bad_version/README.md rename to leetcode_ideal/first_bad_version/README.md diff --git a/leetcode/add_binary/__init__.py b/leetcode_ideal/first_bad_version/__init__.py similarity index 100% rename from leetcode/add_binary/__init__.py rename to leetcode_ideal/first_bad_version/__init__.py diff --git a/leetcode_ideal/first_bad_version/helpers.py b/leetcode_ideal/first_bad_version/helpers.py new file mode 100644 index 0000000..31dc58a --- /dev/null +++ b/leetcode_ideal/first_bad_version/helpers.py @@ -0,0 +1,8 @@ +def run_first_bad_version(solution_class: type, n: int, bad: int) -> int: + solution = solution_class(bad) + return solution.first_bad_version(n) + + +def assert_first_bad_version(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode_ideal/first_bad_version/playground.ipynb b/leetcode_ideal/first_bad_version/playground.ipynb new file mode 100644 index 0000000..b029826 --- /dev/null +++ b/leetcode_ideal/first_bad_version/playground.ipynb @@ -0,0 +1,92 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_first_bad_version, run_first_bad_version\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "n = 5\n", + "bad = 4\n", + "expected = 4" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_first_bad_version(Solution, n, bad)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_first_bad_version(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/first_bad_version/solution.py b/leetcode_ideal/first_bad_version/solution.py similarity index 100% rename from leetcode/first_bad_version/solution.py rename to leetcode_ideal/first_bad_version/solution.py diff --git a/leetcode_ideal/first_bad_version/test_solution.py b/leetcode_ideal/first_bad_version/test_solution.py new file mode 100644 index 0000000..e97cf8c --- /dev/null +++ b/leetcode_ideal/first_bad_version/test_solution.py @@ -0,0 +1,18 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_first_bad_version, run_first_bad_version +from .solution import Solution + + +class TestFirstBadVersion: + + @pytest.mark.parametrize( + "n, bad, expected", [(5, 4, 4), (1, 1, 1), (3, 1, 1), (10, 7, 7), (5, -1, 1)] + ) + @logged_test + def test_first_bad_version(self, n: int, bad: int, expected: int): + + result = run_first_bad_version(Solution, n, bad) + assert_first_bad_version(result, expected) diff --git a/leetcode/implement_trie_prefix_tree/README.md b/leetcode_ideal/implement_trie_prefix_tree/README.md similarity index 100% rename from leetcode/implement_trie_prefix_tree/README.md rename to leetcode_ideal/implement_trie_prefix_tree/README.md diff --git a/leetcode/balanced_binary_tree/__init__.py b/leetcode_ideal/implement_trie_prefix_tree/__init__.py similarity index 100% rename from leetcode/balanced_binary_tree/__init__.py rename to leetcode_ideal/implement_trie_prefix_tree/__init__.py diff --git a/leetcode_ideal/implement_trie_prefix_tree/helpers.py b/leetcode_ideal/implement_trie_prefix_tree/helpers.py new file mode 100644 index 0000000..3c368ac --- /dev/null +++ b/leetcode_ideal/implement_trie_prefix_tree/helpers.py @@ -0,0 +1,25 @@ +from typing import Any + + +def run_trie( + solution_class: type, operations: list[str], inputs: list[list[str]] +) -> tuple[list[bool | None], Any]: + trie = None + results: list[bool | None] = [] + for i, op in enumerate(operations): + if op == "Trie": + trie = solution_class() + results.append(None) + elif op == "insert" and trie is not None: + trie.insert(inputs[i][0]) + results.append(None) + elif op == "search" and trie is not None: + results.append(trie.search(inputs[i][0])) + elif op == "starts_with" and trie is not None: + results.append(trie.starts_with(inputs[i][0])) + return results, trie + + +def assert_trie(result: list[bool | None], expected: list[bool | None]) -> bool: + assert result == expected + return True diff --git a/leetcode_ideal/implement_trie_prefix_tree/playground.ipynb b/leetcode_ideal/implement_trie_prefix_tree/playground.ipynb new file mode 100644 index 0000000..6b8493c --- /dev/null +++ b/leetcode_ideal/implement_trie_prefix_tree/playground.ipynb @@ -0,0 +1,204 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_trie, run_trie\n", + "from solution import Trie" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "operations = [\"Trie\", \"insert\", \"search\", \"search\", \"starts_with\", \"insert\", \"search\"]\n", + "inputs = [[], [\"apple\"], [\"apple\"], [\"app\"], [\"app\"], [\"app\"], [\"app\"]]\n", + "expected = [None, None, True, False, True, None, True]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Results: [None, None, True, False, True, None, True]\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root\n", + "\n", + "root\n", + "\n", + "\n", + "\n", + "root_0\n", + "\n", + "a\n", + "\n", + "\n", + "\n", + "root->root_0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root_0_0\n", + "\n", + "p\n", + "\n", + "\n", + "\n", + "root_0->root_0_0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root_0_0_0\n", + "\n", + "p\n", + "\n", + "\n", + "\n", + "root_0_0->root_0_0_0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root_0_0_0_0\n", + "\n", + "l\n", + "\n", + "\n", + "\n", + "root_0_0_0->root_0_0_0_0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root_0_0_0_leaf_1\n", + "\n", + "#: True\n", + "\n", + "\n", + "\n", + "root_0_0_0->root_0_0_0_leaf_1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root_0_0_0_0_0\n", + "\n", + "e\n", + "\n", + "\n", + "\n", + "root_0_0_0_0->root_0_0_0_0_0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root_0_0_0_0_0_leaf_0\n", + "\n", + "#: True\n", + "\n", + "\n", + "\n", + "root_0_0_0_0_0->root_0_0_0_0_0_leaf_0\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result, trie = run_trie(Trie, operations, inputs)\n", + "print(f\"Results: {result}\")\n", + "trie # Show the trie visualization" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c8308208", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_trie(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/implement_trie_prefix_tree/solution.py b/leetcode_ideal/implement_trie_prefix_tree/solution.py similarity index 100% rename from leetcode/implement_trie_prefix_tree/solution.py rename to leetcode_ideal/implement_trie_prefix_tree/solution.py diff --git a/leetcode_ideal/implement_trie_prefix_tree/test_solution.py b/leetcode_ideal/implement_trie_prefix_tree/test_solution.py new file mode 100644 index 0000000..267b613 --- /dev/null +++ b/leetcode_ideal/implement_trie_prefix_tree/test_solution.py @@ -0,0 +1,42 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_trie, run_trie + + +class TestImplementTriePrefixTree: + @pytest.mark.parametrize( + "operations, inputs, expected", + [ + ( + ["Trie", "insert", "search", "search", "starts_with", "insert", "search"], + [[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]], + [None, None, True, False, True, None, True], + ), + ( + ["Trie", "insert", "insert", "search", "search", "starts_with", "starts_with"], + [[], ["hello"], ["world"], ["hello"], ["hi"], ["hel"], ["wor"]], + [None, None, None, True, False, True, True], + ), + ( + ["Trie", "insert", "insert", "search", "search", "starts_with", "starts_with"], + [[], ["a"], ["aa"], ["a"], ["aa"], ["a"], ["aa"]], + [None, None, None, True, True, True, True], + ), + ( + ["Trie", "insert", "search", "starts_with", "insert", "search", "starts_with"], + [[], ["test"], ["testing"], ["test"], ["testing"], ["testing"], ["test"]], + [None, None, False, True, None, True, True], + ), + (["Trie", "search", "starts_with"], [[], ["empty"], ["empty"]], [None, False, False]), + ], + ) + @logged_test + def test_trie_operations( + self, operations: list[str], inputs: list[list[str]], expected: list[bool | None] + ): + from .solution import Trie + + result, _ = run_trie(Trie, operations, inputs) + assert_trie(result, expected) diff --git a/leetcode/invert_binary_tree/README.md b/leetcode_ideal/invert_binary_tree/README.md similarity index 100% rename from leetcode/invert_binary_tree/README.md rename to leetcode_ideal/invert_binary_tree/README.md diff --git a/leetcode/basic_calculator/__init__.py b/leetcode_ideal/invert_binary_tree/__init__.py similarity index 100% rename from leetcode/basic_calculator/__init__.py rename to leetcode_ideal/invert_binary_tree/__init__.py diff --git a/leetcode_ideal/invert_binary_tree/helpers.py b/leetcode_ideal/invert_binary_tree/helpers.py new file mode 100644 index 0000000..b917772 --- /dev/null +++ b/leetcode_ideal/invert_binary_tree/helpers.py @@ -0,0 +1,15 @@ +from leetcode_py import TreeNode + + +def create_tree(root_list: list[int | None]) -> TreeNode[int] | None: + return TreeNode[int].from_list(root_list) + + +def run_invert_tree(solution_class: type, root_list: list[int | None]) -> TreeNode[int] | None: + root = create_tree(root_list) + return solution_class().invert_tree(root) + + +def assert_invert_tree(result: TreeNode[int] | None, expected: TreeNode[int] | None) -> bool: + assert result == expected + return True diff --git a/leetcode_ideal/invert_binary_tree/playground.ipynb b/leetcode_ideal/invert_binary_tree/playground.ipynb new file mode 100644 index 0000000..9765d29 --- /dev/null +++ b/leetcode_ideal/invert_binary_tree/playground.ipynb @@ -0,0 +1,185 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_invert_tree, run_invert_tree\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import create_tree\n", + "\n", + "# Example test case\n", + "root_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\n", + "expected = create_tree([4, 7, 2, 9, 6, 3, 1])" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "0\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "4\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "0->4\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "2\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "1->2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "3\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "1->3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "5\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "4->5\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "6\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "4->6\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "TreeNode([4, 7, 2, 9, 6, 3, 1])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_invert_tree(Solution, root_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_invert_tree(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/invert_binary_tree/solution.py b/leetcode_ideal/invert_binary_tree/solution.py similarity index 100% rename from leetcode/invert_binary_tree/solution.py rename to leetcode_ideal/invert_binary_tree/solution.py diff --git a/leetcode_ideal/invert_binary_tree/test_solution.py b/leetcode_ideal/invert_binary_tree/test_solution.py new file mode 100644 index 0000000..b664517 --- /dev/null +++ b/leetcode_ideal/invert_binary_tree/test_solution.py @@ -0,0 +1,58 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_invert_tree, run_invert_tree + + +class TestInvertBinaryTree: + @pytest.mark.parametrize( + "root_list, expected_list", + [ + ([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), + ([2, 1, 3], [2, 3, 1]), + ([], []), + ], + ) + @logged_test + def test_invert_tree_recursive(self, root_list: list[int | None], expected_list: list[int | None]): + from .helpers import create_tree + from .solution import Solution + + result = run_invert_tree(Solution, root_list) + expected = create_tree(expected_list) + assert_invert_tree(result, expected) + + @pytest.mark.parametrize( + "root_list, expected_list", + [ + ([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), + ([2, 1, 3], [2, 3, 1]), + ([], []), + ], + ) + @logged_test + def test_invert_tree_dfs(self, root_list: list[int | None], expected_list: list[int | None]): + from .helpers import create_tree + from .solution import SolutionDFS + + result = run_invert_tree(SolutionDFS, root_list) + expected = create_tree(expected_list) + assert_invert_tree(result, expected) + + @pytest.mark.parametrize( + "root_list, expected_list", + [ + ([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), + ([2, 1, 3], [2, 3, 1]), + ([], []), + ], + ) + @logged_test + def test_invert_tree_bfs(self, root_list: list[int | None], expected_list: list[int | None]): + from .helpers import create_tree + from .solution import SolutionBFS + + result = run_invert_tree(SolutionBFS, root_list) + expected = create_tree(expected_list) + assert_invert_tree(result, expected) diff --git a/leetcode/linked_list_cycle/README.md b/leetcode_ideal/linked_list_cycle/README.md similarity index 100% rename from leetcode/linked_list_cycle/README.md rename to leetcode_ideal/linked_list_cycle/README.md diff --git a/leetcode/best_time_to_buy_and_sell_stock/__init__.py b/leetcode_ideal/linked_list_cycle/__init__.py similarity index 100% rename from leetcode/best_time_to_buy_and_sell_stock/__init__.py rename to leetcode_ideal/linked_list_cycle/__init__.py diff --git a/leetcode_ideal/linked_list_cycle/helpers.py b/leetcode_ideal/linked_list_cycle/helpers.py new file mode 100644 index 0000000..0d4489a --- /dev/null +++ b/leetcode_ideal/linked_list_cycle/helpers.py @@ -0,0 +1,27 @@ +from leetcode_py import ListNode + + +def create_cycle_list(values: list[int], pos: int) -> ListNode | None: + if not values: + return None + nodes = [] + head = ListNode(values[0]) + nodes.append(head) + current = head + for i in range(1, len(values)): + current.next = ListNode(values[i]) + current = current.next + nodes.append(current) + if pos != -1 and pos < len(nodes): + current.next = nodes[pos] + return head + + +def run_has_cycle(solution_class: type, values: list[int], pos: int) -> bool: + head = create_cycle_list(values, pos) + return solution_class().has_cycle(head) + + +def assert_has_cycle(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode_ideal/linked_list_cycle/playground.ipynb b/leetcode_ideal/linked_list_cycle/playground.ipynb new file mode 100644 index 0000000..e8662cf --- /dev/null +++ b/leetcode_ideal/linked_list_cycle/playground.ipynb @@ -0,0 +1,92 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_has_cycle, run_has_cycle\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "values = [3, 2, 0, -4]\n", + "pos = 1\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_has_cycle(Solution, values, pos)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_has_cycle(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/linked_list_cycle/solution.py b/leetcode_ideal/linked_list_cycle/solution.py similarity index 100% rename from leetcode/linked_list_cycle/solution.py rename to leetcode_ideal/linked_list_cycle/solution.py diff --git a/leetcode_ideal/linked_list_cycle/test_solution.py b/leetcode_ideal/linked_list_cycle/test_solution.py new file mode 100644 index 0000000..652ebbc --- /dev/null +++ b/leetcode_ideal/linked_list_cycle/test_solution.py @@ -0,0 +1,38 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_has_cycle, run_has_cycle +from .solution import Solution + + +class TestLinkedListCycle: + + @pytest.mark.parametrize( + "values, pos, expected", + [ + ([3, 2, 0, -4], 1, True), + ([1, 2], 0, True), + ([1], -1, False), + ([], -1, False), + ([1, 2, 3], -1, False), + ([1, 2, 3, 4, 5], 0, True), + ([1, 2, 3, 4, 5], 2, True), + ([1, 2, 3, 4, 5], 4, True), + ([1], 0, True), + ([1, 2], 1, True), + ([1, 2, 3, 4, 5, 6, 7, 8], 3, True), + ([1, 2, 3, 4, 5, 6, 7, 8], -1, False), + ([1, 2], -1, False), + ([5, 10], 0, True), + ([5, 10], 1, True), + ([0], -1, False), + ([-1, -2, -3], 1, True), + ([100, 200, 300], 0, True), + ], + ) + @logged_test + def test_has_cycle(self, values: list[int], pos: int, expected: bool): + + result = run_has_cycle(Solution, values, pos) + assert_has_cycle(result, expected) diff --git a/leetcode/lru_cache/README.md b/leetcode_ideal/lru_cache/README.md similarity index 100% rename from leetcode/lru_cache/README.md rename to leetcode_ideal/lru_cache/README.md diff --git a/leetcode/binary_search/__init__.py b/leetcode_ideal/lru_cache/__init__.py similarity index 100% rename from leetcode/binary_search/__init__.py rename to leetcode_ideal/lru_cache/__init__.py diff --git a/leetcode_ideal/lru_cache/helpers.py b/leetcode_ideal/lru_cache/helpers.py new file mode 100644 index 0000000..8fdef5d --- /dev/null +++ b/leetcode_ideal/lru_cache/helpers.py @@ -0,0 +1,23 @@ +from typing import Any + + +def run_lru_cache( + solution_class: type, operations: list[str], inputs: list[list[int]] +) -> tuple[list[int | None], Any]: + cache = None + results: list[int | None] = [] + for i, op in enumerate(operations): + if op == "LRUCache": + cache = solution_class(inputs[i][0]) + results.append(None) + elif op == "get" and cache is not None: + results.append(cache.get(inputs[i][0])) + elif op == "put" and cache is not None: + cache.put(inputs[i][0], inputs[i][1]) + results.append(None) + return results, cache + + +def assert_lru_cache(result: list[int | None], expected: list[int | None]) -> bool: + assert result == expected + return True diff --git a/leetcode_ideal/lru_cache/playground.ipynb b/leetcode_ideal/lru_cache/playground.ipynb new file mode 100644 index 0000000..0d58702 --- /dev/null +++ b/leetcode_ideal/lru_cache/playground.ipynb @@ -0,0 +1,100 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_lru_cache, run_lru_cache\n", + "from solution import LRUCache" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "operations = [\"LRUCache\", \"put\", \"put\", \"get\", \"put\", \"get\", \"put\", \"get\", \"get\", \"get\"]\n", + "inputs = [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\n", + "expected = [None, None, None, 1, None, -1, None, -1, 3, 4]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Results: [None, None, None, 1, None, -1, None, -1, 3, 4]\n" + ] + }, + { + "data": { + "text/plain": [ + "OrderedDict([(3, 3), (4, 4)])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result, cache = run_lru_cache(LRUCache, operations, inputs)\n", + "print(f\"Results: {result}\")\n", + "cache.cache" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_lru_cache(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode_ideal/lru_cache/solution.py b/leetcode_ideal/lru_cache/solution.py new file mode 100644 index 0000000..ea09dcd --- /dev/null +++ b/leetcode_ideal/lru_cache/solution.py @@ -0,0 +1,126 @@ +from collections import OrderedDict + +from leetcode_py.data_structures.doubly_list_node import DoublyListNode + + +class LRUCache: + # Space: O(capacity) + def __init__(self, capacity: int) -> None: + self.capacity = capacity + self.cache: OrderedDict[int, int] = OrderedDict() + + # Time: O(1) + # Space: O(1) + def get(self, key: int) -> int: + if key not in self.cache: + return -1 + + # Move to end (most recent) + self.cache.move_to_end(key) + return self.cache[key] + + # Time: O(1) + # Space: O(1) + def put(self, key: int, value: int) -> None: + if key in self.cache: + # Update existing and move to end + self.cache[key] = value + self.cache.move_to_end(key) + else: + # Add new + if len(self.cache) >= self.capacity: + # Remove LRU (first item) + self.cache.popitem(last=False) + + self.cache[key] = value + + +class CacheNode(DoublyListNode[int]): + def __init__(self, key: int = 0, val: int = 0) -> None: + super().__init__(val) + self.key = key + + +class LRUCacheWithDoublyList: + def __init__(self, capacity: int) -> None: + self.capacity = capacity + self.cache: dict[int, CacheNode] = {} + + # Dummy head and tail nodes + self.head = CacheNode() + self.tail = CacheNode() + self.head.next = self.tail + self.tail.prev = self.head + + def _add_node(self, node: CacheNode) -> None: + """Add node right after head""" + node.prev = self.head + node.next = self.head.next + if self.head.next: + self.head.next.prev = node + self.head.next = node + + def _remove_node(self, node: CacheNode) -> None: + """Remove node from list""" + if node.prev: + node.prev.next = node.next + if node.next: + node.next.prev = node.prev + + def _move_to_head(self, node: CacheNode) -> None: + """Move node to head (most recent)""" + self._remove_node(node) + self._add_node(node) + + def _pop_tail(self) -> CacheNode: + """Remove last node before tail""" + last_node = self.tail.prev + assert isinstance(last_node, CacheNode), "Expected CacheNode" + self._remove_node(last_node) + return last_node + + def get(self, key: int) -> int: + node = self.cache.get(key) + if not node: + return -1 + + # Move to head (most recent) + self._move_to_head(node) + return node.val + + def put(self, key: int, value: int) -> None: + node = self.cache.get(key) + + if node: + # Update existing + node.val = value + self._move_to_head(node) + else: + # Add new + new_node = CacheNode(key, value) + + if len(self.cache) >= self.capacity: + # Remove LRU + tail = self._pop_tail() + del self.cache[tail.key] + + self.cache[key] = new_node + self._add_node(new_node) + + +# LRU CACHE IMPLEMENTATION STRATEGIES +# +# Strategy 1: OrderedDict (LRUCache) +# - Uses Python's built-in OrderedDict +# - move_to_end() for O(1) reordering +# - popitem(last=False) for O(1) LRU removal +# - Simple and clean implementation +# +# Strategy 2: HashMap + Doubly Linked List (LRUCacheWithDoublyList) +# - HashMap for O(1) key lookup +# - Doubly linked list for O(1) insertion/deletion +# - Manual node management with dummy head/tail +# - More complex but shows underlying data structure +# +# Both achieve O(1) time complexity for get() and put() +# Space complexity: O(capacity) for both approaches diff --git a/leetcode_ideal/lru_cache/test_solution.py b/leetcode_ideal/lru_cache/test_solution.py new file mode 100644 index 0000000..ed4cc3f --- /dev/null +++ b/leetcode_ideal/lru_cache/test_solution.py @@ -0,0 +1,61 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_lru_cache, run_lru_cache + + +class TestLRUCache: + @pytest.mark.parametrize( + "operations, inputs, expected", + [ + ( + ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"], + [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]], + [None, None, None, 1, None, -1, None, -1, 3, 4], + ), + ( + ["LRUCache", "get", "put", "get", "put", "put", "get", "get"], + [[2], [2], [2, 6], [1], [1, 5], [1, 2], [1], [2]], + [None, -1, None, -1, None, None, 2, 6], + ), + ( + ["LRUCache", "put", "get", "put", "get", "get"], + [[1], [2, 1], [2], [3, 2], [2], [3]], + [None, None, 1, None, -1, 2], + ), + ], + ) + @logged_test + def test_lru_cache( + self, + operations: list[str], + inputs: list[list[int]], + expected: list[int | None], + ): + from .solution import LRUCache + + result, _ = run_lru_cache(LRUCache, operations, inputs) + assert_lru_cache(result, expected) + + @pytest.mark.parametrize( + "operations, inputs, expected", + [ + ( + ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"], + [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]], + [None, None, None, 1, None, -1, None, -1, 3, 4], + ), + ], + ) + @logged_test + def test_lru_cache_with_doubly_list( + self, + operations: list[str], + inputs: list[list[int]], + expected: list[int | None], + ): + from .solution import LRUCacheWithDoublyList + + result, _ = run_lru_cache(LRUCacheWithDoublyList, operations, inputs) + assert_lru_cache(result, expected) diff --git a/leetcode/reverse_linked_list/README.md b/leetcode_ideal/reverse_linked_list/README.md similarity index 100% rename from leetcode/reverse_linked_list/README.md rename to leetcode_ideal/reverse_linked_list/README.md diff --git a/leetcode/binary_tree_level_order_traversal/__init__.py b/leetcode_ideal/reverse_linked_list/__init__.py similarity index 100% rename from leetcode/binary_tree_level_order_traversal/__init__.py rename to leetcode_ideal/reverse_linked_list/__init__.py diff --git a/leetcode_ideal/reverse_linked_list/helpers.py b/leetcode_ideal/reverse_linked_list/helpers.py new file mode 100644 index 0000000..eb7d7ba --- /dev/null +++ b/leetcode_ideal/reverse_linked_list/helpers.py @@ -0,0 +1,15 @@ +from leetcode_py import ListNode + + +def create_list(head_list: list[int]) -> ListNode[int] | None: + return ListNode.from_list(list(head_list)) + + +def run_reverse_list(solution_class: type, head_list: list[int]) -> ListNode[int] | None: + head = create_list(head_list) + return solution_class().reverse_list(head) + + +def assert_reverse_list(result: ListNode[int] | None, expected: ListNode[int] | None) -> bool: + assert result == expected + return True diff --git a/leetcode_ideal/reverse_linked_list/playground.ipynb b/leetcode_ideal/reverse_linked_list/playground.ipynb new file mode 100644 index 0000000..32ced7a --- /dev/null +++ b/leetcode_ideal/reverse_linked_list/playground.ipynb @@ -0,0 +1,159 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_reverse_list, create_list, run_reverse_list\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "head_list = [1, 2, 3, 4, 5]\n", + "expected = create_list([5, 4, 3, 2, 1])" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_0\n", + "\n", + "5\n", + "\n", + "\n", + "\n", + "node_1\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "node_0->node_1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_2\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "node_1->node_2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_3\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "node_2->node_3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_4\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "node_3->node_4\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "ListNode([5, 4, 3, 2, 1])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_reverse_list(Solution, head_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_reverse_list(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/reverse_linked_list/solution.py b/leetcode_ideal/reverse_linked_list/solution.py similarity index 100% rename from leetcode/reverse_linked_list/solution.py rename to leetcode_ideal/reverse_linked_list/solution.py diff --git a/leetcode_ideal/reverse_linked_list/test_solution.py b/leetcode_ideal/reverse_linked_list/test_solution.py new file mode 100644 index 0000000..3dd9237 --- /dev/null +++ b/leetcode_ideal/reverse_linked_list/test_solution.py @@ -0,0 +1,30 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_reverse_list, create_list, run_reverse_list + + +class TestReverseLinkedList: + @pytest.mark.parametrize( + "head_list, expected_list", + [ + ([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]), + ([1, 2], [2, 1]), + ([1], [1]), + ([], []), + ([1, 2, 3], [3, 2, 1]), + ([1, 2, 3, 4], [4, 3, 2, 1]), + ([-1, -2, -3], [-3, -2, -1]), + ([0], [0]), + ([5000, -5000], [-5000, 5000]), + ([1, 1, 1], [1, 1, 1]), + ], + ) + @logged_test + def test_reverse_list(self, head_list: list[int], expected_list: list[int]): + from .solution import Solution + + result = run_reverse_list(Solution, head_list) + expected = create_list(expected_list) + assert_reverse_list(result, expected) diff --git a/leetcode/serialize_and_deserialize_binary_tree/README.md b/leetcode_ideal/serialize_and_deserialize_binary_tree/README.md similarity index 100% rename from leetcode/serialize_and_deserialize_binary_tree/README.md rename to leetcode_ideal/serialize_and_deserialize_binary_tree/README.md diff --git a/leetcode/binary_tree_right_side_view/__init__.py b/leetcode_ideal/serialize_and_deserialize_binary_tree/__init__.py similarity index 100% rename from leetcode/binary_tree_right_side_view/__init__.py rename to leetcode_ideal/serialize_and_deserialize_binary_tree/__init__.py diff --git a/leetcode_ideal/serialize_and_deserialize_binary_tree/helpers.py b/leetcode_ideal/serialize_and_deserialize_binary_tree/helpers.py new file mode 100644 index 0000000..faaecd6 --- /dev/null +++ b/leetcode_ideal/serialize_and_deserialize_binary_tree/helpers.py @@ -0,0 +1,26 @@ +from collections.abc import Sequence + +from leetcode_py import TreeNode + + +def create_tree(root_list: Sequence[int | None]) -> TreeNode[int] | None: + return TreeNode[int].from_list(list(root_list)) if root_list else None + + +def run_serialize_deserialize( + solution_class: type, root_list: Sequence[int | None] +) -> TreeNode[int] | None: + root = create_tree(root_list) + codec = solution_class() + serialized = codec.serialize(root) + return codec.deserialize(serialized) + + +def assert_serialize_deserialize(result: TreeNode[int] | None, expected: TreeNode[int] | None) -> bool: + if result is None and expected is None: + assert True + elif result is not None and expected is not None: + assert result.to_list() == expected.to_list() + else: + assert False + return True diff --git a/leetcode_ideal/serialize_and_deserialize_binary_tree/playground.ipynb b/leetcode_ideal/serialize_and_deserialize_binary_tree/playground.ipynb new file mode 100644 index 0000000..76648a2 --- /dev/null +++ b/leetcode_ideal/serialize_and_deserialize_binary_tree/playground.ipynb @@ -0,0 +1,159 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_serialize_deserialize, create_tree, run_serialize_deserialize\n", + "from solution import Codec" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root_list = [1, 2, 3, None, None, 4, 5]\n", + "expected = create_tree(root_list)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "2\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "0->2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "3\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "2->3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "4\n", + "\n", + "5\n", + "\n", + "\n", + "\n", + "2->4\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "TreeNode([1, 2, 3, None, None, 4, 5])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_serialize_deserialize(Codec, root_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_serialize_deserialize(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/serialize_and_deserialize_binary_tree/solution.py b/leetcode_ideal/serialize_and_deserialize_binary_tree/solution.py similarity index 100% rename from leetcode/serialize_and_deserialize_binary_tree/solution.py rename to leetcode_ideal/serialize_and_deserialize_binary_tree/solution.py diff --git a/leetcode_ideal/serialize_and_deserialize_binary_tree/test_solution.py b/leetcode_ideal/serialize_and_deserialize_binary_tree/test_solution.py new file mode 100644 index 0000000..45c17ba --- /dev/null +++ b/leetcode_ideal/serialize_and_deserialize_binary_tree/test_solution.py @@ -0,0 +1,43 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_serialize_deserialize, create_tree, run_serialize_deserialize + + +class TestSerializeAndDeserializeBinaryTree: + @pytest.mark.parametrize( + "root_list", + [ + ([1, 2, 3, None, None, 4, 5]), + ([]), + ([1]), + ([1, 2]), + ([1, None, 2]), + ([1, 2, 3, 4, 5, 6, 7]), + ([5, 2, 3, None, None, 2, 4, 3, 1]), + ([0]), + ([-1]), + ([1000]), + ([-1000]), + ([1, 2, None, 3, None, 4, None]), + ([1, None, 2, None, 3, None, 4]), + ([-5, -3, -8, -2, -1, -7, -9]), + ([0, -1, 1, -2, None, None, 2]), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]), + ([1, None, 2, None, 3, None, 4, None, 5]), + ([1, 2, None, 3, None, 4, None, 5]), + ([5, 4, 7, 3, None, 2, None, -1, None, 9]), + ([1, 1, 1, 1, 1, 1, 1]), + ([2, 2, None, 2, None]), + ([10, 5, 15, None, 6, 12, 20, None, None, None, 13, 18, 25]), + ([50, 30, 70, 20, 40, 60, 80, 10, 25, 35, 45]), + ], + ) + @logged_test + def test_serialize_deserialize(self, root_list: list[int | None]): + from .solution import Codec + + result = run_serialize_deserialize(Codec, root_list) + expected = create_tree(root_list) + assert_serialize_deserialize(result, expected) diff --git a/leetcode/serialize_and_deserialize_binary_tree/tests.py b/leetcode_ideal/serialize_and_deserialize_binary_tree/tests.py similarity index 100% rename from leetcode/serialize_and_deserialize_binary_tree/tests.py rename to leetcode_ideal/serialize_and_deserialize_binary_tree/tests.py diff --git a/leetcode/valid_parentheses/README.md b/leetcode_ideal/valid_parentheses/README.md similarity index 100% rename from leetcode/valid_parentheses/README.md rename to leetcode_ideal/valid_parentheses/README.md diff --git a/leetcode/climbing_stairs/__init__.py b/leetcode_ideal/valid_parentheses/__init__.py similarity index 100% rename from leetcode/climbing_stairs/__init__.py rename to leetcode_ideal/valid_parentheses/__init__.py diff --git a/leetcode_ideal/valid_parentheses/helpers.py b/leetcode_ideal/valid_parentheses/helpers.py new file mode 100644 index 0000000..449141e --- /dev/null +++ b/leetcode_ideal/valid_parentheses/helpers.py @@ -0,0 +1,7 @@ +def run_is_valid(solution_class: type, s: str) -> bool: + return solution_class().is_valid(s) + + +def assert_is_valid(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode_ideal/valid_parentheses/playground.ipynb b/leetcode_ideal/valid_parentheses/playground.ipynb new file mode 100644 index 0000000..6672f1d --- /dev/null +++ b/leetcode_ideal/valid_parentheses/playground.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_is_valid, run_is_valid\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "s = \"()\"\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_is_valid(Solution, s)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "test", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_is_valid(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/valid_parentheses/solution.py b/leetcode_ideal/valid_parentheses/solution.py similarity index 100% rename from leetcode/valid_parentheses/solution.py rename to leetcode_ideal/valid_parentheses/solution.py diff --git a/leetcode_ideal/valid_parentheses/test_solution.py b/leetcode_ideal/valid_parentheses/test_solution.py new file mode 100644 index 0000000..a12d36b --- /dev/null +++ b/leetcode_ideal/valid_parentheses/test_solution.py @@ -0,0 +1,45 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_is_valid, run_is_valid + + +class TestValidParentheses: + @pytest.mark.parametrize( + "s, expected", + [ + ("()", True), + ("()[]{}", True), + ("(]", False), + ("([])", True), + ("([)]", False), + ("", True), + ("(", False), + (")", False), + ("{[()]}", True), + ("{[(])}", False), + ("(((", False), + (")))", False), + ("()()()", True), + ("({[]})", True), + ("({[}])", False), + ("[[[[[]]]]]", True), + ("[[[[[]]]]", False), + ("{{{{{}}}}}", True), + ("((((((((((", False), + ("))))))))))", False), + ("(){}[]", True), + ("([{}])", True), + ("([{]})", False), + ("(())", True), + ("(()", False), + ("())", False), + ], + ) + @logged_test + def test_is_valid(self, s: str, expected: bool): + from .solution import Solution + + result = run_is_valid(Solution, s) + assert_is_valid(result, expected) diff --git a/leetcode/clone_graph/__init__.py b/leetcode_old/__init__.py similarity index 100% rename from leetcode/clone_graph/__init__.py rename to leetcode_old/__init__.py diff --git a/leetcode/accounts_merge/README.md b/leetcode_old/accounts_merge/README.md similarity index 100% rename from leetcode/accounts_merge/README.md rename to leetcode_old/accounts_merge/README.md diff --git a/leetcode/coin_change/__init__.py b/leetcode_old/accounts_merge/__init__.py similarity index 100% rename from leetcode/coin_change/__init__.py rename to leetcode_old/accounts_merge/__init__.py diff --git a/leetcode/accounts_merge/playground.ipynb b/leetcode_old/accounts_merge/playground.ipynb similarity index 100% rename from leetcode/accounts_merge/playground.ipynb rename to leetcode_old/accounts_merge/playground.ipynb diff --git a/leetcode/accounts_merge/solution.py b/leetcode_old/accounts_merge/solution.py similarity index 100% rename from leetcode/accounts_merge/solution.py rename to leetcode_old/accounts_merge/solution.py diff --git a/leetcode/accounts_merge/tests.py b/leetcode_old/accounts_merge/tests.py similarity index 100% rename from leetcode/accounts_merge/tests.py rename to leetcode_old/accounts_merge/tests.py diff --git a/leetcode/add_binary/README.md b/leetcode_old/add_binary/README.md similarity index 100% rename from leetcode/add_binary/README.md rename to leetcode_old/add_binary/README.md diff --git a/leetcode/combination_sum/__init__.py b/leetcode_old/add_binary/__init__.py similarity index 100% rename from leetcode/combination_sum/__init__.py rename to leetcode_old/add_binary/__init__.py diff --git a/leetcode/add_binary/playground.ipynb b/leetcode_old/add_binary/playground.ipynb similarity index 100% rename from leetcode/add_binary/playground.ipynb rename to leetcode_old/add_binary/playground.ipynb diff --git a/leetcode/add_binary/solution.py b/leetcode_old/add_binary/solution.py similarity index 100% rename from leetcode/add_binary/solution.py rename to leetcode_old/add_binary/solution.py diff --git a/leetcode/add_binary/tests.py b/leetcode_old/add_binary/tests.py similarity index 100% rename from leetcode/add_binary/tests.py rename to leetcode_old/add_binary/tests.py diff --git a/leetcode/balanced_binary_tree/README.md b/leetcode_old/balanced_binary_tree/README.md similarity index 100% rename from leetcode/balanced_binary_tree/README.md rename to leetcode_old/balanced_binary_tree/README.md diff --git a/leetcode/container_with_most_water/__init__.py b/leetcode_old/balanced_binary_tree/__init__.py similarity index 100% rename from leetcode/container_with_most_water/__init__.py rename to leetcode_old/balanced_binary_tree/__init__.py diff --git a/leetcode/balanced_binary_tree/playground.ipynb b/leetcode_old/balanced_binary_tree/playground.ipynb similarity index 100% rename from leetcode/balanced_binary_tree/playground.ipynb rename to leetcode_old/balanced_binary_tree/playground.ipynb diff --git a/leetcode/balanced_binary_tree/solution.py b/leetcode_old/balanced_binary_tree/solution.py similarity index 100% rename from leetcode/balanced_binary_tree/solution.py rename to leetcode_old/balanced_binary_tree/solution.py diff --git a/leetcode/balanced_binary_tree/tests.py b/leetcode_old/balanced_binary_tree/tests.py similarity index 100% rename from leetcode/balanced_binary_tree/tests.py rename to leetcode_old/balanced_binary_tree/tests.py diff --git a/leetcode/basic_calculator/README.md b/leetcode_old/basic_calculator/README.md similarity index 100% rename from leetcode/basic_calculator/README.md rename to leetcode_old/basic_calculator/README.md diff --git a/leetcode/contains_duplicate/__init__.py b/leetcode_old/basic_calculator/__init__.py similarity index 100% rename from leetcode/contains_duplicate/__init__.py rename to leetcode_old/basic_calculator/__init__.py diff --git a/leetcode/basic_calculator/playground.ipynb b/leetcode_old/basic_calculator/playground.ipynb similarity index 100% rename from leetcode/basic_calculator/playground.ipynb rename to leetcode_old/basic_calculator/playground.ipynb diff --git a/leetcode/basic_calculator/solution.py b/leetcode_old/basic_calculator/solution.py similarity index 100% rename from leetcode/basic_calculator/solution.py rename to leetcode_old/basic_calculator/solution.py diff --git a/leetcode/basic_calculator/tests.py b/leetcode_old/basic_calculator/tests.py similarity index 100% rename from leetcode/basic_calculator/tests.py rename to leetcode_old/basic_calculator/tests.py diff --git a/leetcode/best_time_to_buy_and_sell_stock/README.md b/leetcode_old/best_time_to_buy_and_sell_stock/README.md similarity index 100% rename from leetcode/best_time_to_buy_and_sell_stock/README.md rename to leetcode_old/best_time_to_buy_and_sell_stock/README.md diff --git a/leetcode/course_schedule/__init__.py b/leetcode_old/best_time_to_buy_and_sell_stock/__init__.py similarity index 100% rename from leetcode/course_schedule/__init__.py rename to leetcode_old/best_time_to_buy_and_sell_stock/__init__.py diff --git a/leetcode/best_time_to_buy_and_sell_stock/playground.ipynb b/leetcode_old/best_time_to_buy_and_sell_stock/playground.ipynb similarity index 100% rename from leetcode/best_time_to_buy_and_sell_stock/playground.ipynb rename to leetcode_old/best_time_to_buy_and_sell_stock/playground.ipynb diff --git a/leetcode/best_time_to_buy_and_sell_stock/solution.py b/leetcode_old/best_time_to_buy_and_sell_stock/solution.py similarity index 100% rename from leetcode/best_time_to_buy_and_sell_stock/solution.py rename to leetcode_old/best_time_to_buy_and_sell_stock/solution.py diff --git a/leetcode/best_time_to_buy_and_sell_stock/tests.py b/leetcode_old/best_time_to_buy_and_sell_stock/tests.py similarity index 100% rename from leetcode/best_time_to_buy_and_sell_stock/tests.py rename to leetcode_old/best_time_to_buy_and_sell_stock/tests.py diff --git a/leetcode/binary_search/README.md b/leetcode_old/binary_search/README.md similarity index 100% rename from leetcode/binary_search/README.md rename to leetcode_old/binary_search/README.md diff --git a/leetcode/diameter_of_binary_tree/__init__.py b/leetcode_old/binary_search/__init__.py similarity index 100% rename from leetcode/diameter_of_binary_tree/__init__.py rename to leetcode_old/binary_search/__init__.py diff --git a/leetcode/binary_search/playground.ipynb b/leetcode_old/binary_search/playground.ipynb similarity index 100% rename from leetcode/binary_search/playground.ipynb rename to leetcode_old/binary_search/playground.ipynb diff --git a/leetcode/binary_search/solution.py b/leetcode_old/binary_search/solution.py similarity index 100% rename from leetcode/binary_search/solution.py rename to leetcode_old/binary_search/solution.py diff --git a/leetcode/binary_search/tests.py b/leetcode_old/binary_search/tests.py similarity index 100% rename from leetcode/binary_search/tests.py rename to leetcode_old/binary_search/tests.py diff --git a/leetcode/binary_tree_level_order_traversal/README.md b/leetcode_old/binary_tree_level_order_traversal/README.md similarity index 100% rename from leetcode/binary_tree_level_order_traversal/README.md rename to leetcode_old/binary_tree_level_order_traversal/README.md diff --git a/leetcode/evaluate_reverse_polish_notation/__init__.py b/leetcode_old/binary_tree_level_order_traversal/__init__.py similarity index 100% rename from leetcode/evaluate_reverse_polish_notation/__init__.py rename to leetcode_old/binary_tree_level_order_traversal/__init__.py diff --git a/leetcode/binary_tree_level_order_traversal/playground.ipynb b/leetcode_old/binary_tree_level_order_traversal/playground.ipynb similarity index 100% rename from leetcode/binary_tree_level_order_traversal/playground.ipynb rename to leetcode_old/binary_tree_level_order_traversal/playground.ipynb diff --git a/leetcode/binary_tree_level_order_traversal/solution.py b/leetcode_old/binary_tree_level_order_traversal/solution.py similarity index 100% rename from leetcode/binary_tree_level_order_traversal/solution.py rename to leetcode_old/binary_tree_level_order_traversal/solution.py diff --git a/leetcode/binary_tree_level_order_traversal/tests.py b/leetcode_old/binary_tree_level_order_traversal/tests.py similarity index 100% rename from leetcode/binary_tree_level_order_traversal/tests.py rename to leetcode_old/binary_tree_level_order_traversal/tests.py diff --git a/leetcode/binary_tree_right_side_view/README.md b/leetcode_old/binary_tree_right_side_view/README.md similarity index 100% rename from leetcode/binary_tree_right_side_view/README.md rename to leetcode_old/binary_tree_right_side_view/README.md diff --git a/leetcode/find_median_from_data_stream/__init__.py b/leetcode_old/binary_tree_right_side_view/__init__.py similarity index 100% rename from leetcode/find_median_from_data_stream/__init__.py rename to leetcode_old/binary_tree_right_side_view/__init__.py diff --git a/leetcode/binary_tree_right_side_view/playground.ipynb b/leetcode_old/binary_tree_right_side_view/playground.ipynb similarity index 100% rename from leetcode/binary_tree_right_side_view/playground.ipynb rename to leetcode_old/binary_tree_right_side_view/playground.ipynb diff --git a/leetcode/binary_tree_right_side_view/solution.py b/leetcode_old/binary_tree_right_side_view/solution.py similarity index 100% rename from leetcode/binary_tree_right_side_view/solution.py rename to leetcode_old/binary_tree_right_side_view/solution.py diff --git a/leetcode/binary_tree_right_side_view/tests.py b/leetcode_old/binary_tree_right_side_view/tests.py similarity index 100% rename from leetcode/binary_tree_right_side_view/tests.py rename to leetcode_old/binary_tree_right_side_view/tests.py diff --git a/leetcode/climbing_stairs/README.md b/leetcode_old/climbing_stairs/README.md similarity index 100% rename from leetcode/climbing_stairs/README.md rename to leetcode_old/climbing_stairs/README.md diff --git a/leetcode/first_bad_version/__init__.py b/leetcode_old/climbing_stairs/__init__.py similarity index 100% rename from leetcode/first_bad_version/__init__.py rename to leetcode_old/climbing_stairs/__init__.py diff --git a/leetcode/climbing_stairs/playground.ipynb b/leetcode_old/climbing_stairs/playground.ipynb similarity index 100% rename from leetcode/climbing_stairs/playground.ipynb rename to leetcode_old/climbing_stairs/playground.ipynb diff --git a/leetcode/climbing_stairs/solution.py b/leetcode_old/climbing_stairs/solution.py similarity index 100% rename from leetcode/climbing_stairs/solution.py rename to leetcode_old/climbing_stairs/solution.py diff --git a/leetcode/climbing_stairs/tests.py b/leetcode_old/climbing_stairs/tests.py similarity index 100% rename from leetcode/climbing_stairs/tests.py rename to leetcode_old/climbing_stairs/tests.py diff --git a/leetcode_old/clone_graph/README.md b/leetcode_old/clone_graph/README.md new file mode 100644 index 0000000..eeda5d5 --- /dev/null +++ b/leetcode_old/clone_graph/README.md @@ -0,0 +1,72 @@ +# Clone Graph + +**Difficulty:** Medium +**Topics:** Hash Table, Depth-First Search, Breadth-First Search, Graph +**Tags:** grind-75 + +**LeetCode:** [Problem 133](https://leetcode.com/problems/clone-graph/description/) + +## Problem Description + +Given a reference of a node in a **connected** undirected graph. + +Return a **deep copy** (clone) of the graph. + +Each node in the graph contains a value (`int`) and a list (`List[Node]`) of its neighbors. + +``` +class Node { + public int val; + public List neighbors; +} +``` + +**Test case format:** + +For simplicity, each node's value is the same as the node's index (1-indexed). For example, the first node with `val == 1`, the second node with `val == 2`, and so on. The graph is represented in the test case using an adjacency list. + +**An adjacency list** is a collection of unordered **lists** used to represent a finite graph. Each list describes the set of neighbors of a node in the graph. + +The given node will always be the first node with `val = 1`. You must return the **copy of the given node** as a reference to the cloned graph. + +## Examples + +### Example 1: + + + +``` +Input: adjList = [[2,4],[1,3],[2,4],[1,3]] +Output: [[2,4],[1,3],[2,4],[1,3]] +Explanation: There are 4 nodes in the graph. +1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4). +2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3). +3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4). +4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3). +``` + +### Example 2: + + + +``` +Input: adjList = [[]] +Output: [[]] +Explanation: Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors. +``` + +### Example 3: + +``` +Input: adjList = [] +Output: [] +Explanation: This an empty graph, it does not have any nodes. +``` + +## Constraints + +- The number of nodes in the graph is in the range `[0, 100]`. +- `1 <= Node.val <= 100` +- `Node.val` is unique for each node. +- There are no repeated edges and no self-loops in the graph. +- The Graph is connected and all nodes can be visited starting from the given node. diff --git a/leetcode/flood_fill/__init__.py b/leetcode_old/clone_graph/__init__.py similarity index 100% rename from leetcode/flood_fill/__init__.py rename to leetcode_old/clone_graph/__init__.py diff --git a/leetcode/clone_graph/playground.ipynb b/leetcode_old/clone_graph/playground.ipynb similarity index 100% rename from leetcode/clone_graph/playground.ipynb rename to leetcode_old/clone_graph/playground.ipynb diff --git a/leetcode_old/clone_graph/solution.py b/leetcode_old/clone_graph/solution.py new file mode 100644 index 0000000..bd53c0b --- /dev/null +++ b/leetcode_old/clone_graph/solution.py @@ -0,0 +1,73 @@ +from collections import deque + +from leetcode_py import GraphNode + + +class Solution: + # Time: O(V + E) + # Space: O(V) + def clone_graph(self, node: GraphNode | None) -> GraphNode | None: + if node is None: + return None + + def dfs(node: GraphNode, visited: dict[int, GraphNode]): + if node.val in visited: + return visited[node.val] + + clone = GraphNode(node.val) + visited[node.val] = clone + + for neighbor in node.neighbors: + clone.neighbors.append(dfs(neighbor, visited)) + + return clone + + return dfs(node, visited={}) + + +class SolutionDFS: + # DFS Iterative + # Time: O(V + E) + # Space: O(V) + def clone_graph(self, node: GraphNode | None) -> GraphNode | None: + if node is None: + return None + + stack = [node] + visited = {node.val: GraphNode(node.val)} + + while stack: + current = stack.pop() + clone = visited[current.val] + + for neighbor in current.neighbors: + if neighbor.val not in visited: + visited[neighbor.val] = GraphNode(neighbor.val) + stack.append(neighbor) + clone.neighbors.append(visited[neighbor.val]) + + return visited[node.val] + + +class SolutionBFS: + # BFS + # Time: O(V + E) + # Space: O(V) + def clone_graph(self, node: GraphNode | None) -> GraphNode | None: + if node is None: + return None + + queue = deque([node]) + visited = {node.val: GraphNode(node.val)} + + while queue: + current = queue.popleft() + clone = visited[current.val] + + for neighbor in current.neighbors: + if neighbor.val not in visited: + visited[neighbor.val] = GraphNode(neighbor.val) + queue.append(neighbor) + clone.neighbors.append(visited[neighbor.val]) + + return visited[node.val] diff --git a/leetcode/clone_graph/tests.py b/leetcode_old/clone_graph/tests.py similarity index 100% rename from leetcode/clone_graph/tests.py rename to leetcode_old/clone_graph/tests.py diff --git a/leetcode/coin_change/README.md b/leetcode_old/coin_change/README.md similarity index 100% rename from leetcode/coin_change/README.md rename to leetcode_old/coin_change/README.md diff --git a/leetcode/implement_queue_using_stacks/__init__.py b/leetcode_old/coin_change/__init__.py similarity index 100% rename from leetcode/implement_queue_using_stacks/__init__.py rename to leetcode_old/coin_change/__init__.py diff --git a/leetcode/coin_change/playground.ipynb b/leetcode_old/coin_change/playground.ipynb similarity index 100% rename from leetcode/coin_change/playground.ipynb rename to leetcode_old/coin_change/playground.ipynb diff --git a/leetcode/coin_change/solution.py b/leetcode_old/coin_change/solution.py similarity index 100% rename from leetcode/coin_change/solution.py rename to leetcode_old/coin_change/solution.py diff --git a/leetcode/coin_change/tests.py b/leetcode_old/coin_change/tests.py similarity index 100% rename from leetcode/coin_change/tests.py rename to leetcode_old/coin_change/tests.py diff --git a/leetcode/combination_sum/README.md b/leetcode_old/combination_sum/README.md similarity index 100% rename from leetcode/combination_sum/README.md rename to leetcode_old/combination_sum/README.md diff --git a/leetcode/implement_trie_prefix_tree/__init__.py b/leetcode_old/combination_sum/__init__.py similarity index 100% rename from leetcode/implement_trie_prefix_tree/__init__.py rename to leetcode_old/combination_sum/__init__.py diff --git a/leetcode_old/combination_sum/playground.ipynb b/leetcode_old/combination_sum/playground.ipynb new file mode 100644 index 0000000..f11281f --- /dev/null +++ b/leetcode_old/combination_sum/playground.ipynb @@ -0,0 +1,74 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "candidates = [2, 3, 6, 7]\n", + "target = 7\n", + "expected = [[2, 2, 3], [7]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "execute", + "metadata": {}, + "outputs": [], + "source": [ + "result = Solution().combination_sum(candidates, target)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "test", + "metadata": {}, + "outputs": [], + "source": [ + "# Sort for comparison\n", + "result_sorted = [sorted(combo) for combo in result]\n", + "expected_sorted = [sorted(combo) for combo in expected]\n", + "result_sorted.sort()\n", + "expected_sorted.sort()\n", + "assert result_sorted == expected_sorted" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/combination_sum/solution.py b/leetcode_old/combination_sum/solution.py similarity index 100% rename from leetcode/combination_sum/solution.py rename to leetcode_old/combination_sum/solution.py diff --git a/leetcode/combination_sum/tests.py b/leetcode_old/combination_sum/tests.py similarity index 100% rename from leetcode/combination_sum/tests.py rename to leetcode_old/combination_sum/tests.py diff --git a/leetcode/container_with_most_water/README.md b/leetcode_old/container_with_most_water/README.md similarity index 100% rename from leetcode/container_with_most_water/README.md rename to leetcode_old/container_with_most_water/README.md diff --git a/leetcode/insert_interval/__init__.py b/leetcode_old/container_with_most_water/__init__.py similarity index 100% rename from leetcode/insert_interval/__init__.py rename to leetcode_old/container_with_most_water/__init__.py diff --git a/leetcode/container_with_most_water/playground.ipynb b/leetcode_old/container_with_most_water/playground.ipynb similarity index 100% rename from leetcode/container_with_most_water/playground.ipynb rename to leetcode_old/container_with_most_water/playground.ipynb diff --git a/leetcode/container_with_most_water/solution.py b/leetcode_old/container_with_most_water/solution.py similarity index 100% rename from leetcode/container_with_most_water/solution.py rename to leetcode_old/container_with_most_water/solution.py diff --git a/leetcode/container_with_most_water/tests.py b/leetcode_old/container_with_most_water/tests.py similarity index 100% rename from leetcode/container_with_most_water/tests.py rename to leetcode_old/container_with_most_water/tests.py diff --git a/leetcode_old/contains_duplicate/README.md b/leetcode_old/contains_duplicate/README.md new file mode 100644 index 0000000..c09c400 --- /dev/null +++ b/leetcode_old/contains_duplicate/README.md @@ -0,0 +1,43 @@ +# Contains Duplicate + +**Difficulty:** Easy +**Topics:** Array, Hash Table, Sorting +**Tags:** grind-75 + +**LeetCode:** [Problem 217](https://leetcode.com/problems/contains-duplicate/description/) + +## Problem Description + +Given an integer array `nums`, return `true` if any value appears **at least twice** in the array, and return `false` if every element is distinct. + +## Examples + +### Example 1: + +``` +Input: nums = [1,2,3,1] +Output: true +``` + +**Explanation:** The element 1 occurs at the indices 0 and 3. + +### Example 2: + +``` +Input: nums = [1,2,3,4] +Output: false +``` + +**Explanation:** All elements are distinct. + +### Example 3: + +``` +Input: nums = [1,1,1,3,3,4,3,2,4,2] +Output: true +``` + +## Constraints + +- 1 <= nums.length <= 10^5 +- -10^9 <= nums[i] <= 10^9 diff --git a/leetcode/invert_binary_tree/__init__.py b/leetcode_old/contains_duplicate/__init__.py similarity index 100% rename from leetcode/invert_binary_tree/__init__.py rename to leetcode_old/contains_duplicate/__init__.py diff --git a/leetcode/course_schedule/playground.ipynb b/leetcode_old/contains_duplicate/playground.ipynb similarity index 75% rename from leetcode/course_schedule/playground.ipynb rename to leetcode_old/contains_duplicate/playground.ipynb index a3fbcc4..1cd54b5 100644 --- a/leetcode/course_schedule/playground.ipynb +++ b/leetcode_old/contains_duplicate/playground.ipynb @@ -6,7 +6,9 @@ "id": "imports", "metadata": {}, "outputs": [], - "source": ["from solution import Solution"] + "source": [ + "from solution import Solution" + ] }, { "cell_type": "code", @@ -14,7 +16,11 @@ "id": "setup", "metadata": {}, "outputs": [], - "source": ["# Example test case\nnum_courses = 2\nprerequisites = [[1, 0]]\nexpected = True"] + "source": [ + "# Example test case\n", + "nums = [1, 2, 3, 1]\n", + "expected = True" + ] }, { "cell_type": "code", @@ -22,7 +28,10 @@ "id": "execute", "metadata": {}, "outputs": [], - "source": ["result = Solution().can_finish(num_courses, prerequisites)\nresult"] + "source": [ + "result = Solution().contains_duplicate(nums)\n", + "result" + ] }, { "cell_type": "code", @@ -30,7 +39,9 @@ "id": "test", "metadata": {}, "outputs": [], - "source": ["assert result == expected"] + "source": [ + "assert result == expected" + ] } ], "metadata": { diff --git a/leetcode_old/contains_duplicate/solution.py b/leetcode_old/contains_duplicate/solution.py new file mode 100644 index 0000000..b9bd400 --- /dev/null +++ b/leetcode_old/contains_duplicate/solution.py @@ -0,0 +1,10 @@ +class Solution: + # Time: O(n) + # Space: O(n) + def contains_duplicate(self, nums: list[int]) -> bool: + seen = set() + for num in nums: + if num in seen: + return True + seen.add(num) + return False diff --git a/leetcode/contains_duplicate/tests.py b/leetcode_old/contains_duplicate/tests.py similarity index 100% rename from leetcode/contains_duplicate/tests.py rename to leetcode_old/contains_duplicate/tests.py diff --git a/leetcode/course_schedule/README.md b/leetcode_old/course_schedule/README.md similarity index 100% rename from leetcode/course_schedule/README.md rename to leetcode_old/course_schedule/README.md diff --git a/leetcode/k_closest_points_to_origin/__init__.py b/leetcode_old/course_schedule/__init__.py similarity index 100% rename from leetcode/k_closest_points_to_origin/__init__.py rename to leetcode_old/course_schedule/__init__.py diff --git a/leetcode_old/course_schedule/playground.ipynb b/leetcode_old/course_schedule/playground.ipynb new file mode 100644 index 0000000..47eca12 --- /dev/null +++ b/leetcode_old/course_schedule/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "num_courses = 2\n", + "prerequisites = [[1, 0]]\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "execute", + "metadata": {}, + "outputs": [], + "source": [ + "result = Solution().can_finish(num_courses, prerequisites)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "test", + "metadata": {}, + "outputs": [], + "source": [ + "assert result == expected" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/course_schedule/solution.py b/leetcode_old/course_schedule/solution.py similarity index 100% rename from leetcode/course_schedule/solution.py rename to leetcode_old/course_schedule/solution.py diff --git a/leetcode/course_schedule/tests.py b/leetcode_old/course_schedule/tests.py similarity index 100% rename from leetcode/course_schedule/tests.py rename to leetcode_old/course_schedule/tests.py diff --git a/leetcode/diameter_of_binary_tree/README.md b/leetcode_old/diameter_of_binary_tree/README.md similarity index 100% rename from leetcode/diameter_of_binary_tree/README.md rename to leetcode_old/diameter_of_binary_tree/README.md diff --git a/leetcode/kth_smallest_element_in_a_bst/__init__.py b/leetcode_old/diameter_of_binary_tree/__init__.py similarity index 100% rename from leetcode/kth_smallest_element_in_a_bst/__init__.py rename to leetcode_old/diameter_of_binary_tree/__init__.py diff --git a/leetcode/diameter_of_binary_tree/playground.ipynb b/leetcode_old/diameter_of_binary_tree/playground.ipynb similarity index 100% rename from leetcode/diameter_of_binary_tree/playground.ipynb rename to leetcode_old/diameter_of_binary_tree/playground.ipynb diff --git a/leetcode/diameter_of_binary_tree/solution.py b/leetcode_old/diameter_of_binary_tree/solution.py similarity index 100% rename from leetcode/diameter_of_binary_tree/solution.py rename to leetcode_old/diameter_of_binary_tree/solution.py diff --git a/leetcode/diameter_of_binary_tree/tests.py b/leetcode_old/diameter_of_binary_tree/tests.py similarity index 100% rename from leetcode/diameter_of_binary_tree/tests.py rename to leetcode_old/diameter_of_binary_tree/tests.py diff --git a/leetcode/evaluate_reverse_polish_notation/README.md b/leetcode_old/evaluate_reverse_polish_notation/README.md similarity index 100% rename from leetcode/evaluate_reverse_polish_notation/README.md rename to leetcode_old/evaluate_reverse_polish_notation/README.md diff --git a/leetcode/largest_rectangle_in_histogram/__init__.py b/leetcode_old/evaluate_reverse_polish_notation/__init__.py similarity index 100% rename from leetcode/largest_rectangle_in_histogram/__init__.py rename to leetcode_old/evaluate_reverse_polish_notation/__init__.py diff --git a/leetcode/evaluate_reverse_polish_notation/playground.ipynb b/leetcode_old/evaluate_reverse_polish_notation/playground.ipynb similarity index 100% rename from leetcode/evaluate_reverse_polish_notation/playground.ipynb rename to leetcode_old/evaluate_reverse_polish_notation/playground.ipynb diff --git a/leetcode/evaluate_reverse_polish_notation/solution.py b/leetcode_old/evaluate_reverse_polish_notation/solution.py similarity index 100% rename from leetcode/evaluate_reverse_polish_notation/solution.py rename to leetcode_old/evaluate_reverse_polish_notation/solution.py diff --git a/leetcode/evaluate_reverse_polish_notation/tests.py b/leetcode_old/evaluate_reverse_polish_notation/tests.py similarity index 100% rename from leetcode/evaluate_reverse_polish_notation/tests.py rename to leetcode_old/evaluate_reverse_polish_notation/tests.py diff --git a/leetcode/find_median_from_data_stream/README.md b/leetcode_old/find_median_from_data_stream/README.md similarity index 100% rename from leetcode/find_median_from_data_stream/README.md rename to leetcode_old/find_median_from_data_stream/README.md diff --git a/leetcode/linked_list_cycle/__init__.py b/leetcode_old/find_median_from_data_stream/__init__.py similarity index 100% rename from leetcode/linked_list_cycle/__init__.py rename to leetcode_old/find_median_from_data_stream/__init__.py diff --git a/leetcode/find_median_from_data_stream/playground.ipynb b/leetcode_old/find_median_from_data_stream/playground.ipynb similarity index 100% rename from leetcode/find_median_from_data_stream/playground.ipynb rename to leetcode_old/find_median_from_data_stream/playground.ipynb diff --git a/leetcode/find_median_from_data_stream/solution.py b/leetcode_old/find_median_from_data_stream/solution.py similarity index 100% rename from leetcode/find_median_from_data_stream/solution.py rename to leetcode_old/find_median_from_data_stream/solution.py diff --git a/leetcode/find_median_from_data_stream/tests.py b/leetcode_old/find_median_from_data_stream/tests.py similarity index 100% rename from leetcode/find_median_from_data_stream/tests.py rename to leetcode_old/find_median_from_data_stream/tests.py diff --git a/leetcode_old/first_bad_version/README.md b/leetcode_old/first_bad_version/README.md new file mode 100644 index 0000000..a710eb0 --- /dev/null +++ b/leetcode_old/first_bad_version/README.md @@ -0,0 +1,47 @@ +# First Bad Version + +**Difficulty:** Easy +**Topics:** Binary Search, Interactive +**Tags:** grind-75 + +**LeetCode:** [Problem 278](https://leetcode.com/problems/first-bad-version/description/) + +## Problem Description + +You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad. + +Suppose you have `n` versions `[1, 2, ..., n]` and you want to find out the first bad one, which causes all the following ones to be bad. + +You are given an API `bool isBadVersion(version)` which returns whether `version` is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API. + +## Examples + +### Example 1: + +``` +Input: n = 5, bad = 4 +Output: 4 +``` + +**Explanation:** + +``` +call isBadVersion(3) -> false +call isBadVersion(5) -> true +call isBadVersion(4) -> true +``` + +Then 4 is the first bad version. + +### Example 2: + +``` +Input: n = 1, bad = 1 +Output: 1 +``` + +## Constraints + +- 1 <= bad <= n <= 2^31 - 1 + +**Note:** The `isBadVersion` API is already defined for you. diff --git a/leetcode/longest_palindrome/__init__.py b/leetcode_old/first_bad_version/__init__.py similarity index 100% rename from leetcode/longest_palindrome/__init__.py rename to leetcode_old/first_bad_version/__init__.py diff --git a/leetcode/first_bad_version/playground.ipynb b/leetcode_old/first_bad_version/playground.ipynb similarity index 100% rename from leetcode/first_bad_version/playground.ipynb rename to leetcode_old/first_bad_version/playground.ipynb diff --git a/leetcode_old/first_bad_version/solution.py b/leetcode_old/first_bad_version/solution.py new file mode 100644 index 0000000..7fa3f49 --- /dev/null +++ b/leetcode_old/first_bad_version/solution.py @@ -0,0 +1,45 @@ +class Solution: + # TODO: template constraint + def __init__(self, first_bad): + self.is_bad_version = lambda version: version >= first_bad + + # Time: O(log n) + # Space: O(1) + def first_bad_version(self, n: int) -> int: + left = 1 + right = n + + while left < right: + mid = (left + right) // 2 + if self.is_bad_version(mid): + right = mid + else: + left = mid + 1 + + return right + + +# BISECT PATTERNS - General Binary Search +# Given: arr = [10,20,30,30,30,40,50], target = 30 +# 0 1 2 3 4 5 6 +# +# bisect_left: Find FIRST occurrence (leftmost insertion point) +# while left < right: +# if arr[mid] >= target: # >= keeps moving left +# right = mid +# Returns: 2 (index of first 30, value=30) +# [10,20,30,30,30,40,50] +# 0 1 2 3 4 5 6 +# ↑ index 2 +# +# bisect_right: Find position AFTER last occurrence +# while left < right: +# if arr[mid] > target: # > allows equal values +# right = mid +# Returns: 5 (index after last 30, value=40) +# [10,20,30,30,30,40,50] +# 0 1 2 3 4 5 6 +# ↑ index 5 +# +# Key difference: >= vs > in the condition +# This problem uses bisect_left pattern to find first bad version diff --git a/leetcode/first_bad_version/tests.py b/leetcode_old/first_bad_version/tests.py similarity index 100% rename from leetcode/first_bad_version/tests.py rename to leetcode_old/first_bad_version/tests.py diff --git a/leetcode/flood_fill/README.md b/leetcode_old/flood_fill/README.md similarity index 100% rename from leetcode/flood_fill/README.md rename to leetcode_old/flood_fill/README.md diff --git a/leetcode/longest_palindromic_substring/__init__.py b/leetcode_old/flood_fill/__init__.py similarity index 100% rename from leetcode/longest_palindromic_substring/__init__.py rename to leetcode_old/flood_fill/__init__.py diff --git a/leetcode_old/flood_fill/playground.ipynb b/leetcode_old/flood_fill/playground.ipynb new file mode 100644 index 0000000..26e6d32 --- /dev/null +++ b/leetcode_old/flood_fill/playground.ipynb @@ -0,0 +1,71 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "image = [[1, 1, 1], [1, 1, 0], [1, 0, 1]]\n", + "sr = 1\n", + "sc = 1\n", + "color = 2\n", + "expected = [[2, 2, 2], [2, 2, 0], [2, 0, 1]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "execute", + "metadata": {}, + "outputs": [], + "source": [ + "result = Solution().flood_fill(image, sr, sc, color)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "test", + "metadata": {}, + "outputs": [], + "source": [ + "assert result == expected" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/flood_fill/solution.py b/leetcode_old/flood_fill/solution.py similarity index 100% rename from leetcode/flood_fill/solution.py rename to leetcode_old/flood_fill/solution.py diff --git a/leetcode/flood_fill/tests.py b/leetcode_old/flood_fill/tests.py similarity index 100% rename from leetcode/flood_fill/tests.py rename to leetcode_old/flood_fill/tests.py diff --git a/leetcode/implement_queue_using_stacks/README.md b/leetcode_old/implement_queue_using_stacks/README.md similarity index 100% rename from leetcode/implement_queue_using_stacks/README.md rename to leetcode_old/implement_queue_using_stacks/README.md diff --git a/leetcode/longest_substring_without_repeating_characters/__init__.py b/leetcode_old/implement_queue_using_stacks/__init__.py similarity index 100% rename from leetcode/longest_substring_without_repeating_characters/__init__.py rename to leetcode_old/implement_queue_using_stacks/__init__.py diff --git a/leetcode/implement_queue_using_stacks/playground.ipynb b/leetcode_old/implement_queue_using_stacks/playground.ipynb similarity index 100% rename from leetcode/implement_queue_using_stacks/playground.ipynb rename to leetcode_old/implement_queue_using_stacks/playground.ipynb diff --git a/leetcode/implement_queue_using_stacks/solution.py b/leetcode_old/implement_queue_using_stacks/solution.py similarity index 100% rename from leetcode/implement_queue_using_stacks/solution.py rename to leetcode_old/implement_queue_using_stacks/solution.py diff --git a/leetcode/implement_queue_using_stacks/tests.py b/leetcode_old/implement_queue_using_stacks/tests.py similarity index 100% rename from leetcode/implement_queue_using_stacks/tests.py rename to leetcode_old/implement_queue_using_stacks/tests.py diff --git a/leetcode_old/implement_trie_prefix_tree/README.md b/leetcode_old/implement_trie_prefix_tree/README.md new file mode 100644 index 0000000..7e0cd3c --- /dev/null +++ b/leetcode_old/implement_trie_prefix_tree/README.md @@ -0,0 +1,48 @@ +# Implement Trie (Prefix Tree) + +**Difficulty:** Medium +**Topics:** Hash Table, String, Design, Trie +**Tags:** grind-75 + +**LeetCode:** [Problem 208](https://leetcode.com/problems/implement-trie-prefix-tree/description/) + +## Problem Description + +A **trie** (pronounced as "try") or **prefix tree** is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker. + +Implement the Trie class: + +- `Trie()` Initializes the trie object. +- `void insert(String word)` Inserts the string `word` into the trie. +- `boolean search(String word)` Returns `true` if the string `word` is in the trie (i.e., was inserted before), and `false` otherwise. +- `boolean startsWith(String prefix)` Returns `true` if there is a previously inserted string `word` that has the prefix `prefix`, and `false` otherwise. + +## Examples + +### Example 1: + +``` +Input +["Trie", "insert", "search", "search", "startsWith", "insert", "search"] +[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]] +Output +[null, null, true, false, true, null, true] +``` + +**Explanation:** + +```python +trie = Trie() +trie.insert("apple") +trie.search("apple") # return True +trie.search("app") # return False +trie.starts_with("app") # return True +trie.insert("app") +trie.search("app") # return True +``` + +## Constraints + +- `1 <= word.length, prefix.length <= 2000` +- `word` and `prefix` consist only of lowercase English letters. +- At most `3 * 10^4` calls **in total** will be made to `insert`, `search`, and `starts_with`. diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/__init__.py b/leetcode_old/implement_trie_prefix_tree/__init__.py similarity index 100% rename from leetcode/lowest_common_ancestor_of_a_binary_search_tree/__init__.py rename to leetcode_old/implement_trie_prefix_tree/__init__.py diff --git a/leetcode/implement_trie_prefix_tree/playground.ipynb b/leetcode_old/implement_trie_prefix_tree/playground.ipynb similarity index 100% rename from leetcode/implement_trie_prefix_tree/playground.ipynb rename to leetcode_old/implement_trie_prefix_tree/playground.ipynb diff --git a/leetcode_old/implement_trie_prefix_tree/solution.py b/leetcode_old/implement_trie_prefix_tree/solution.py new file mode 100644 index 0000000..35ea50b --- /dev/null +++ b/leetcode_old/implement_trie_prefix_tree/solution.py @@ -0,0 +1,40 @@ +from leetcode_py.data_structures import DictTree, RecursiveDict + + +class Trie(DictTree[str]): + END_OF_WORD = "#" + + # Time: O(1) + # Space: O(1) + def __init__(self) -> None: + self.root: RecursiveDict[str] = {} + + # Time: O(m) where m is word length + # Space: O(m) + def insert(self, word: str) -> None: + node = self.root + for char in word: + if char not in node: + node[char] = {} + node = node[char] + node[self.END_OF_WORD] = True + + # Time: O(m) where m is word length + # Space: O(1) + def search(self, word: str) -> bool: + node = self.root + for char in word: + if char not in node: + return False + node = node[char] + return self.END_OF_WORD in node + + # Time: O(m) where m is prefix length + # Space: O(1) + def starts_with(self, prefix: str) -> bool: + node = self.root + for char in prefix: + if char not in node: + return False + node = node[char] + return True diff --git a/leetcode/implement_trie_prefix_tree/tests.py b/leetcode_old/implement_trie_prefix_tree/tests.py similarity index 100% rename from leetcode/implement_trie_prefix_tree/tests.py rename to leetcode_old/implement_trie_prefix_tree/tests.py diff --git a/leetcode/insert_interval/README.md b/leetcode_old/insert_interval/README.md similarity index 100% rename from leetcode/insert_interval/README.md rename to leetcode_old/insert_interval/README.md diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/__init__.py b/leetcode_old/insert_interval/__init__.py similarity index 100% rename from leetcode/lowest_common_ancestor_of_a_binary_tree/__init__.py rename to leetcode_old/insert_interval/__init__.py diff --git a/leetcode/insert_interval/playground.ipynb b/leetcode_old/insert_interval/playground.ipynb similarity index 100% rename from leetcode/insert_interval/playground.ipynb rename to leetcode_old/insert_interval/playground.ipynb diff --git a/leetcode/insert_interval/solution.py b/leetcode_old/insert_interval/solution.py similarity index 100% rename from leetcode/insert_interval/solution.py rename to leetcode_old/insert_interval/solution.py diff --git a/leetcode/insert_interval/tests.py b/leetcode_old/insert_interval/tests.py similarity index 100% rename from leetcode/insert_interval/tests.py rename to leetcode_old/insert_interval/tests.py diff --git a/leetcode_old/invert_binary_tree/README.md b/leetcode_old/invert_binary_tree/README.md new file mode 100644 index 0000000..a2b7ded --- /dev/null +++ b/leetcode_old/invert_binary_tree/README.md @@ -0,0 +1,39 @@ +# Invert Binary Tree + +**Difficulty:** Easy +**Topics:** Tree, Depth-First Search, Breadth-First Search, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 226](https://leetcode.com/problems/invert-binary-tree/description/) + +## Problem Description + +Given the `root` of a binary tree, invert the tree, and return its root. + +## Examples + +### Example 1: + +``` +Input: root = [4,2,7,1,3,6,9] +Output: [4,7,2,9,6,3,1] +``` + +### Example 2: + +``` +Input: root = [2,1,3] +Output: [2,3,1] +``` + +### Example 3: + +``` +Input: root = [] +Output: [] +``` + +## Constraints + +- The number of nodes in the tree is in the range [0, 100] +- -100 <= Node.val <= 100 diff --git a/leetcode/lru_cache/__init__.py b/leetcode_old/invert_binary_tree/__init__.py similarity index 100% rename from leetcode/lru_cache/__init__.py rename to leetcode_old/invert_binary_tree/__init__.py diff --git a/leetcode/invert_binary_tree/playground.ipynb b/leetcode_old/invert_binary_tree/playground.ipynb similarity index 100% rename from leetcode/invert_binary_tree/playground.ipynb rename to leetcode_old/invert_binary_tree/playground.ipynb diff --git a/leetcode_old/invert_binary_tree/solution.py b/leetcode_old/invert_binary_tree/solution.py new file mode 100644 index 0000000..1e11b18 --- /dev/null +++ b/leetcode_old/invert_binary_tree/solution.py @@ -0,0 +1,59 @@ +from collections import deque + +from leetcode_py import TreeNode + +# Note: "Fringe" is the general CS term for the data structure holding nodes to be explored. +# Stack (LIFO) → DFS, Queue (FIFO) → BFS, Priority Queue → A*/Best-first search + + +class Solution: + # DFS recursive + # Time: O(n) + # Space: O(h) where h is height of tree + def invert_tree(self, root: TreeNode[int] | None) -> TreeNode[int] | None: + if not root: + return None + + root.left, root.right = self.invert_tree(root.right), self.invert_tree(root.left) + return root + + +class SolutionDFS: + # DFS iterative + # Time: O(n) + # Space: O(h) where h is height of tree + def invert_tree(self, root: TreeNode[int] | None) -> TreeNode[int] | None: + if not root: + return None + + stack: list[TreeNode[int] | None] = [root] + while stack: + node = stack.pop() + if node is None: + continue + node.left, node.right = node.right, node.left + + stack.append(node.left) + stack.append(node.right) + + return root + + +class SolutionBFS: + # Time: O(n) + # Space: O(w) where w is maximum width of tree + def invert_tree(self, root: TreeNode[int] | None) -> TreeNode[int] | None: + if not root: + return None + + queue: deque[TreeNode[int] | None] = deque([root]) + while queue: + node = queue.popleft() + if node is None: + continue + node.left, node.right = node.right, node.left + + queue.append(node.left) + queue.append(node.right) + + return root diff --git a/leetcode/invert_binary_tree/tests.py b/leetcode_old/invert_binary_tree/tests.py similarity index 100% rename from leetcode/invert_binary_tree/tests.py rename to leetcode_old/invert_binary_tree/tests.py diff --git a/leetcode/k_closest_points_to_origin/README.md b/leetcode_old/k_closest_points_to_origin/README.md similarity index 100% rename from leetcode/k_closest_points_to_origin/README.md rename to leetcode_old/k_closest_points_to_origin/README.md diff --git a/leetcode/majority_element/__init__.py b/leetcode_old/k_closest_points_to_origin/__init__.py similarity index 100% rename from leetcode/majority_element/__init__.py rename to leetcode_old/k_closest_points_to_origin/__init__.py diff --git a/leetcode/k_closest_points_to_origin/playground.ipynb b/leetcode_old/k_closest_points_to_origin/playground.ipynb similarity index 100% rename from leetcode/k_closest_points_to_origin/playground.ipynb rename to leetcode_old/k_closest_points_to_origin/playground.ipynb diff --git a/leetcode/k_closest_points_to_origin/solution.py b/leetcode_old/k_closest_points_to_origin/solution.py similarity index 100% rename from leetcode/k_closest_points_to_origin/solution.py rename to leetcode_old/k_closest_points_to_origin/solution.py diff --git a/leetcode/k_closest_points_to_origin/tests.py b/leetcode_old/k_closest_points_to_origin/tests.py similarity index 100% rename from leetcode/k_closest_points_to_origin/tests.py rename to leetcode_old/k_closest_points_to_origin/tests.py diff --git a/leetcode/kth_smallest_element_in_a_bst/README.md b/leetcode_old/kth_smallest_element_in_a_bst/README.md similarity index 100% rename from leetcode/kth_smallest_element_in_a_bst/README.md rename to leetcode_old/kth_smallest_element_in_a_bst/README.md diff --git a/leetcode/maximum_depth_of_binary_tree/__init__.py b/leetcode_old/kth_smallest_element_in_a_bst/__init__.py similarity index 100% rename from leetcode/maximum_depth_of_binary_tree/__init__.py rename to leetcode_old/kth_smallest_element_in_a_bst/__init__.py diff --git a/leetcode/kth_smallest_element_in_a_bst/playground.ipynb b/leetcode_old/kth_smallest_element_in_a_bst/playground.ipynb similarity index 100% rename from leetcode/kth_smallest_element_in_a_bst/playground.ipynb rename to leetcode_old/kth_smallest_element_in_a_bst/playground.ipynb diff --git a/leetcode/kth_smallest_element_in_a_bst/solution.py b/leetcode_old/kth_smallest_element_in_a_bst/solution.py similarity index 100% rename from leetcode/kth_smallest_element_in_a_bst/solution.py rename to leetcode_old/kth_smallest_element_in_a_bst/solution.py diff --git a/leetcode/kth_smallest_element_in_a_bst/tests.py b/leetcode_old/kth_smallest_element_in_a_bst/tests.py similarity index 100% rename from leetcode/kth_smallest_element_in_a_bst/tests.py rename to leetcode_old/kth_smallest_element_in_a_bst/tests.py diff --git a/leetcode/largest_rectangle_in_histogram/README.md b/leetcode_old/largest_rectangle_in_histogram/README.md similarity index 100% rename from leetcode/largest_rectangle_in_histogram/README.md rename to leetcode_old/largest_rectangle_in_histogram/README.md diff --git a/leetcode/maximum_profit_in_job_scheduling/__init__.py b/leetcode_old/largest_rectangle_in_histogram/__init__.py similarity index 100% rename from leetcode/maximum_profit_in_job_scheduling/__init__.py rename to leetcode_old/largest_rectangle_in_histogram/__init__.py diff --git a/leetcode/largest_rectangle_in_histogram/playground.ipynb b/leetcode_old/largest_rectangle_in_histogram/playground.ipynb similarity index 100% rename from leetcode/largest_rectangle_in_histogram/playground.ipynb rename to leetcode_old/largest_rectangle_in_histogram/playground.ipynb diff --git a/leetcode/largest_rectangle_in_histogram/solution.py b/leetcode_old/largest_rectangle_in_histogram/solution.py similarity index 100% rename from leetcode/largest_rectangle_in_histogram/solution.py rename to leetcode_old/largest_rectangle_in_histogram/solution.py diff --git a/leetcode/largest_rectangle_in_histogram/tests.py b/leetcode_old/largest_rectangle_in_histogram/tests.py similarity index 100% rename from leetcode/largest_rectangle_in_histogram/tests.py rename to leetcode_old/largest_rectangle_in_histogram/tests.py diff --git a/leetcode_old/linked_list_cycle/README.md b/leetcode_old/linked_list_cycle/README.md new file mode 100644 index 0000000..4ec75c4 --- /dev/null +++ b/leetcode_old/linked_list_cycle/README.md @@ -0,0 +1,58 @@ +# Linked List Cycle + +**Difficulty:** Easy +**Topics:** Hash Table, Linked List, Two Pointers +**Tags:** grind-75 + +**LeetCode:** [Problem 141](https://leetcode.com/problems/linked-list-cycle/description/) + +## Problem Description + +Given `head`, the head of a linked list, determine if the linked list has a cycle in it. + +There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the `next` pointer. Internally, `pos` is used to denote the index of the node that tail's `next` pointer is connected to. **Note that `pos` is not passed as a parameter**. + +Return `true` _if there is a cycle in the linked list_. Otherwise, return `false`. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist.png) + +``` +Input: head = [3,2,0,-4], pos = 1 +Output: true +``` + +**Explanation:** There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed). + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test2.png) + +``` +Input: head = [1,2], pos = 0 +Output: true +``` + +**Explanation:** There is a cycle in the linked list, where the tail connects to the 0th node. + +### Example 3: + +![Example 3](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test3.png) + +``` +Input: head = [1], pos = -1 +Output: false +``` + +**Explanation:** There is no cycle in the linked list. + +## Constraints + +- The number of the nodes in the list is in the range `[0, 10^4]`. +- `-10^5 <= Node.val <= 10^5` +- `pos` is `-1` or a **valid index** in the linked-list. + +**Follow up:** Can you solve it using `O(1)` (i.e. constant) memory? diff --git a/leetcode/maximum_subarray/__init__.py b/leetcode_old/linked_list_cycle/__init__.py similarity index 100% rename from leetcode/maximum_subarray/__init__.py rename to leetcode_old/linked_list_cycle/__init__.py diff --git a/leetcode/linked_list_cycle/playground.ipynb b/leetcode_old/linked_list_cycle/playground.ipynb similarity index 100% rename from leetcode/linked_list_cycle/playground.ipynb rename to leetcode_old/linked_list_cycle/playground.ipynb diff --git a/leetcode_old/linked_list_cycle/solution.py b/leetcode_old/linked_list_cycle/solution.py new file mode 100644 index 0000000..6c3f838 --- /dev/null +++ b/leetcode_old/linked_list_cycle/solution.py @@ -0,0 +1,18 @@ +from leetcode_py import ListNode + + +class Solution: + # Time: O(n) + # Space: O(1) + def has_cycle(self, head: ListNode[int] | None) -> bool: + fast = head + slow = head + + while fast and fast.next: + assert slow is not None + fast = fast.next.next + slow = slow.next + if fast is slow: + return True + + return False diff --git a/leetcode/linked_list_cycle/tests.py b/leetcode_old/linked_list_cycle/tests.py similarity index 100% rename from leetcode/linked_list_cycle/tests.py rename to leetcode_old/linked_list_cycle/tests.py diff --git a/leetcode/longest_palindrome/README.md b/leetcode_old/longest_palindrome/README.md similarity index 100% rename from leetcode/longest_palindrome/README.md rename to leetcode_old/longest_palindrome/README.md diff --git a/leetcode/merge_intervals/__init__.py b/leetcode_old/longest_palindrome/__init__.py similarity index 100% rename from leetcode/merge_intervals/__init__.py rename to leetcode_old/longest_palindrome/__init__.py diff --git a/leetcode/longest_palindrome/playground.ipynb b/leetcode_old/longest_palindrome/playground.ipynb similarity index 100% rename from leetcode/longest_palindrome/playground.ipynb rename to leetcode_old/longest_palindrome/playground.ipynb diff --git a/leetcode/longest_palindrome/solution.py b/leetcode_old/longest_palindrome/solution.py similarity index 100% rename from leetcode/longest_palindrome/solution.py rename to leetcode_old/longest_palindrome/solution.py diff --git a/leetcode/longest_palindrome/tests.py b/leetcode_old/longest_palindrome/tests.py similarity index 100% rename from leetcode/longest_palindrome/tests.py rename to leetcode_old/longest_palindrome/tests.py diff --git a/leetcode/longest_palindromic_substring/README.md b/leetcode_old/longest_palindromic_substring/README.md similarity index 100% rename from leetcode/longest_palindromic_substring/README.md rename to leetcode_old/longest_palindromic_substring/README.md diff --git a/leetcode/merge_k_sorted_lists/__init__.py b/leetcode_old/longest_palindromic_substring/__init__.py similarity index 100% rename from leetcode/merge_k_sorted_lists/__init__.py rename to leetcode_old/longest_palindromic_substring/__init__.py diff --git a/leetcode/longest_palindromic_substring/playground.ipynb b/leetcode_old/longest_palindromic_substring/playground.ipynb similarity index 100% rename from leetcode/longest_palindromic_substring/playground.ipynb rename to leetcode_old/longest_palindromic_substring/playground.ipynb diff --git a/leetcode/longest_palindromic_substring/solution.py b/leetcode_old/longest_palindromic_substring/solution.py similarity index 100% rename from leetcode/longest_palindromic_substring/solution.py rename to leetcode_old/longest_palindromic_substring/solution.py diff --git a/leetcode/longest_palindromic_substring/tests.py b/leetcode_old/longest_palindromic_substring/tests.py similarity index 100% rename from leetcode/longest_palindromic_substring/tests.py rename to leetcode_old/longest_palindromic_substring/tests.py diff --git a/leetcode/longest_substring_without_repeating_characters/README.md b/leetcode_old/longest_substring_without_repeating_characters/README.md similarity index 100% rename from leetcode/longest_substring_without_repeating_characters/README.md rename to leetcode_old/longest_substring_without_repeating_characters/README.md diff --git a/leetcode/merge_two_sorted_lists/__init__.py b/leetcode_old/longest_substring_without_repeating_characters/__init__.py similarity index 100% rename from leetcode/merge_two_sorted_lists/__init__.py rename to leetcode_old/longest_substring_without_repeating_characters/__init__.py diff --git a/leetcode/longest_substring_without_repeating_characters/playground.ipynb b/leetcode_old/longest_substring_without_repeating_characters/playground.ipynb similarity index 100% rename from leetcode/longest_substring_without_repeating_characters/playground.ipynb rename to leetcode_old/longest_substring_without_repeating_characters/playground.ipynb diff --git a/leetcode/longest_substring_without_repeating_characters/solution.py b/leetcode_old/longest_substring_without_repeating_characters/solution.py similarity index 100% rename from leetcode/longest_substring_without_repeating_characters/solution.py rename to leetcode_old/longest_substring_without_repeating_characters/solution.py diff --git a/leetcode/longest_substring_without_repeating_characters/tests.py b/leetcode_old/longest_substring_without_repeating_characters/tests.py similarity index 100% rename from leetcode/longest_substring_without_repeating_characters/tests.py rename to leetcode_old/longest_substring_without_repeating_characters/tests.py diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/README.md b/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/README.md similarity index 100% rename from leetcode/lowest_common_ancestor_of_a_binary_search_tree/README.md rename to leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/README.md diff --git a/leetcode/middle_of_the_linked_list/__init__.py b/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/__init__.py similarity index 100% rename from leetcode/middle_of_the_linked_list/__init__.py rename to leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/__init__.py diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb b/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb similarity index 100% rename from leetcode/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb rename to leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/solution.py b/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/solution.py similarity index 100% rename from leetcode/lowest_common_ancestor_of_a_binary_search_tree/solution.py rename to leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/solution.py diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/tests.py b/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/tests.py similarity index 100% rename from leetcode/lowest_common_ancestor_of_a_binary_search_tree/tests.py rename to leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/tests.py diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/README.md b/leetcode_old/lowest_common_ancestor_of_a_binary_tree/README.md similarity index 100% rename from leetcode/lowest_common_ancestor_of_a_binary_tree/README.md rename to leetcode_old/lowest_common_ancestor_of_a_binary_tree/README.md diff --git a/leetcode/min_stack/__init__.py b/leetcode_old/lowest_common_ancestor_of_a_binary_tree/__init__.py similarity index 100% rename from leetcode/min_stack/__init__.py rename to leetcode_old/lowest_common_ancestor_of_a_binary_tree/__init__.py diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/playground.ipynb b/leetcode_old/lowest_common_ancestor_of_a_binary_tree/playground.ipynb similarity index 100% rename from leetcode/lowest_common_ancestor_of_a_binary_tree/playground.ipynb rename to leetcode_old/lowest_common_ancestor_of_a_binary_tree/playground.ipynb diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/solution.py b/leetcode_old/lowest_common_ancestor_of_a_binary_tree/solution.py similarity index 100% rename from leetcode/lowest_common_ancestor_of_a_binary_tree/solution.py rename to leetcode_old/lowest_common_ancestor_of_a_binary_tree/solution.py diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/tests.py b/leetcode_old/lowest_common_ancestor_of_a_binary_tree/tests.py similarity index 100% rename from leetcode/lowest_common_ancestor_of_a_binary_tree/tests.py rename to leetcode_old/lowest_common_ancestor_of_a_binary_tree/tests.py diff --git a/leetcode_old/lru_cache/README.md b/leetcode_old/lru_cache/README.md new file mode 100644 index 0000000..7037dbc --- /dev/null +++ b/leetcode_old/lru_cache/README.md @@ -0,0 +1,50 @@ +# LRU Cache + +**Difficulty:** Medium +**Topics:** Hash Table, Linked List, Design, Doubly-Linked List +**Tags:** grind-75 + +**LeetCode:** [Problem 146](https://leetcode.com/problems/lru-cache/description/) + +## Problem Description + +Design a data structure that follows the constraints of a Least Recently Used (LRU) cache. + +Implement the `LRUCache` class: + +- `LRUCache(int capacity)` Initialize the LRU cache with positive size capacity +- `int get(int key)` Return the value of the key if the key exists, otherwise return -1 +- `void put(int key, int value)` Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key + +The functions `get` and `put` must each run in `O(1)` average time complexity. + +## Examples + +### Example 1: + +``` +Input +["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"] +[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]] +Output +[null, null, null, 1, null, -1, null, -1, 3, 4] + +Explanation +LRUCache lRUCache = new LRUCache(2); +lRUCache.put(1, 1); // cache is {1=1} +lRUCache.put(2, 2); // cache is {1=1, 2=2} +lRUCache.get(1); // return 1 +lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3} +lRUCache.get(2); // returns -1 (not found) +lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3} +lRUCache.get(1); // return -1 (not found) +lRUCache.get(3); // return 3 +lRUCache.get(4); // return 4 +``` + +## Constraints + +- 1 <= capacity <= 3000 +- 0 <= key <= 10^4 +- 0 <= value <= 10^5 +- At most 2 \* 10^5 calls will be made to get and put diff --git a/leetcode/minimum_height_trees/__init__.py b/leetcode_old/lru_cache/__init__.py similarity index 100% rename from leetcode/minimum_height_trees/__init__.py rename to leetcode_old/lru_cache/__init__.py diff --git a/leetcode/lru_cache/playground.ipynb b/leetcode_old/lru_cache/playground.ipynb similarity index 100% rename from leetcode/lru_cache/playground.ipynb rename to leetcode_old/lru_cache/playground.ipynb diff --git a/leetcode/lru_cache/solution.py b/leetcode_old/lru_cache/solution.py similarity index 100% rename from leetcode/lru_cache/solution.py rename to leetcode_old/lru_cache/solution.py diff --git a/leetcode/lru_cache/tests.py b/leetcode_old/lru_cache/tests.py similarity index 100% rename from leetcode/lru_cache/tests.py rename to leetcode_old/lru_cache/tests.py diff --git a/leetcode/majority_element/README.md b/leetcode_old/majority_element/README.md similarity index 100% rename from leetcode/majority_element/README.md rename to leetcode_old/majority_element/README.md diff --git a/leetcode/minimum_window_substring/__init__.py b/leetcode_old/majority_element/__init__.py similarity index 100% rename from leetcode/minimum_window_substring/__init__.py rename to leetcode_old/majority_element/__init__.py diff --git a/leetcode/majority_element/playground.ipynb b/leetcode_old/majority_element/playground.ipynb similarity index 100% rename from leetcode/majority_element/playground.ipynb rename to leetcode_old/majority_element/playground.ipynb diff --git a/leetcode/majority_element/solution.py b/leetcode_old/majority_element/solution.py similarity index 100% rename from leetcode/majority_element/solution.py rename to leetcode_old/majority_element/solution.py diff --git a/leetcode/majority_element/tests.py b/leetcode_old/majority_element/tests.py similarity index 100% rename from leetcode/majority_element/tests.py rename to leetcode_old/majority_element/tests.py diff --git a/leetcode/maximum_depth_of_binary_tree/README.md b/leetcode_old/maximum_depth_of_binary_tree/README.md similarity index 100% rename from leetcode/maximum_depth_of_binary_tree/README.md rename to leetcode_old/maximum_depth_of_binary_tree/README.md diff --git a/leetcode/number_of_islands/__init__.py b/leetcode_old/maximum_depth_of_binary_tree/__init__.py similarity index 100% rename from leetcode/number_of_islands/__init__.py rename to leetcode_old/maximum_depth_of_binary_tree/__init__.py diff --git a/leetcode/maximum_depth_of_binary_tree/playground.ipynb b/leetcode_old/maximum_depth_of_binary_tree/playground.ipynb similarity index 100% rename from leetcode/maximum_depth_of_binary_tree/playground.ipynb rename to leetcode_old/maximum_depth_of_binary_tree/playground.ipynb diff --git a/leetcode/maximum_depth_of_binary_tree/solution.py b/leetcode_old/maximum_depth_of_binary_tree/solution.py similarity index 100% rename from leetcode/maximum_depth_of_binary_tree/solution.py rename to leetcode_old/maximum_depth_of_binary_tree/solution.py diff --git a/leetcode/maximum_depth_of_binary_tree/tests.py b/leetcode_old/maximum_depth_of_binary_tree/tests.py similarity index 100% rename from leetcode/maximum_depth_of_binary_tree/tests.py rename to leetcode_old/maximum_depth_of_binary_tree/tests.py diff --git a/leetcode/maximum_profit_in_job_scheduling/README.md b/leetcode_old/maximum_profit_in_job_scheduling/README.md similarity index 100% rename from leetcode/maximum_profit_in_job_scheduling/README.md rename to leetcode_old/maximum_profit_in_job_scheduling/README.md diff --git a/leetcode/partition_equal_subset_sum/__init__.py b/leetcode_old/maximum_profit_in_job_scheduling/__init__.py similarity index 100% rename from leetcode/partition_equal_subset_sum/__init__.py rename to leetcode_old/maximum_profit_in_job_scheduling/__init__.py diff --git a/leetcode/maximum_profit_in_job_scheduling/playground.ipynb b/leetcode_old/maximum_profit_in_job_scheduling/playground.ipynb similarity index 100% rename from leetcode/maximum_profit_in_job_scheduling/playground.ipynb rename to leetcode_old/maximum_profit_in_job_scheduling/playground.ipynb diff --git a/leetcode/maximum_profit_in_job_scheduling/solution.py b/leetcode_old/maximum_profit_in_job_scheduling/solution.py similarity index 100% rename from leetcode/maximum_profit_in_job_scheduling/solution.py rename to leetcode_old/maximum_profit_in_job_scheduling/solution.py diff --git a/leetcode/maximum_profit_in_job_scheduling/tests.py b/leetcode_old/maximum_profit_in_job_scheduling/tests.py similarity index 100% rename from leetcode/maximum_profit_in_job_scheduling/tests.py rename to leetcode_old/maximum_profit_in_job_scheduling/tests.py diff --git a/leetcode/maximum_subarray/README.md b/leetcode_old/maximum_subarray/README.md similarity index 100% rename from leetcode/maximum_subarray/README.md rename to leetcode_old/maximum_subarray/README.md diff --git a/leetcode/permutations/__init__.py b/leetcode_old/maximum_subarray/__init__.py similarity index 100% rename from leetcode/permutations/__init__.py rename to leetcode_old/maximum_subarray/__init__.py diff --git a/leetcode/maximum_subarray/playground.ipynb b/leetcode_old/maximum_subarray/playground.ipynb similarity index 100% rename from leetcode/maximum_subarray/playground.ipynb rename to leetcode_old/maximum_subarray/playground.ipynb diff --git a/leetcode/maximum_subarray/solution.py b/leetcode_old/maximum_subarray/solution.py similarity index 100% rename from leetcode/maximum_subarray/solution.py rename to leetcode_old/maximum_subarray/solution.py diff --git a/leetcode/maximum_subarray/tests.py b/leetcode_old/maximum_subarray/tests.py similarity index 100% rename from leetcode/maximum_subarray/tests.py rename to leetcode_old/maximum_subarray/tests.py diff --git a/leetcode/merge_intervals/README.md b/leetcode_old/merge_intervals/README.md similarity index 100% rename from leetcode/merge_intervals/README.md rename to leetcode_old/merge_intervals/README.md diff --git a/leetcode/product_of_array_except_self/__init__.py b/leetcode_old/merge_intervals/__init__.py similarity index 100% rename from leetcode/product_of_array_except_self/__init__.py rename to leetcode_old/merge_intervals/__init__.py diff --git a/leetcode/merge_intervals/playground.ipynb b/leetcode_old/merge_intervals/playground.ipynb similarity index 100% rename from leetcode/merge_intervals/playground.ipynb rename to leetcode_old/merge_intervals/playground.ipynb diff --git a/leetcode/merge_intervals/solution.py b/leetcode_old/merge_intervals/solution.py similarity index 100% rename from leetcode/merge_intervals/solution.py rename to leetcode_old/merge_intervals/solution.py diff --git a/leetcode/merge_intervals/tests.py b/leetcode_old/merge_intervals/tests.py similarity index 100% rename from leetcode/merge_intervals/tests.py rename to leetcode_old/merge_intervals/tests.py diff --git a/leetcode/merge_k_sorted_lists/README.md b/leetcode_old/merge_k_sorted_lists/README.md similarity index 100% rename from leetcode/merge_k_sorted_lists/README.md rename to leetcode_old/merge_k_sorted_lists/README.md diff --git a/leetcode/ransom_note/__init__.py b/leetcode_old/merge_k_sorted_lists/__init__.py similarity index 100% rename from leetcode/ransom_note/__init__.py rename to leetcode_old/merge_k_sorted_lists/__init__.py diff --git a/leetcode/merge_k_sorted_lists/playground.ipynb b/leetcode_old/merge_k_sorted_lists/playground.ipynb similarity index 100% rename from leetcode/merge_k_sorted_lists/playground.ipynb rename to leetcode_old/merge_k_sorted_lists/playground.ipynb diff --git a/leetcode/merge_k_sorted_lists/solution.py b/leetcode_old/merge_k_sorted_lists/solution.py similarity index 100% rename from leetcode/merge_k_sorted_lists/solution.py rename to leetcode_old/merge_k_sorted_lists/solution.py diff --git a/leetcode/merge_k_sorted_lists/tests.py b/leetcode_old/merge_k_sorted_lists/tests.py similarity index 100% rename from leetcode/merge_k_sorted_lists/tests.py rename to leetcode_old/merge_k_sorted_lists/tests.py diff --git a/leetcode/merge_two_sorted_lists/README.md b/leetcode_old/merge_two_sorted_lists/README.md similarity index 100% rename from leetcode/merge_two_sorted_lists/README.md rename to leetcode_old/merge_two_sorted_lists/README.md diff --git a/leetcode/reverse_linked_list/__init__.py b/leetcode_old/merge_two_sorted_lists/__init__.py similarity index 100% rename from leetcode/reverse_linked_list/__init__.py rename to leetcode_old/merge_two_sorted_lists/__init__.py diff --git a/leetcode/merge_two_sorted_lists/playground.ipynb b/leetcode_old/merge_two_sorted_lists/playground.ipynb similarity index 100% rename from leetcode/merge_two_sorted_lists/playground.ipynb rename to leetcode_old/merge_two_sorted_lists/playground.ipynb diff --git a/leetcode/merge_two_sorted_lists/solution.py b/leetcode_old/merge_two_sorted_lists/solution.py similarity index 100% rename from leetcode/merge_two_sorted_lists/solution.py rename to leetcode_old/merge_two_sorted_lists/solution.py diff --git a/leetcode/merge_two_sorted_lists/tests.py b/leetcode_old/merge_two_sorted_lists/tests.py similarity index 100% rename from leetcode/merge_two_sorted_lists/tests.py rename to leetcode_old/merge_two_sorted_lists/tests.py diff --git a/leetcode/middle_of_the_linked_list/README.md b/leetcode_old/middle_of_the_linked_list/README.md similarity index 100% rename from leetcode/middle_of_the_linked_list/README.md rename to leetcode_old/middle_of_the_linked_list/README.md diff --git a/leetcode/reverse_linked_list_ii/__init__.py b/leetcode_old/middle_of_the_linked_list/__init__.py similarity index 100% rename from leetcode/reverse_linked_list_ii/__init__.py rename to leetcode_old/middle_of_the_linked_list/__init__.py diff --git a/leetcode/middle_of_the_linked_list/playground.ipynb b/leetcode_old/middle_of_the_linked_list/playground.ipynb similarity index 100% rename from leetcode/middle_of_the_linked_list/playground.ipynb rename to leetcode_old/middle_of_the_linked_list/playground.ipynb diff --git a/leetcode/middle_of_the_linked_list/solution.py b/leetcode_old/middle_of_the_linked_list/solution.py similarity index 100% rename from leetcode/middle_of_the_linked_list/solution.py rename to leetcode_old/middle_of_the_linked_list/solution.py diff --git a/leetcode/middle_of_the_linked_list/tests.py b/leetcode_old/middle_of_the_linked_list/tests.py similarity index 100% rename from leetcode/middle_of_the_linked_list/tests.py rename to leetcode_old/middle_of_the_linked_list/tests.py diff --git a/leetcode/min_stack/README.md b/leetcode_old/min_stack/README.md similarity index 100% rename from leetcode/min_stack/README.md rename to leetcode_old/min_stack/README.md diff --git a/leetcode/rotting_oranges/__init__.py b/leetcode_old/min_stack/__init__.py similarity index 100% rename from leetcode/rotting_oranges/__init__.py rename to leetcode_old/min_stack/__init__.py diff --git a/leetcode/min_stack/playground.ipynb b/leetcode_old/min_stack/playground.ipynb similarity index 100% rename from leetcode/min_stack/playground.ipynb rename to leetcode_old/min_stack/playground.ipynb diff --git a/leetcode/min_stack/solution.py b/leetcode_old/min_stack/solution.py similarity index 100% rename from leetcode/min_stack/solution.py rename to leetcode_old/min_stack/solution.py diff --git a/leetcode/min_stack/tests.py b/leetcode_old/min_stack/tests.py similarity index 100% rename from leetcode/min_stack/tests.py rename to leetcode_old/min_stack/tests.py diff --git a/leetcode/minimum_height_trees/README.md b/leetcode_old/minimum_height_trees/README.md similarity index 100% rename from leetcode/minimum_height_trees/README.md rename to leetcode_old/minimum_height_trees/README.md diff --git a/leetcode/search_in_rotated_sorted_array/__init__.py b/leetcode_old/minimum_height_trees/__init__.py similarity index 100% rename from leetcode/search_in_rotated_sorted_array/__init__.py rename to leetcode_old/minimum_height_trees/__init__.py diff --git a/leetcode/minimum_height_trees/playground.ipynb b/leetcode_old/minimum_height_trees/playground.ipynb similarity index 100% rename from leetcode/minimum_height_trees/playground.ipynb rename to leetcode_old/minimum_height_trees/playground.ipynb diff --git a/leetcode/minimum_height_trees/solution.py b/leetcode_old/minimum_height_trees/solution.py similarity index 100% rename from leetcode/minimum_height_trees/solution.py rename to leetcode_old/minimum_height_trees/solution.py diff --git a/leetcode/minimum_height_trees/tests.py b/leetcode_old/minimum_height_trees/tests.py similarity index 100% rename from leetcode/minimum_height_trees/tests.py rename to leetcode_old/minimum_height_trees/tests.py diff --git a/leetcode/minimum_window_substring/README.md b/leetcode_old/minimum_window_substring/README.md similarity index 100% rename from leetcode/minimum_window_substring/README.md rename to leetcode_old/minimum_window_substring/README.md diff --git a/leetcode/serialize_and_deserialize_binary_tree/__init__.py b/leetcode_old/minimum_window_substring/__init__.py similarity index 100% rename from leetcode/serialize_and_deserialize_binary_tree/__init__.py rename to leetcode_old/minimum_window_substring/__init__.py diff --git a/leetcode/minimum_window_substring/playground.ipynb b/leetcode_old/minimum_window_substring/playground.ipynb similarity index 100% rename from leetcode/minimum_window_substring/playground.ipynb rename to leetcode_old/minimum_window_substring/playground.ipynb diff --git a/leetcode/minimum_window_substring/solution.py b/leetcode_old/minimum_window_substring/solution.py similarity index 100% rename from leetcode/minimum_window_substring/solution.py rename to leetcode_old/minimum_window_substring/solution.py diff --git a/leetcode/minimum_window_substring/tests.py b/leetcode_old/minimum_window_substring/tests.py similarity index 100% rename from leetcode/minimum_window_substring/tests.py rename to leetcode_old/minimum_window_substring/tests.py diff --git a/leetcode/number_of_islands/README.md b/leetcode_old/number_of_islands/README.md similarity index 100% rename from leetcode/number_of_islands/README.md rename to leetcode_old/number_of_islands/README.md diff --git a/leetcode/sort_colors/__init__.py b/leetcode_old/number_of_islands/__init__.py similarity index 100% rename from leetcode/sort_colors/__init__.py rename to leetcode_old/number_of_islands/__init__.py diff --git a/leetcode/number_of_islands/playground.ipynb b/leetcode_old/number_of_islands/playground.ipynb similarity index 95% rename from leetcode/number_of_islands/playground.ipynb rename to leetcode_old/number_of_islands/playground.ipynb index 6b0ed5f..e943b42 100644 --- a/leetcode/number_of_islands/playground.ipynb +++ b/leetcode_old/number_of_islands/playground.ipynb @@ -34,7 +34,8 @@ "metadata": {}, "outputs": [], "source": [ - "result = Solution().num_islands(grid)\nresult" + "result = Solution().num_islands(grid)\n", + "result" ] }, { diff --git a/leetcode/number_of_islands/solution.py b/leetcode_old/number_of_islands/solution.py similarity index 100% rename from leetcode/number_of_islands/solution.py rename to leetcode_old/number_of_islands/solution.py diff --git a/leetcode/number_of_islands/tests.py b/leetcode_old/number_of_islands/tests.py similarity index 100% rename from leetcode/number_of_islands/tests.py rename to leetcode_old/number_of_islands/tests.py diff --git a/leetcode/partition_equal_subset_sum/README.md b/leetcode_old/partition_equal_subset_sum/README.md similarity index 100% rename from leetcode/partition_equal_subset_sum/README.md rename to leetcode_old/partition_equal_subset_sum/README.md diff --git a/leetcode/spiral_matrix/__init__.py b/leetcode_old/partition_equal_subset_sum/__init__.py similarity index 100% rename from leetcode/spiral_matrix/__init__.py rename to leetcode_old/partition_equal_subset_sum/__init__.py diff --git a/leetcode/partition_equal_subset_sum/playground.ipynb b/leetcode_old/partition_equal_subset_sum/playground.ipynb similarity index 100% rename from leetcode/partition_equal_subset_sum/playground.ipynb rename to leetcode_old/partition_equal_subset_sum/playground.ipynb diff --git a/leetcode/partition_equal_subset_sum/solution.py b/leetcode_old/partition_equal_subset_sum/solution.py similarity index 100% rename from leetcode/partition_equal_subset_sum/solution.py rename to leetcode_old/partition_equal_subset_sum/solution.py diff --git a/leetcode/partition_equal_subset_sum/tests.py b/leetcode_old/partition_equal_subset_sum/tests.py similarity index 100% rename from leetcode/partition_equal_subset_sum/tests.py rename to leetcode_old/partition_equal_subset_sum/tests.py diff --git a/leetcode/permutations/README.md b/leetcode_old/permutations/README.md similarity index 100% rename from leetcode/permutations/README.md rename to leetcode_old/permutations/README.md diff --git a/leetcode/string_to_integer_atoi/__init__.py b/leetcode_old/permutations/__init__.py similarity index 100% rename from leetcode/string_to_integer_atoi/__init__.py rename to leetcode_old/permutations/__init__.py diff --git a/leetcode_old/permutations/playground.ipynb b/leetcode_old/permutations/playground.ipynb new file mode 100644 index 0000000..8e7608d --- /dev/null +++ b/leetcode_old/permutations/playground.ipynb @@ -0,0 +1,75 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [1, 2, 3]\n", + "expected = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "execute", + "metadata": {}, + "outputs": [], + "source": [ + "result = Solution().permute(nums)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "test", + "metadata": {}, + "outputs": [], + "source": [ + "# Check that we have the right number of permutations\n", + "assert len(result) == len(expected)\n", + "# Sort for comparison since order doesn't matter\n", + "result_sorted = [sorted(perm) for perm in result]\n", + "expected_sorted = [sorted(perm) for perm in expected]\n", + "result_sorted.sort()\n", + "expected_sorted.sort()\n", + "assert result_sorted == expected_sorted" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/permutations/solution.py b/leetcode_old/permutations/solution.py similarity index 100% rename from leetcode/permutations/solution.py rename to leetcode_old/permutations/solution.py diff --git a/leetcode/permutations/tests.py b/leetcode_old/permutations/tests.py similarity index 100% rename from leetcode/permutations/tests.py rename to leetcode_old/permutations/tests.py diff --git a/leetcode/product_of_array_except_self/README.md b/leetcode_old/product_of_array_except_self/README.md similarity index 100% rename from leetcode/product_of_array_except_self/README.md rename to leetcode_old/product_of_array_except_self/README.md diff --git a/leetcode/task_scheduler/__init__.py b/leetcode_old/product_of_array_except_self/__init__.py similarity index 100% rename from leetcode/task_scheduler/__init__.py rename to leetcode_old/product_of_array_except_self/__init__.py diff --git a/leetcode/product_of_array_except_self/playground.ipynb b/leetcode_old/product_of_array_except_self/playground.ipynb similarity index 100% rename from leetcode/product_of_array_except_self/playground.ipynb rename to leetcode_old/product_of_array_except_self/playground.ipynb diff --git a/leetcode/product_of_array_except_self/solution.py b/leetcode_old/product_of_array_except_self/solution.py similarity index 100% rename from leetcode/product_of_array_except_self/solution.py rename to leetcode_old/product_of_array_except_self/solution.py diff --git a/leetcode/product_of_array_except_self/tests.py b/leetcode_old/product_of_array_except_self/tests.py similarity index 100% rename from leetcode/product_of_array_except_self/tests.py rename to leetcode_old/product_of_array_except_self/tests.py diff --git a/leetcode/ransom_note/README.md b/leetcode_old/ransom_note/README.md similarity index 100% rename from leetcode/ransom_note/README.md rename to leetcode_old/ransom_note/README.md diff --git a/leetcode/three_sum/__init__.py b/leetcode_old/ransom_note/__init__.py similarity index 100% rename from leetcode/three_sum/__init__.py rename to leetcode_old/ransom_note/__init__.py diff --git a/leetcode/ransom_note/playground.ipynb b/leetcode_old/ransom_note/playground.ipynb similarity index 100% rename from leetcode/ransom_note/playground.ipynb rename to leetcode_old/ransom_note/playground.ipynb diff --git a/leetcode/ransom_note/solution.py b/leetcode_old/ransom_note/solution.py similarity index 100% rename from leetcode/ransom_note/solution.py rename to leetcode_old/ransom_note/solution.py diff --git a/leetcode/ransom_note/tests.py b/leetcode_old/ransom_note/tests.py similarity index 100% rename from leetcode/ransom_note/tests.py rename to leetcode_old/ransom_note/tests.py diff --git a/leetcode_old/reverse_linked_list/README.md b/leetcode_old/reverse_linked_list/README.md new file mode 100644 index 0000000..ae96ddd --- /dev/null +++ b/leetcode_old/reverse_linked_list/README.md @@ -0,0 +1,45 @@ +# Reverse Linked List + +**Difficulty:** Easy +**Topics:** Linked List, Recursion +**Tags:** grind-75 + +**LeetCode:** [Problem 206](https://leetcode.com/problems/reverse-linked-list/description/) + +## Problem Description + +Given the `head` of a singly linked list, reverse the list, and return the reversed list. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2021/02/19/rev1ex1.jpg) + +``` +Input: head = [1,2,3,4,5] +Output: [5,4,3,2,1] +``` + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2021/02/19/rev1ex2.jpg) + +``` +Input: head = [1,2] +Output: [2,1] +``` + +### Example 3: + +``` +Input: head = [] +Output: [] +``` + +## Constraints + +- The number of nodes in the list is the range `[0, 5000]`. +- `-5000 <= Node.val <= 5000` + +**Follow up:** A linked list can be reversed either iteratively or recursively. Could you implement both? diff --git a/leetcode/time_based_key_value_store/__init__.py b/leetcode_old/reverse_linked_list/__init__.py similarity index 100% rename from leetcode/time_based_key_value_store/__init__.py rename to leetcode_old/reverse_linked_list/__init__.py diff --git a/leetcode/reverse_linked_list/playground.ipynb b/leetcode_old/reverse_linked_list/playground.ipynb similarity index 100% rename from leetcode/reverse_linked_list/playground.ipynb rename to leetcode_old/reverse_linked_list/playground.ipynb diff --git a/leetcode_old/reverse_linked_list/solution.py b/leetcode_old/reverse_linked_list/solution.py new file mode 100644 index 0000000..58a0cbe --- /dev/null +++ b/leetcode_old/reverse_linked_list/solution.py @@ -0,0 +1,46 @@ +from leetcode_py import ListNode + + +class Solution: + # Time: O(n) + # Space: O(1) + def reverse_list(self, head: ListNode[int] | None) -> ListNode[int] | None: + if not head: + return None + + # Iterative approach using three pointers + # Example: [1,2,3] -> [3,2,1] + # + # Initial: prev curr + # None ↓ + # 1 -> 2 -> 3 -> None + # + prev: ListNode[int] | None = None + curr: ListNode[int] | None = head + + while curr: + # Store next node before breaking the link + next_node = curr.next + # + # prev curr next_node + # None ↓ ↓ + # 1 -> 2 -> 3 -> None + # + + # Reverse the current link + curr.next = prev + # None <- 1 2 -> 3 -> None + # prev curr next_node + # + + # Move pointers forward + prev = curr + curr = next_node + # 1 <- 2 3 -> None + # prev curr + # + + # 1 <- 2 <- 3 None + # prev curr + # prev now points to new head of reversed list + return prev diff --git a/leetcode/reverse_linked_list/tests.py b/leetcode_old/reverse_linked_list/tests.py similarity index 100% rename from leetcode/reverse_linked_list/tests.py rename to leetcode_old/reverse_linked_list/tests.py diff --git a/leetcode/reverse_linked_list_ii/README.md b/leetcode_old/reverse_linked_list_ii/README.md similarity index 100% rename from leetcode/reverse_linked_list_ii/README.md rename to leetcode_old/reverse_linked_list_ii/README.md diff --git a/leetcode/trapping_rain_water/__init__.py b/leetcode_old/reverse_linked_list_ii/__init__.py similarity index 100% rename from leetcode/trapping_rain_water/__init__.py rename to leetcode_old/reverse_linked_list_ii/__init__.py diff --git a/leetcode/reverse_linked_list_ii/playground.ipynb b/leetcode_old/reverse_linked_list_ii/playground.ipynb similarity index 100% rename from leetcode/reverse_linked_list_ii/playground.ipynb rename to leetcode_old/reverse_linked_list_ii/playground.ipynb diff --git a/leetcode/reverse_linked_list_ii/solution.py b/leetcode_old/reverse_linked_list_ii/solution.py similarity index 100% rename from leetcode/reverse_linked_list_ii/solution.py rename to leetcode_old/reverse_linked_list_ii/solution.py diff --git a/leetcode/reverse_linked_list_ii/tests.py b/leetcode_old/reverse_linked_list_ii/tests.py similarity index 100% rename from leetcode/reverse_linked_list_ii/tests.py rename to leetcode_old/reverse_linked_list_ii/tests.py diff --git a/leetcode/rotting_oranges/README.md b/leetcode_old/rotting_oranges/README.md similarity index 100% rename from leetcode/rotting_oranges/README.md rename to leetcode_old/rotting_oranges/README.md diff --git a/leetcode/valid_anagram/__init__.py b/leetcode_old/rotting_oranges/__init__.py similarity index 100% rename from leetcode/valid_anagram/__init__.py rename to leetcode_old/rotting_oranges/__init__.py diff --git a/leetcode/rotting_oranges/playground.ipynb b/leetcode_old/rotting_oranges/playground.ipynb similarity index 74% rename from leetcode/rotting_oranges/playground.ipynb rename to leetcode_old/rotting_oranges/playground.ipynb index 077b70a..7d3f7bf 100644 --- a/leetcode/rotting_oranges/playground.ipynb +++ b/leetcode_old/rotting_oranges/playground.ipynb @@ -6,7 +6,9 @@ "id": "imports", "metadata": {}, "outputs": [], - "source": ["from solution import Solution"] + "source": [ + "from solution import Solution" + ] }, { "cell_type": "code", @@ -14,7 +16,11 @@ "id": "setup", "metadata": {}, "outputs": [], - "source": ["# Example test case\ngrid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]\nexpected = 4"] + "source": [ + "# Example test case\n", + "grid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]\n", + "expected = 4" + ] }, { "cell_type": "code", @@ -22,7 +28,10 @@ "id": "execute", "metadata": {}, "outputs": [], - "source": ["result = Solution().oranges_rotting(grid)\nresult"] + "source": [ + "result = Solution().oranges_rotting(grid)\n", + "result" + ] }, { "cell_type": "code", @@ -30,7 +39,9 @@ "id": "test", "metadata": {}, "outputs": [], - "source": ["assert result == expected"] + "source": [ + "assert result == expected" + ] } ], "metadata": { diff --git a/leetcode/rotting_oranges/solution.py b/leetcode_old/rotting_oranges/solution.py similarity index 100% rename from leetcode/rotting_oranges/solution.py rename to leetcode_old/rotting_oranges/solution.py diff --git a/leetcode/rotting_oranges/tests.py b/leetcode_old/rotting_oranges/tests.py similarity index 100% rename from leetcode/rotting_oranges/tests.py rename to leetcode_old/rotting_oranges/tests.py diff --git a/leetcode/search_in_rotated_sorted_array/README.md b/leetcode_old/search_in_rotated_sorted_array/README.md similarity index 100% rename from leetcode/search_in_rotated_sorted_array/README.md rename to leetcode_old/search_in_rotated_sorted_array/README.md diff --git a/leetcode/valid_palindrome/__init__.py b/leetcode_old/search_in_rotated_sorted_array/__init__.py similarity index 100% rename from leetcode/valid_palindrome/__init__.py rename to leetcode_old/search_in_rotated_sorted_array/__init__.py diff --git a/leetcode/search_in_rotated_sorted_array/playground.ipynb b/leetcode_old/search_in_rotated_sorted_array/playground.ipynb similarity index 100% rename from leetcode/search_in_rotated_sorted_array/playground.ipynb rename to leetcode_old/search_in_rotated_sorted_array/playground.ipynb diff --git a/leetcode/search_in_rotated_sorted_array/solution.py b/leetcode_old/search_in_rotated_sorted_array/solution.py similarity index 100% rename from leetcode/search_in_rotated_sorted_array/solution.py rename to leetcode_old/search_in_rotated_sorted_array/solution.py diff --git a/leetcode/search_in_rotated_sorted_array/tests.py b/leetcode_old/search_in_rotated_sorted_array/tests.py similarity index 100% rename from leetcode/search_in_rotated_sorted_array/tests.py rename to leetcode_old/search_in_rotated_sorted_array/tests.py diff --git a/leetcode_old/serialize_and_deserialize_binary_tree/README.md b/leetcode_old/serialize_and_deserialize_binary_tree/README.md new file mode 100644 index 0000000..8226e6c --- /dev/null +++ b/leetcode_old/serialize_and_deserialize_binary_tree/README.md @@ -0,0 +1,38 @@ +# Serialize and Deserialize Binary Tree + +**Difficulty:** Hard +**Topics:** String, Tree, Depth-First Search, Breadth-First Search, Design, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 297](https://leetcode.com/problems/serialize-and-deserialize-binary-tree/description/) + +## Problem Description + +Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment. + +Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure. + +**Clarification:** The input/output format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2020/09/15/serdeser.jpg) + +``` +Input: root = [1,2,3,null,null,4,5] +Output: [1,2,3,null,null,4,5] +``` + +### Example 2: + +``` +Input: root = [] +Output: [] +``` + +## Constraints + +- The number of nodes in the tree is in the range [0, 10^4]. +- -1000 <= Node.val <= 1000 diff --git a/leetcode/valid_parentheses/__init__.py b/leetcode_old/serialize_and_deserialize_binary_tree/__init__.py similarity index 100% rename from leetcode/valid_parentheses/__init__.py rename to leetcode_old/serialize_and_deserialize_binary_tree/__init__.py diff --git a/leetcode/serialize_and_deserialize_binary_tree/playground.ipynb b/leetcode_old/serialize_and_deserialize_binary_tree/playground.ipynb similarity index 100% rename from leetcode/serialize_and_deserialize_binary_tree/playground.ipynb rename to leetcode_old/serialize_and_deserialize_binary_tree/playground.ipynb diff --git a/leetcode_old/serialize_and_deserialize_binary_tree/solution.py b/leetcode_old/serialize_and_deserialize_binary_tree/solution.py new file mode 100644 index 0000000..aeb3177 --- /dev/null +++ b/leetcode_old/serialize_and_deserialize_binary_tree/solution.py @@ -0,0 +1,81 @@ +from leetcode_py import TreeNode + + +class Codec: + # Preorder with Null Markers + # Time: O(n) + # Space: O(n) + def serialize(self, root: TreeNode | None) -> str: + vals = [] + + def dfs(node: TreeNode | None): + if not node: + vals.append("#") + return + vals.append(str(node.val)) + dfs(node.left) + dfs(node.right) + + dfs(root) + return ",".join(vals) + + # Time: O(n) + # Space: O(n) + def deserialize(self, data: str) -> TreeNode | None: + vals = iter(data.split(",")) + + def dfs(): + val = next(vals) + if val == "#": + return None + node = TreeNode(int(val)) + node.left = dfs() + node.right = dfs() + return node + + return dfs() + + +# Binary Tree Serialization Techniques + +# Example Tree: +# 1 +# / \ +# 2 3 +# / \ +# 4 5 + +# 1. Preorder with Null Markers (This Implementation) +# Visit: root → left → right, mark nulls with '#' +# Result: "1,2,#,#,3,4,#,#,5,#,#" +# Pros: Self-contained, unambiguous, O(n) reconstruction +# Cons: Longer string due to null markers + +# 2. Level-order (BFS) with Null Markers +# Visit level by level, mark nulls with '#' +# Result: "1,2,3,#,#,4,5" +# Pros: Simple format like preorder, level-by-level intuitive +# Cons: Still requires queue processing + +# 3. Postorder with Null Markers +# Visit: left → right → root +# Result: "#,#,2,#,#,4,#,#,5,3,1" +# Pros: Bottom-up reconstruction +# Cons: Less intuitive than preorder + +# 4. Inorder + Preorder (Two Arrays) +# Inorder: [2,1,4,3,5], Preorder: [1,2,3,4,5] +# Pros: Works for any binary tree structure +# Cons: Requires two arrays, only works with unique values + +# 5. Parenthetical Preorder +# Same traversal as #1 but with parentheses format: value(left)(right) +# Result: "1(2()())(3(4()())(5()()))" +# Pros: Human readable structure, shows nesting clearly +# Cons: Complex parsing, verbose + +# 6. Parenthetical Postorder +# Same traversal as #3 but with parentheses format: (left)(right)value +# Result: "(()()2)((()()4)(()()5)3)1" +# Pros: Bottom-up readable structure +# Cons: Even more complex parsing diff --git a/leetcode_old/serialize_and_deserialize_binary_tree/tests.py b/leetcode_old/serialize_and_deserialize_binary_tree/tests.py new file mode 100644 index 0000000..f1fb387 --- /dev/null +++ b/leetcode_old/serialize_and_deserialize_binary_tree/tests.py @@ -0,0 +1,100 @@ +import pytest + +from leetcode_py import TreeNode +from leetcode_py.test_utils import logged_test + +from .solution import Codec + + +class TestSerializeAndDeserializeBinaryTree: + def setup_method(self): + self.codec = Codec() + + @pytest.mark.parametrize( + "root_list", + [ + # Original test cases + ([1, 2, 3, None, None, 4, 5]), + ([]), + ([1]), + ([1, 2]), + ([1, None, 2]), + ([1, 2, 3, 4, 5, 6, 7]), + ([5, 2, 3, None, None, 2, 4, 3, 1]), + # Edge cases + ([0]), # Single node with value 0 + ([-1]), # Single node with negative value + ([1000]), # Single node with max value + ([-1000]), # Single node with min value + # Skewed trees + ([1, 2, None, 3, None, 4, None]), # Left skewed + ([1, None, 2, None, 3, None, 4]), # Right skewed + # Trees with negative values + ([-5, -3, -8, -2, -1, -7, -9]), + ([0, -1, 1, -2, None, None, 2]), + # Larger trees + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]), + # Trees with mixed null patterns + ([1, None, 2, None, 3, None, 4, None, 5]), + ([1, 2, None, 3, None, 4, None, 5]), + ([5, 4, 7, 3, None, 2, None, -1, None, 9]), + # Duplicate values + ([1, 1, 1, 1, 1, 1, 1]), + ([2, 2, None, 2, None]), + # Complex asymmetric trees + ([10, 5, 15, None, 6, 12, 20, None, None, None, 13, 18, 25]), + ([50, 30, 70, 20, 40, 60, 80, 10, 25, 35, 45]), + ], + ) + @logged_test + def test_serialize_deserialize(self, root_list: list[int | None]): + root = TreeNode.from_list(root_list) if root_list else None + serialized = self.codec.serialize(root) + deserialized = self.codec.deserialize(serialized) + if root is None: + assert deserialized is None + else: + assert deserialized is not None + assert deserialized.to_list() == root.to_list() + + @logged_test + def test_multiple_serialize_deserialize_cycles(self): + """Test that multiple serialize/deserialize cycles preserve the tree""" + root_list = [1, 2, 3, None, None, 4, 5] + root = TreeNode.from_list(root_list) + + # Perform multiple cycles + current = root + for _ in range(3): + serialized = self.codec.serialize(current) + current = self.codec.deserialize(serialized) + + assert current is not None + assert current.to_list() == root_list + + @logged_test + def test_serialization_format(self): + """Test that serialization produces expected string format""" + # Simple tree: [1, 2, 3] + root = TreeNode.from_list([1, 2, 3]) + serialized = self.codec.serialize(root) + + # Should be preorder: root, left, right with # for null + assert serialized == "1,2,#,#,3,#,#" + + # Empty tree + serialized_empty = self.codec.serialize(None) + assert serialized_empty == "#" + + @logged_test + def test_deserialization_edge_cases(self): + """Test deserialization with various string inputs""" + # Single null + assert self.codec.deserialize("#") is None + + # Single node + single = self.codec.deserialize("42,#,#") + assert single is not None + assert single.val == 42 + assert single.left is None + assert single.right is None diff --git a/leetcode/sort_colors/README.md b/leetcode_old/sort_colors/README.md similarity index 100% rename from leetcode/sort_colors/README.md rename to leetcode_old/sort_colors/README.md diff --git a/leetcode/validate_binary_search_tree/__init__.py b/leetcode_old/sort_colors/__init__.py similarity index 100% rename from leetcode/validate_binary_search_tree/__init__.py rename to leetcode_old/sort_colors/__init__.py diff --git a/leetcode/sort_colors/playground.ipynb b/leetcode_old/sort_colors/playground.ipynb similarity index 100% rename from leetcode/sort_colors/playground.ipynb rename to leetcode_old/sort_colors/playground.ipynb diff --git a/leetcode/sort_colors/solution.py b/leetcode_old/sort_colors/solution.py similarity index 100% rename from leetcode/sort_colors/solution.py rename to leetcode_old/sort_colors/solution.py diff --git a/leetcode/sort_colors/tests.py b/leetcode_old/sort_colors/tests.py similarity index 100% rename from leetcode/sort_colors/tests.py rename to leetcode_old/sort_colors/tests.py diff --git a/leetcode/spiral_matrix/README.md b/leetcode_old/spiral_matrix/README.md similarity index 100% rename from leetcode/spiral_matrix/README.md rename to leetcode_old/spiral_matrix/README.md diff --git a/leetcode/word_break/__init__.py b/leetcode_old/spiral_matrix/__init__.py similarity index 100% rename from leetcode/word_break/__init__.py rename to leetcode_old/spiral_matrix/__init__.py diff --git a/leetcode/spiral_matrix/playground.ipynb b/leetcode_old/spiral_matrix/playground.ipynb similarity index 100% rename from leetcode/spiral_matrix/playground.ipynb rename to leetcode_old/spiral_matrix/playground.ipynb diff --git a/leetcode/spiral_matrix/solution.py b/leetcode_old/spiral_matrix/solution.py similarity index 100% rename from leetcode/spiral_matrix/solution.py rename to leetcode_old/spiral_matrix/solution.py diff --git a/leetcode/spiral_matrix/tests.py b/leetcode_old/spiral_matrix/tests.py similarity index 100% rename from leetcode/spiral_matrix/tests.py rename to leetcode_old/spiral_matrix/tests.py diff --git a/leetcode/string_to_integer_atoi/README.md b/leetcode_old/string_to_integer_atoi/README.md similarity index 100% rename from leetcode/string_to_integer_atoi/README.md rename to leetcode_old/string_to_integer_atoi/README.md diff --git a/leetcode/word_ladder/__init__.py b/leetcode_old/string_to_integer_atoi/__init__.py similarity index 100% rename from leetcode/word_ladder/__init__.py rename to leetcode_old/string_to_integer_atoi/__init__.py diff --git a/leetcode/string_to_integer_atoi/playground.ipynb b/leetcode_old/string_to_integer_atoi/playground.ipynb similarity index 100% rename from leetcode/string_to_integer_atoi/playground.ipynb rename to leetcode_old/string_to_integer_atoi/playground.ipynb diff --git a/leetcode/string_to_integer_atoi/solution.py b/leetcode_old/string_to_integer_atoi/solution.py similarity index 100% rename from leetcode/string_to_integer_atoi/solution.py rename to leetcode_old/string_to_integer_atoi/solution.py diff --git a/leetcode/string_to_integer_atoi/tests.py b/leetcode_old/string_to_integer_atoi/tests.py similarity index 100% rename from leetcode/string_to_integer_atoi/tests.py rename to leetcode_old/string_to_integer_atoi/tests.py diff --git a/leetcode/task_scheduler/README.md b/leetcode_old/task_scheduler/README.md similarity index 100% rename from leetcode/task_scheduler/README.md rename to leetcode_old/task_scheduler/README.md diff --git a/leetcode/zero_one_matrix/__init__.py b/leetcode_old/task_scheduler/__init__.py similarity index 100% rename from leetcode/zero_one_matrix/__init__.py rename to leetcode_old/task_scheduler/__init__.py diff --git a/leetcode/task_scheduler/playground.ipynb b/leetcode_old/task_scheduler/playground.ipynb similarity index 100% rename from leetcode/task_scheduler/playground.ipynb rename to leetcode_old/task_scheduler/playground.ipynb diff --git a/leetcode/task_scheduler/solution.py b/leetcode_old/task_scheduler/solution.py similarity index 100% rename from leetcode/task_scheduler/solution.py rename to leetcode_old/task_scheduler/solution.py diff --git a/leetcode/task_scheduler/tests.py b/leetcode_old/task_scheduler/tests.py similarity index 100% rename from leetcode/task_scheduler/tests.py rename to leetcode_old/task_scheduler/tests.py diff --git a/leetcode/three_sum/README.md b/leetcode_old/three_sum/README.md similarity index 100% rename from leetcode/three_sum/README.md rename to leetcode_old/three_sum/README.md diff --git a/leetcode_old/three_sum/__init__.py b/leetcode_old/three_sum/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode_old/three_sum/playground.ipynb b/leetcode_old/three_sum/playground.ipynb new file mode 100644 index 0000000..613f39b --- /dev/null +++ b/leetcode_old/three_sum/playground.ipynb @@ -0,0 +1,73 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [-1, 0, 1, 2, -1, -4]\n", + "expected = [[-1, -1, 2], [-1, 0, 1]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "execute", + "metadata": {}, + "outputs": [], + "source": [ + "result = Solution().three_sum(nums)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "test", + "metadata": {}, + "outputs": [], + "source": [ + "# Sort for comparison since order doesn't matter\n", + "result_sorted = [sorted(triplet) for triplet in result]\n", + "expected_sorted = [sorted(triplet) for triplet in expected]\n", + "result_sorted.sort()\n", + "expected_sorted.sort()\n", + "assert result_sorted == expected_sorted" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/three_sum/solution.py b/leetcode_old/three_sum/solution.py similarity index 100% rename from leetcode/three_sum/solution.py rename to leetcode_old/three_sum/solution.py diff --git a/leetcode/three_sum/tests.py b/leetcode_old/three_sum/tests.py similarity index 100% rename from leetcode/three_sum/tests.py rename to leetcode_old/three_sum/tests.py diff --git a/leetcode/time_based_key_value_store/README.md b/leetcode_old/time_based_key_value_store/README.md similarity index 100% rename from leetcode/time_based_key_value_store/README.md rename to leetcode_old/time_based_key_value_store/README.md diff --git a/leetcode_old/time_based_key_value_store/__init__.py b/leetcode_old/time_based_key_value_store/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/time_based_key_value_store/playground.ipynb b/leetcode_old/time_based_key_value_store/playground.ipynb similarity index 100% rename from leetcode/time_based_key_value_store/playground.ipynb rename to leetcode_old/time_based_key_value_store/playground.ipynb diff --git a/leetcode/time_based_key_value_store/solution.py b/leetcode_old/time_based_key_value_store/solution.py similarity index 100% rename from leetcode/time_based_key_value_store/solution.py rename to leetcode_old/time_based_key_value_store/solution.py diff --git a/leetcode/time_based_key_value_store/tests.py b/leetcode_old/time_based_key_value_store/tests.py similarity index 100% rename from leetcode/time_based_key_value_store/tests.py rename to leetcode_old/time_based_key_value_store/tests.py diff --git a/leetcode/trapping_rain_water/README.md b/leetcode_old/trapping_rain_water/README.md similarity index 100% rename from leetcode/trapping_rain_water/README.md rename to leetcode_old/trapping_rain_water/README.md diff --git a/leetcode_old/trapping_rain_water/__init__.py b/leetcode_old/trapping_rain_water/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/trapping_rain_water/playground.ipynb b/leetcode_old/trapping_rain_water/playground.ipynb similarity index 100% rename from leetcode/trapping_rain_water/playground.ipynb rename to leetcode_old/trapping_rain_water/playground.ipynb diff --git a/leetcode/trapping_rain_water/solution.py b/leetcode_old/trapping_rain_water/solution.py similarity index 100% rename from leetcode/trapping_rain_water/solution.py rename to leetcode_old/trapping_rain_water/solution.py diff --git a/leetcode/trapping_rain_water/tests.py b/leetcode_old/trapping_rain_water/tests.py similarity index 100% rename from leetcode/trapping_rain_water/tests.py rename to leetcode_old/trapping_rain_water/tests.py diff --git a/leetcode/valid_anagram/README.md b/leetcode_old/valid_anagram/README.md similarity index 100% rename from leetcode/valid_anagram/README.md rename to leetcode_old/valid_anagram/README.md diff --git a/leetcode_old/valid_anagram/__init__.py b/leetcode_old/valid_anagram/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/valid_anagram/playground.ipynb b/leetcode_old/valid_anagram/playground.ipynb similarity index 100% rename from leetcode/valid_anagram/playground.ipynb rename to leetcode_old/valid_anagram/playground.ipynb diff --git a/leetcode/valid_anagram/solution.py b/leetcode_old/valid_anagram/solution.py similarity index 100% rename from leetcode/valid_anagram/solution.py rename to leetcode_old/valid_anagram/solution.py diff --git a/leetcode/valid_anagram/tests.py b/leetcode_old/valid_anagram/tests.py similarity index 100% rename from leetcode/valid_anagram/tests.py rename to leetcode_old/valid_anagram/tests.py diff --git a/leetcode/valid_palindrome/README.md b/leetcode_old/valid_palindrome/README.md similarity index 100% rename from leetcode/valid_palindrome/README.md rename to leetcode_old/valid_palindrome/README.md diff --git a/leetcode_old/valid_palindrome/__init__.py b/leetcode_old/valid_palindrome/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/valid_palindrome/playground.ipynb b/leetcode_old/valid_palindrome/playground.ipynb similarity index 100% rename from leetcode/valid_palindrome/playground.ipynb rename to leetcode_old/valid_palindrome/playground.ipynb diff --git a/leetcode/valid_palindrome/solution.py b/leetcode_old/valid_palindrome/solution.py similarity index 100% rename from leetcode/valid_palindrome/solution.py rename to leetcode_old/valid_palindrome/solution.py diff --git a/leetcode/valid_palindrome/tests.py b/leetcode_old/valid_palindrome/tests.py similarity index 100% rename from leetcode/valid_palindrome/tests.py rename to leetcode_old/valid_palindrome/tests.py diff --git a/leetcode_old/valid_parentheses/README.md b/leetcode_old/valid_parentheses/README.md new file mode 100644 index 0000000..05d24f0 --- /dev/null +++ b/leetcode_old/valid_parentheses/README.md @@ -0,0 +1,59 @@ +# Valid Parentheses + +**Difficulty:** Easy +**Topics:** String, Stack +**Tags:** grind-75 + +**LeetCode:** [Problem 20](https://leetcode.com/problems/valid-parentheses/description/) + +## Problem Description + +Given a string `s` containing just the characters `'('`, `')'`, `'{'`, `'}'`, `'['` and `']'`, determine if the input string is valid. + +An input string is valid if: + +1. Open brackets must be closed by the same type of brackets. +2. Open brackets must be closed in the correct order. +3. Every close bracket has a corresponding open bracket of the same type. + +## Examples + +### Example 1: + +``` +Input: s = "()" +Output: true +``` + +### Example 2: + +``` +Input: s = "()[]{}" +Output: true +``` + +### Example 3: + +``` +Input: s = "(]" +Output: false +``` + +### Example 4: + +``` +Input: s = "([])" +Output: true +``` + +### Example 5: + +``` +Input: s = "([)]" +Output: false +``` + +## Constraints + +- `1 <= s.length <= 10^4` +- `s` consists of parentheses only `'()[]{}'`. diff --git a/leetcode_old/valid_parentheses/__init__.py b/leetcode_old/valid_parentheses/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/valid_parentheses/playground.ipynb b/leetcode_old/valid_parentheses/playground.ipynb similarity index 95% rename from leetcode/valid_parentheses/playground.ipynb rename to leetcode_old/valid_parentheses/playground.ipynb index b5b4f34..e62b442 100644 --- a/leetcode/valid_parentheses/playground.ipynb +++ b/leetcode_old/valid_parentheses/playground.ipynb @@ -29,7 +29,8 @@ "metadata": {}, "outputs": [], "source": [ - "result = Solution().is_valid(s)\nresult" + "result = Solution().is_valid(s)\n", + "result" ] }, { diff --git a/leetcode_old/valid_parentheses/solution.py b/leetcode_old/valid_parentheses/solution.py new file mode 100644 index 0000000..779f734 --- /dev/null +++ b/leetcode_old/valid_parentheses/solution.py @@ -0,0 +1,14 @@ +class Solution: + # Time: O(n) + # Space: O(n) + def is_valid(self, s: str) -> bool: + stack = [] + pairs = {"(": ")", "[": "]", "{": "}"} + + for char in s: + if char in pairs: + stack.append(char) + elif not stack or pairs[stack.pop()] != char: + return False + + return not stack diff --git a/leetcode/valid_parentheses/tests.py b/leetcode_old/valid_parentheses/tests.py similarity index 100% rename from leetcode/valid_parentheses/tests.py rename to leetcode_old/valid_parentheses/tests.py diff --git a/leetcode/validate_binary_search_tree/README.md b/leetcode_old/validate_binary_search_tree/README.md similarity index 100% rename from leetcode/validate_binary_search_tree/README.md rename to leetcode_old/validate_binary_search_tree/README.md diff --git a/leetcode_old/validate_binary_search_tree/__init__.py b/leetcode_old/validate_binary_search_tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/validate_binary_search_tree/playground.ipynb b/leetcode_old/validate_binary_search_tree/playground.ipynb similarity index 100% rename from leetcode/validate_binary_search_tree/playground.ipynb rename to leetcode_old/validate_binary_search_tree/playground.ipynb diff --git a/leetcode/validate_binary_search_tree/solution.py b/leetcode_old/validate_binary_search_tree/solution.py similarity index 100% rename from leetcode/validate_binary_search_tree/solution.py rename to leetcode_old/validate_binary_search_tree/solution.py diff --git a/leetcode/validate_binary_search_tree/tests.py b/leetcode_old/validate_binary_search_tree/tests.py similarity index 100% rename from leetcode/validate_binary_search_tree/tests.py rename to leetcode_old/validate_binary_search_tree/tests.py diff --git a/leetcode/word_break/README.md b/leetcode_old/word_break/README.md similarity index 100% rename from leetcode/word_break/README.md rename to leetcode_old/word_break/README.md diff --git a/leetcode_old/word_break/__init__.py b/leetcode_old/word_break/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/word_break/playground.ipynb b/leetcode_old/word_break/playground.ipynb similarity index 100% rename from leetcode/word_break/playground.ipynb rename to leetcode_old/word_break/playground.ipynb diff --git a/leetcode/word_break/solution.py b/leetcode_old/word_break/solution.py similarity index 100% rename from leetcode/word_break/solution.py rename to leetcode_old/word_break/solution.py diff --git a/leetcode/word_break/tests.py b/leetcode_old/word_break/tests.py similarity index 100% rename from leetcode/word_break/tests.py rename to leetcode_old/word_break/tests.py diff --git a/leetcode/word_ladder/README.md b/leetcode_old/word_ladder/README.md similarity index 100% rename from leetcode/word_ladder/README.md rename to leetcode_old/word_ladder/README.md diff --git a/leetcode_old/word_ladder/__init__.py b/leetcode_old/word_ladder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/word_ladder/playground.ipynb b/leetcode_old/word_ladder/playground.ipynb similarity index 100% rename from leetcode/word_ladder/playground.ipynb rename to leetcode_old/word_ladder/playground.ipynb diff --git a/leetcode/word_ladder/solution.py b/leetcode_old/word_ladder/solution.py similarity index 100% rename from leetcode/word_ladder/solution.py rename to leetcode_old/word_ladder/solution.py diff --git a/leetcode/word_ladder/tests.py b/leetcode_old/word_ladder/tests.py similarity index 100% rename from leetcode/word_ladder/tests.py rename to leetcode_old/word_ladder/tests.py diff --git a/leetcode/zero_one_matrix/README.md b/leetcode_old/zero_one_matrix/README.md similarity index 100% rename from leetcode/zero_one_matrix/README.md rename to leetcode_old/zero_one_matrix/README.md diff --git a/leetcode_old/zero_one_matrix/__init__.py b/leetcode_old/zero_one_matrix/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/flood_fill/playground.ipynb b/leetcode_old/zero_one_matrix/playground.ipynb similarity index 73% rename from leetcode/flood_fill/playground.ipynb rename to leetcode_old/zero_one_matrix/playground.ipynb index 53f0ea6..317b713 100644 --- a/leetcode/flood_fill/playground.ipynb +++ b/leetcode_old/zero_one_matrix/playground.ipynb @@ -6,7 +6,9 @@ "id": "imports", "metadata": {}, "outputs": [], - "source": ["from solution import Solution"] + "source": [ + "from solution import Solution" + ] }, { "cell_type": "code", @@ -14,7 +16,11 @@ "id": "setup", "metadata": {}, "outputs": [], - "source": ["# Example test case\nimage = [[1, 1, 1], [1, 1, 0], [1, 0, 1]]\nsr = 1\nsc = 1\ncolor = 2\nexpected = [[2, 2, 2], [2, 2, 0], [2, 0, 1]]"] + "source": [ + "# Example test case\n", + "mat = [[0, 0, 0], [0, 1, 0], [1, 1, 1]]\n", + "expected = [[0, 0, 0], [0, 1, 0], [1, 2, 1]]" + ] }, { "cell_type": "code", @@ -22,7 +28,10 @@ "id": "execute", "metadata": {}, "outputs": [], - "source": ["result = Solution().flood_fill(image, sr, sc, color)\nresult"] + "source": [ + "result = Solution().update_matrix(mat)\n", + "result" + ] }, { "cell_type": "code", @@ -30,7 +39,9 @@ "id": "test", "metadata": {}, "outputs": [], - "source": ["assert result == expected"] + "source": [ + "assert result == expected" + ] } ], "metadata": { diff --git a/leetcode/zero_one_matrix/solution.py b/leetcode_old/zero_one_matrix/solution.py similarity index 100% rename from leetcode/zero_one_matrix/solution.py rename to leetcode_old/zero_one_matrix/solution.py diff --git a/leetcode/zero_one_matrix/tests.py b/leetcode_old/zero_one_matrix/tests.py similarity index 100% rename from leetcode/zero_one_matrix/tests.py rename to leetcode_old/zero_one_matrix/tests.py From 53c5affe19012b73384897e40750934132a7671a Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Wed, 10 Sep 2025 23:29:56 +0700 Subject: [PATCH 03/39] feat: update template new version --- ...01-cookiecutter-template-generalization.md | 306 ------------------ .../{examples => examples_old}/README.md | 0 .../{examples => examples_old}/basic.json5 | 0 .../{examples => examples_old}/design.json5 | 0 .../{json => json_old}/accounts_merge.json | 0 .../{json => json_old}/add_binary.json | 0 .../balanced_binary_tree.json | 0 .../{json => json_old}/basic_calculator.json | 0 .../best_time_to_buy_and_sell_stock.json | 0 .../{json => json_old}/binary_search.json | 0 .../binary_tree_level_order_traversal.json | 0 .../binary_tree_right_side_view.json | 0 .../{json => json_old}/climbing_stairs.json | 0 .../{json => json_old}/clone_graph.json | 0 .../{json => json_old}/coin_change.json | 0 .../{json => json_old}/combination_sum.json | 0 .../container_with_most_water.json | 0 .../contains_duplicate.json | 0 .../{json => json_old}/course_schedule.json | 0 .../diameter_of_binary_tree.json | 0 .../evaluate_reverse_polish_notation.json | 0 .../find_median_from_data_stream.json | 0 .../{json => json_old}/first_bad_version.json | 0 .../{json => json_old}/flood_fill.json | 0 .../implement_queue_using_stacks.json | 0 .../implement_trie_prefix_tree.json | 0 .../{json => json_old}/insert_interval.json | 0 .../invert_binary_tree.json | 0 .../k_closest_points_to_origin.json | 0 .../kth_smallest_element_in_a_bst.json | 0 .../largest_rectangle_in_histogram.json | 0 .../{json => json_old}/linked_list_cycle.json | 0 .../longest_palindrome.json | 0 .../longest_palindromic_substring.json | 0 ...ubstring_without_repeating_characters.json | 0 ...mmon_ancestor_of_a_binary_search_tree.json | 0 ...west_common_ancestor_of_a_binary_tree.json | 0 .../{json => json_old}/lru_cache.json | 0 .../{json => json_old}/majority_element.json | 0 .../maximum_depth_of_binary_tree.json | 0 .../maximum_profit_in_job_scheduling.json | 0 .../{json => json_old}/maximum_subarray.json | 0 .../{json => json_old}/merge_intervals.json | 0 .../merge_k_sorted_lists.json | 0 .../merge_two_sorted_lists.json | 0 .../middle_of_the_linked_list.json | 0 .../{json => json_old}/min_stack.json | 0 .../minimum_height_trees.json | 0 .../minimum_window_substring.json | 0 .../{json => json_old}/number_of_islands.json | 0 .../partition_equal_subset_sum.json | 0 .../{json => json_old}/permutations.json | 0 .../product_of_array_except_self.json | 0 .../{json => json_old}/ransom_note.json | 0 .../reverse_linked_list.json | 0 .../reverse_linked_list_ii.json | 0 .../{json => json_old}/rotting_oranges.json | 0 .../search_in_rotated_sorted_array.json | 0 ...serialize_and_deserialize_binary_tree.json | 0 .../{json => json_old}/sort_colors.json | 0 .../{json => json_old}/spiral_matrix.json | 0 .../string_to_integer_atoi.json | 0 .../{json => json_old}/task_scheduler.json | 0 .../{json => json_old}/three_sum.json | 0 .../time_based_key_value_store.json | 0 .../trapping_rain_water.json | 0 .../{json => json_old}/valid_anagram.json | 0 .../{json => json_old}/valid_palindrome.json | 0 .../{json => json_old}/valid_parentheses.json | 0 .../validate_binary_search_tree.json | 0 .../{json => json_old}/word_break.json | 0 .../{json => json_old}/word_ladder.json | 0 .../{json => json_old}/zero_one_matrix.json | 0 .../{{cookiecutter.problem_name}}/helpers.py | 21 ++ .../playground.ipynb | 11 +- .../{{cookiecutter.problem_name}}/solution.py | 35 +- .../test_solution.py | 35 ++ .../{{cookiecutter.problem_name}}/tests.py | 21 -- Makefile | 2 +- leetcode_ideal/clone_graph/test_solution.py | 39 +-- .../contains_duplicate/test_solution.py | 6 +- .../first_bad_version/test_solution.py | 2 +- .../test_solution.py | 5 +- .../invert_binary_tree/test_solution.py | 47 +-- .../linked_list_cycle/test_solution.py | 2 +- leetcode_ideal/lru_cache/test_solution.py | 31 +- .../reverse_linked_list/test_solution.py | 5 +- .../test_solution.py | 5 +- .../valid_parentheses/test_solution.py | 5 +- 89 files changed, 117 insertions(+), 461 deletions(-) delete mode 100644 .amazonq/plans/01-cookiecutter-template-generalization.md rename .templates/leetcode/{examples => examples_old}/README.md (100%) rename .templates/leetcode/{examples => examples_old}/basic.json5 (100%) rename .templates/leetcode/{examples => examples_old}/design.json5 (100%) rename .templates/leetcode/{json => json_old}/accounts_merge.json (100%) rename .templates/leetcode/{json => json_old}/add_binary.json (100%) rename .templates/leetcode/{json => json_old}/balanced_binary_tree.json (100%) rename .templates/leetcode/{json => json_old}/basic_calculator.json (100%) rename .templates/leetcode/{json => json_old}/best_time_to_buy_and_sell_stock.json (100%) rename .templates/leetcode/{json => json_old}/binary_search.json (100%) rename .templates/leetcode/{json => json_old}/binary_tree_level_order_traversal.json (100%) rename .templates/leetcode/{json => json_old}/binary_tree_right_side_view.json (100%) rename .templates/leetcode/{json => json_old}/climbing_stairs.json (100%) rename .templates/leetcode/{json => json_old}/clone_graph.json (100%) rename .templates/leetcode/{json => json_old}/coin_change.json (100%) rename .templates/leetcode/{json => json_old}/combination_sum.json (100%) rename .templates/leetcode/{json => json_old}/container_with_most_water.json (100%) rename .templates/leetcode/{json => json_old}/contains_duplicate.json (100%) rename .templates/leetcode/{json => json_old}/course_schedule.json (100%) rename .templates/leetcode/{json => json_old}/diameter_of_binary_tree.json (100%) rename .templates/leetcode/{json => json_old}/evaluate_reverse_polish_notation.json (100%) rename .templates/leetcode/{json => json_old}/find_median_from_data_stream.json (100%) rename .templates/leetcode/{json => json_old}/first_bad_version.json (100%) rename .templates/leetcode/{json => json_old}/flood_fill.json (100%) rename .templates/leetcode/{json => json_old}/implement_queue_using_stacks.json (100%) rename .templates/leetcode/{json => json_old}/implement_trie_prefix_tree.json (100%) rename .templates/leetcode/{json => json_old}/insert_interval.json (100%) rename .templates/leetcode/{json => json_old}/invert_binary_tree.json (100%) rename .templates/leetcode/{json => json_old}/k_closest_points_to_origin.json (100%) rename .templates/leetcode/{json => json_old}/kth_smallest_element_in_a_bst.json (100%) rename .templates/leetcode/{json => json_old}/largest_rectangle_in_histogram.json (100%) rename .templates/leetcode/{json => json_old}/linked_list_cycle.json (100%) rename .templates/leetcode/{json => json_old}/longest_palindrome.json (100%) rename .templates/leetcode/{json => json_old}/longest_palindromic_substring.json (100%) rename .templates/leetcode/{json => json_old}/longest_substring_without_repeating_characters.json (100%) rename .templates/leetcode/{json => json_old}/lowest_common_ancestor_of_a_binary_search_tree.json (100%) rename .templates/leetcode/{json => json_old}/lowest_common_ancestor_of_a_binary_tree.json (100%) rename .templates/leetcode/{json => json_old}/lru_cache.json (100%) rename .templates/leetcode/{json => json_old}/majority_element.json (100%) rename .templates/leetcode/{json => json_old}/maximum_depth_of_binary_tree.json (100%) rename .templates/leetcode/{json => json_old}/maximum_profit_in_job_scheduling.json (100%) rename .templates/leetcode/{json => json_old}/maximum_subarray.json (100%) rename .templates/leetcode/{json => json_old}/merge_intervals.json (100%) rename .templates/leetcode/{json => json_old}/merge_k_sorted_lists.json (100%) rename .templates/leetcode/{json => json_old}/merge_two_sorted_lists.json (100%) rename .templates/leetcode/{json => json_old}/middle_of_the_linked_list.json (100%) rename .templates/leetcode/{json => json_old}/min_stack.json (100%) rename .templates/leetcode/{json => json_old}/minimum_height_trees.json (100%) rename .templates/leetcode/{json => json_old}/minimum_window_substring.json (100%) rename .templates/leetcode/{json => json_old}/number_of_islands.json (100%) rename .templates/leetcode/{json => json_old}/partition_equal_subset_sum.json (100%) rename .templates/leetcode/{json => json_old}/permutations.json (100%) rename .templates/leetcode/{json => json_old}/product_of_array_except_self.json (100%) rename .templates/leetcode/{json => json_old}/ransom_note.json (100%) rename .templates/leetcode/{json => json_old}/reverse_linked_list.json (100%) rename .templates/leetcode/{json => json_old}/reverse_linked_list_ii.json (100%) rename .templates/leetcode/{json => json_old}/rotting_oranges.json (100%) rename .templates/leetcode/{json => json_old}/search_in_rotated_sorted_array.json (100%) rename .templates/leetcode/{json => json_old}/serialize_and_deserialize_binary_tree.json (100%) rename .templates/leetcode/{json => json_old}/sort_colors.json (100%) rename .templates/leetcode/{json => json_old}/spiral_matrix.json (100%) rename .templates/leetcode/{json => json_old}/string_to_integer_atoi.json (100%) rename .templates/leetcode/{json => json_old}/task_scheduler.json (100%) rename .templates/leetcode/{json => json_old}/three_sum.json (100%) rename .templates/leetcode/{json => json_old}/time_based_key_value_store.json (100%) rename .templates/leetcode/{json => json_old}/trapping_rain_water.json (100%) rename .templates/leetcode/{json => json_old}/valid_anagram.json (100%) rename .templates/leetcode/{json => json_old}/valid_palindrome.json (100%) rename .templates/leetcode/{json => json_old}/valid_parentheses.json (100%) rename .templates/leetcode/{json => json_old}/validate_binary_search_tree.json (100%) rename .templates/leetcode/{json => json_old}/word_break.json (100%) rename .templates/leetcode/{json => json_old}/word_ladder.json (100%) rename .templates/leetcode/{json => json_old}/zero_one_matrix.json (100%) create mode 100644 .templates/leetcode/{{cookiecutter.problem_name}}/helpers.py create mode 100644 .templates/leetcode/{{cookiecutter.problem_name}}/test_solution.py delete mode 100644 .templates/leetcode/{{cookiecutter.problem_name}}/tests.py diff --git a/.amazonq/plans/01-cookiecutter-template-generalization.md b/.amazonq/plans/01-cookiecutter-template-generalization.md deleted file mode 100644 index 379db5f..0000000 --- a/.amazonq/plans/01-cookiecutter-template-generalization.md +++ /dev/null @@ -1,306 +0,0 @@ -# Cookiecutter Template Generalization Plan - -## Current Issues Identified - -### 1. **Template Constraint Issues** - -- `first_bad_version/solution.py`: Has `# TODO: template constraint` - needs custom `__init__` method -- Template doesn't support constructor parameters or custom initialization logic -- No support for API mocking patterns (like `isBadVersion`) - -### 2. **Limited Method Flexibility** - -- No support for static methods, class methods, or property decorators -- Missing support for optional parameters with defaults -- No support for method overloading patterns -- Cannot handle methods that don't need `self` parameter - -### 3. **Complex Test Patterns Not Supported** - -- Design problems need operation sequences (LRU Cache pattern) -- Interactive problems need API mocking (First Bad Version) -- Some problems need custom helper methods in tests -- No support for test fixtures or complex setup - -### 4. **Playground Notebook Limitations** - -- Fixed 4-cell structure doesn't fit all problem types -- No support for visualization cells -- Missing support for interactive debugging -- Cannot handle complex setup requirements -- **Import conflicts**: Cannot import `tests.py` due to root-level `tests/` directory conflict -- **Missing test helpers**: Need access to test utility functions like `create_cycle_list` in notebooks -- **Path manipulation required**: Manual `sys.path` modifications needed for imports - -### 5. **Missing Template Variants** - -- No support for multiple solution approaches in one file -- Missing templates for specific problem categories (graph, trie, etc.) -- No support for helper classes or data structures -- Cannot generate algorithm explanation comments - -## Proposed Solutions - -### Phase 1: Template Structure Improvements - -#### 1.1 Enhanced Method Configuration - -```json -"solution_methods": [ - { - "name": "method_name", - "parameters": "param1: type1, param2: type2 = default", - "return_type": "ReturnType", - "dummy_return": "default_value", - "decorators": ["@staticmethod", "@classmethod", "@property"], - "is_constructor": true, - "time_complexity": "O(n)", - "space_complexity": "O(1)", - "algorithm_notes": "Brief explanation" - } -] -``` - -#### 1.2 Flexible Template Structure - -```jinja2 -{%- for method in solution_methods %} -{%- if method.decorators %} -{%- for decorator in method.decorators %} - {{ decorator }} -{%- endfor %} -{%- endif %} - # Time: {{ method.time_complexity or "O(?)" }} - # Space: {{ method.space_complexity or "O(?)" }} -{%- if method.is_constructor %} - def {{ method.name }}(self{% if method.parameters %}, {{ method.parameters }}{% endif %}){% if method.return_type %} -> {{ method.return_type }}{% endif %}: -{%- else %} - def {{ method.name }}({% if not method.decorators or "@staticmethod" not in method.decorators %}self{% if method.parameters %}, {% endif %}{% endif %}{{ method.parameters or "" }}){% if method.return_type %} -> {{ method.return_type }}{% endif %}: -{%- endif %} -{%- if method.algorithm_notes %} - # {{ method.algorithm_notes }} -{%- endif %} - # TODO: Implement {{ method.name }} -{%- if method.dummy_return %} - return {{ method.dummy_return }} -{%- endif %} -{%- endfor %} -``` - -### Phase 2: Test Template Enhancements - -#### 2.1 Flexible Test Patterns - -```json -"test_patterns": { - "type": "basic|design|interactive|tree|graph", - "setup_complexity": "simple|complex|custom", - "assertion_type": "direct|tree_comparison|operation_sequence" -} -``` - -#### 2.2 Enhanced Test Methods - -```json -"test_methods": [ - { - "name": "test_method", - "pattern": "parametrized|custom|fixture", - "setup_code": "custom setup if needed", - "parametrize": "params", - "test_cases": "test data", - "body": "test logic", - "cleanup_code": "optional cleanup" - } -] -``` - -### Phase 3: Notebook Template Flexibility - -#### 3.1 Dynamic Cell Structure - -```json -"notebook_cells": [ - { - "id": "imports", - "type": "code", - "content": "{{ playground_imports }}" - }, - { - "id": "setup", - "type": "code", - "content": "{{ playground_setup }}" - }, - { - "id": "visualization", - "type": "code", - "content": "{{ playground_visualization }}", - "optional": true - }, - { - "id": "execute", - "type": "code", - "content": "{{ playground_execution }}" - }, - { - "id": "test", - "type": "code", - "content": "{{ playground_assertion }}" - } -] -``` - -#### 3.2 Test Helper Integration - -```json -"notebook_config": { - "needs_test_helpers": true, - "helper_functions": ["create_cycle_list", "build_tree", "create_graph"], - "import_strategy": "helpers_file|inline_helpers|test_utils" -} -``` - -### Phase 4: Problem-Specific Templates - -#### 4.1 Template Categories - -- **Basic**: Array, string, number problems -- **Design**: Data structure implementation (LRU Cache, Trie) -- **Interactive**: Problems with external APIs (First Bad Version) -- **Tree**: Binary tree problems with visualization -- **Graph**: Graph problems with node structures -- **Algorithm**: Complex algorithms with step-by-step breakdown - -#### 4.2 Category-Specific Fields - -```json -"template_category": "design", -"design_config": { - "operation_methods": ["get", "put", "delete"], - "test_operation_sequence": true, - "supports_multiple_instances": true -}, -"interactive_config": { - "external_apis": ["isBadVersion"], - "mock_setup_required": true -}, -"tree_config": { - "visualization_enabled": true, - "supports_null_nodes": true -} -``` - -## Implementation Plan - -### Step 1: Backup and Analysis (Week 1) - -- [ ] Create backup of current template -- [ ] Analyze all 70+ existing problems for patterns -- [ ] Document current template limitations -- [ ] Create test cases for new template features - -### Step 2: Core Template Enhancement (Week 2) - -- [ ] Implement enhanced method configuration -- [ ] Add support for decorators and special methods -- [ ] Update solution.py template with flexibility -- [ ] Add algorithm complexity and notes support - -### Step 3: Test Template Improvements (Week 3) - -- [ ] Implement flexible test patterns -- [ ] Add support for design problem test sequences -- [ ] Add interactive problem mocking support -- [ ] Update tests.py template - -### Step 4: Notebook Template Enhancement (Week 4) - -- [ ] Implement dynamic cell structure -- [ ] Add visualization cell support -- [ ] Add debugging and exploration cells -- [ ] **Solve import conflicts**: Create `helpers.py` pattern for test utilities -- [ ] **Clean imports**: Remove need for manual `sys.path` manipulation -- [ ] **Test helper access**: Provide clean access to test utility functions -- [ ] Update playground.ipynb template - -### Step 5: Category-Specific Templates (Week 5) - -- [ ] Create template variants for each category -- [ ] Implement category-specific configuration -- [ ] Add template selection logic to generator -- [ ] Update JSON schema validation - -### Step 6: Migration and Testing (Week 6) - -- [ ] Test new template with existing problems -- [ ] Create migration script for existing JSONs -- [ ] Update documentation and examples -- [ ] Validate all generated problems pass linting - -## Success Criteria - -1. **Template Flexibility**: Support all current problem types without manual fixes -2. **Zero Manual Intervention**: Generated code should pass `make p-lint` immediately -3. **Backward Compatibility**: Existing JSON files should work with new template -4. **Enhanced Features**: Support for decorators, complex tests, and visualizations -5. **Category Support**: Automatic template selection based on problem type - -## Risk Mitigation - -1. **Breaking Changes**: Maintain backward compatibility with existing JSONs -2. **Complexity**: Keep template readable with clear documentation -3. **Performance**: Ensure generation time remains fast -4. **Maintenance**: Create comprehensive test suite for template validation - -## Files to Modify - -### Core Template Files - -- `.templates/leetcode/{{cookiecutter.problem_name}}/solution.py` -- `.templates/leetcode/{{cookiecutter.problem_name}}/tests.py` -- `.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb` -- **Add**: `.templates/leetcode/{{cookiecutter.problem_name}}/helpers.py` (conditional) -- `.templates/leetcode/cookiecutter.json` - -### Generator Logic - -- `leetcode_py/tools/generator.py` -- `.templates/leetcode/gen.py` - -### Examples and Documentation - -- `.templates/leetcode/examples/basic.json5` -- `.templates/leetcode/examples/design.json5` -- Add: `.templates/leetcode/examples/interactive.json5` -- Add: `.templates/leetcode/examples/tree.json5` -- Add: `.templates/leetcode/examples/graph.json5` - -### Validation and Testing - -- Add: `.templates/leetcode/validate_template.py` -- Add: `tests/templates/test_template_generation.py` - -## Notebook Import Issue Solutions - -### Option 1: Dedicated `helpers.py` File (Recommended) - -- Generate `helpers.py` when `needs_test_helpers: true` -- Extract reusable functions from test class -- Clean imports: `from helpers import create_cycle_list` -- No path manipulation needed - -### Option 2: Enhanced Test Utils - -- Add problem-specific helpers to `leetcode_py.test_utils` -- Import: `from leetcode_py.test_utils import create_cycle_list` -- Centralized but may become bloated - -### Option 3: Inline Helper Functions - -- Generate helper functions directly in notebook setup cell -- Self-contained but duplicates code -- Good for simple helpers - -**Chosen Approach**: Option 1 with conditional generation based on problem type. - -This plan addresses all identified template limitations while maintaining backward compatibility and adding powerful new features for different problem categories. diff --git a/.templates/leetcode/examples/README.md b/.templates/leetcode/examples_old/README.md similarity index 100% rename from .templates/leetcode/examples/README.md rename to .templates/leetcode/examples_old/README.md diff --git a/.templates/leetcode/examples/basic.json5 b/.templates/leetcode/examples_old/basic.json5 similarity index 100% rename from .templates/leetcode/examples/basic.json5 rename to .templates/leetcode/examples_old/basic.json5 diff --git a/.templates/leetcode/examples/design.json5 b/.templates/leetcode/examples_old/design.json5 similarity index 100% rename from .templates/leetcode/examples/design.json5 rename to .templates/leetcode/examples_old/design.json5 diff --git a/.templates/leetcode/json/accounts_merge.json b/.templates/leetcode/json_old/accounts_merge.json similarity index 100% rename from .templates/leetcode/json/accounts_merge.json rename to .templates/leetcode/json_old/accounts_merge.json diff --git a/.templates/leetcode/json/add_binary.json b/.templates/leetcode/json_old/add_binary.json similarity index 100% rename from .templates/leetcode/json/add_binary.json rename to .templates/leetcode/json_old/add_binary.json diff --git a/.templates/leetcode/json/balanced_binary_tree.json b/.templates/leetcode/json_old/balanced_binary_tree.json similarity index 100% rename from .templates/leetcode/json/balanced_binary_tree.json rename to .templates/leetcode/json_old/balanced_binary_tree.json diff --git a/.templates/leetcode/json/basic_calculator.json b/.templates/leetcode/json_old/basic_calculator.json similarity index 100% rename from .templates/leetcode/json/basic_calculator.json rename to .templates/leetcode/json_old/basic_calculator.json diff --git a/.templates/leetcode/json/best_time_to_buy_and_sell_stock.json b/.templates/leetcode/json_old/best_time_to_buy_and_sell_stock.json similarity index 100% rename from .templates/leetcode/json/best_time_to_buy_and_sell_stock.json rename to .templates/leetcode/json_old/best_time_to_buy_and_sell_stock.json diff --git a/.templates/leetcode/json/binary_search.json b/.templates/leetcode/json_old/binary_search.json similarity index 100% rename from .templates/leetcode/json/binary_search.json rename to .templates/leetcode/json_old/binary_search.json diff --git a/.templates/leetcode/json/binary_tree_level_order_traversal.json b/.templates/leetcode/json_old/binary_tree_level_order_traversal.json similarity index 100% rename from .templates/leetcode/json/binary_tree_level_order_traversal.json rename to .templates/leetcode/json_old/binary_tree_level_order_traversal.json diff --git a/.templates/leetcode/json/binary_tree_right_side_view.json b/.templates/leetcode/json_old/binary_tree_right_side_view.json similarity index 100% rename from .templates/leetcode/json/binary_tree_right_side_view.json rename to .templates/leetcode/json_old/binary_tree_right_side_view.json diff --git a/.templates/leetcode/json/climbing_stairs.json b/.templates/leetcode/json_old/climbing_stairs.json similarity index 100% rename from .templates/leetcode/json/climbing_stairs.json rename to .templates/leetcode/json_old/climbing_stairs.json diff --git a/.templates/leetcode/json/clone_graph.json b/.templates/leetcode/json_old/clone_graph.json similarity index 100% rename from .templates/leetcode/json/clone_graph.json rename to .templates/leetcode/json_old/clone_graph.json diff --git a/.templates/leetcode/json/coin_change.json b/.templates/leetcode/json_old/coin_change.json similarity index 100% rename from .templates/leetcode/json/coin_change.json rename to .templates/leetcode/json_old/coin_change.json diff --git a/.templates/leetcode/json/combination_sum.json b/.templates/leetcode/json_old/combination_sum.json similarity index 100% rename from .templates/leetcode/json/combination_sum.json rename to .templates/leetcode/json_old/combination_sum.json diff --git a/.templates/leetcode/json/container_with_most_water.json b/.templates/leetcode/json_old/container_with_most_water.json similarity index 100% rename from .templates/leetcode/json/container_with_most_water.json rename to .templates/leetcode/json_old/container_with_most_water.json diff --git a/.templates/leetcode/json/contains_duplicate.json b/.templates/leetcode/json_old/contains_duplicate.json similarity index 100% rename from .templates/leetcode/json/contains_duplicate.json rename to .templates/leetcode/json_old/contains_duplicate.json diff --git a/.templates/leetcode/json/course_schedule.json b/.templates/leetcode/json_old/course_schedule.json similarity index 100% rename from .templates/leetcode/json/course_schedule.json rename to .templates/leetcode/json_old/course_schedule.json diff --git a/.templates/leetcode/json/diameter_of_binary_tree.json b/.templates/leetcode/json_old/diameter_of_binary_tree.json similarity index 100% rename from .templates/leetcode/json/diameter_of_binary_tree.json rename to .templates/leetcode/json_old/diameter_of_binary_tree.json diff --git a/.templates/leetcode/json/evaluate_reverse_polish_notation.json b/.templates/leetcode/json_old/evaluate_reverse_polish_notation.json similarity index 100% rename from .templates/leetcode/json/evaluate_reverse_polish_notation.json rename to .templates/leetcode/json_old/evaluate_reverse_polish_notation.json diff --git a/.templates/leetcode/json/find_median_from_data_stream.json b/.templates/leetcode/json_old/find_median_from_data_stream.json similarity index 100% rename from .templates/leetcode/json/find_median_from_data_stream.json rename to .templates/leetcode/json_old/find_median_from_data_stream.json diff --git a/.templates/leetcode/json/first_bad_version.json b/.templates/leetcode/json_old/first_bad_version.json similarity index 100% rename from .templates/leetcode/json/first_bad_version.json rename to .templates/leetcode/json_old/first_bad_version.json diff --git a/.templates/leetcode/json/flood_fill.json b/.templates/leetcode/json_old/flood_fill.json similarity index 100% rename from .templates/leetcode/json/flood_fill.json rename to .templates/leetcode/json_old/flood_fill.json diff --git a/.templates/leetcode/json/implement_queue_using_stacks.json b/.templates/leetcode/json_old/implement_queue_using_stacks.json similarity index 100% rename from .templates/leetcode/json/implement_queue_using_stacks.json rename to .templates/leetcode/json_old/implement_queue_using_stacks.json diff --git a/.templates/leetcode/json/implement_trie_prefix_tree.json b/.templates/leetcode/json_old/implement_trie_prefix_tree.json similarity index 100% rename from .templates/leetcode/json/implement_trie_prefix_tree.json rename to .templates/leetcode/json_old/implement_trie_prefix_tree.json diff --git a/.templates/leetcode/json/insert_interval.json b/.templates/leetcode/json_old/insert_interval.json similarity index 100% rename from .templates/leetcode/json/insert_interval.json rename to .templates/leetcode/json_old/insert_interval.json diff --git a/.templates/leetcode/json/invert_binary_tree.json b/.templates/leetcode/json_old/invert_binary_tree.json similarity index 100% rename from .templates/leetcode/json/invert_binary_tree.json rename to .templates/leetcode/json_old/invert_binary_tree.json diff --git a/.templates/leetcode/json/k_closest_points_to_origin.json b/.templates/leetcode/json_old/k_closest_points_to_origin.json similarity index 100% rename from .templates/leetcode/json/k_closest_points_to_origin.json rename to .templates/leetcode/json_old/k_closest_points_to_origin.json diff --git a/.templates/leetcode/json/kth_smallest_element_in_a_bst.json b/.templates/leetcode/json_old/kth_smallest_element_in_a_bst.json similarity index 100% rename from .templates/leetcode/json/kth_smallest_element_in_a_bst.json rename to .templates/leetcode/json_old/kth_smallest_element_in_a_bst.json diff --git a/.templates/leetcode/json/largest_rectangle_in_histogram.json b/.templates/leetcode/json_old/largest_rectangle_in_histogram.json similarity index 100% rename from .templates/leetcode/json/largest_rectangle_in_histogram.json rename to .templates/leetcode/json_old/largest_rectangle_in_histogram.json diff --git a/.templates/leetcode/json/linked_list_cycle.json b/.templates/leetcode/json_old/linked_list_cycle.json similarity index 100% rename from .templates/leetcode/json/linked_list_cycle.json rename to .templates/leetcode/json_old/linked_list_cycle.json diff --git a/.templates/leetcode/json/longest_palindrome.json b/.templates/leetcode/json_old/longest_palindrome.json similarity index 100% rename from .templates/leetcode/json/longest_palindrome.json rename to .templates/leetcode/json_old/longest_palindrome.json diff --git a/.templates/leetcode/json/longest_palindromic_substring.json b/.templates/leetcode/json_old/longest_palindromic_substring.json similarity index 100% rename from .templates/leetcode/json/longest_palindromic_substring.json rename to .templates/leetcode/json_old/longest_palindromic_substring.json diff --git a/.templates/leetcode/json/longest_substring_without_repeating_characters.json b/.templates/leetcode/json_old/longest_substring_without_repeating_characters.json similarity index 100% rename from .templates/leetcode/json/longest_substring_without_repeating_characters.json rename to .templates/leetcode/json_old/longest_substring_without_repeating_characters.json diff --git a/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_search_tree.json b/.templates/leetcode/json_old/lowest_common_ancestor_of_a_binary_search_tree.json similarity index 100% rename from .templates/leetcode/json/lowest_common_ancestor_of_a_binary_search_tree.json rename to .templates/leetcode/json_old/lowest_common_ancestor_of_a_binary_search_tree.json diff --git a/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_tree.json b/.templates/leetcode/json_old/lowest_common_ancestor_of_a_binary_tree.json similarity index 100% rename from .templates/leetcode/json/lowest_common_ancestor_of_a_binary_tree.json rename to .templates/leetcode/json_old/lowest_common_ancestor_of_a_binary_tree.json diff --git a/.templates/leetcode/json/lru_cache.json b/.templates/leetcode/json_old/lru_cache.json similarity index 100% rename from .templates/leetcode/json/lru_cache.json rename to .templates/leetcode/json_old/lru_cache.json diff --git a/.templates/leetcode/json/majority_element.json b/.templates/leetcode/json_old/majority_element.json similarity index 100% rename from .templates/leetcode/json/majority_element.json rename to .templates/leetcode/json_old/majority_element.json diff --git a/.templates/leetcode/json/maximum_depth_of_binary_tree.json b/.templates/leetcode/json_old/maximum_depth_of_binary_tree.json similarity index 100% rename from .templates/leetcode/json/maximum_depth_of_binary_tree.json rename to .templates/leetcode/json_old/maximum_depth_of_binary_tree.json diff --git a/.templates/leetcode/json/maximum_profit_in_job_scheduling.json b/.templates/leetcode/json_old/maximum_profit_in_job_scheduling.json similarity index 100% rename from .templates/leetcode/json/maximum_profit_in_job_scheduling.json rename to .templates/leetcode/json_old/maximum_profit_in_job_scheduling.json diff --git a/.templates/leetcode/json/maximum_subarray.json b/.templates/leetcode/json_old/maximum_subarray.json similarity index 100% rename from .templates/leetcode/json/maximum_subarray.json rename to .templates/leetcode/json_old/maximum_subarray.json diff --git a/.templates/leetcode/json/merge_intervals.json b/.templates/leetcode/json_old/merge_intervals.json similarity index 100% rename from .templates/leetcode/json/merge_intervals.json rename to .templates/leetcode/json_old/merge_intervals.json diff --git a/.templates/leetcode/json/merge_k_sorted_lists.json b/.templates/leetcode/json_old/merge_k_sorted_lists.json similarity index 100% rename from .templates/leetcode/json/merge_k_sorted_lists.json rename to .templates/leetcode/json_old/merge_k_sorted_lists.json diff --git a/.templates/leetcode/json/merge_two_sorted_lists.json b/.templates/leetcode/json_old/merge_two_sorted_lists.json similarity index 100% rename from .templates/leetcode/json/merge_two_sorted_lists.json rename to .templates/leetcode/json_old/merge_two_sorted_lists.json diff --git a/.templates/leetcode/json/middle_of_the_linked_list.json b/.templates/leetcode/json_old/middle_of_the_linked_list.json similarity index 100% rename from .templates/leetcode/json/middle_of_the_linked_list.json rename to .templates/leetcode/json_old/middle_of_the_linked_list.json diff --git a/.templates/leetcode/json/min_stack.json b/.templates/leetcode/json_old/min_stack.json similarity index 100% rename from .templates/leetcode/json/min_stack.json rename to .templates/leetcode/json_old/min_stack.json diff --git a/.templates/leetcode/json/minimum_height_trees.json b/.templates/leetcode/json_old/minimum_height_trees.json similarity index 100% rename from .templates/leetcode/json/minimum_height_trees.json rename to .templates/leetcode/json_old/minimum_height_trees.json diff --git a/.templates/leetcode/json/minimum_window_substring.json b/.templates/leetcode/json_old/minimum_window_substring.json similarity index 100% rename from .templates/leetcode/json/minimum_window_substring.json rename to .templates/leetcode/json_old/minimum_window_substring.json diff --git a/.templates/leetcode/json/number_of_islands.json b/.templates/leetcode/json_old/number_of_islands.json similarity index 100% rename from .templates/leetcode/json/number_of_islands.json rename to .templates/leetcode/json_old/number_of_islands.json diff --git a/.templates/leetcode/json/partition_equal_subset_sum.json b/.templates/leetcode/json_old/partition_equal_subset_sum.json similarity index 100% rename from .templates/leetcode/json/partition_equal_subset_sum.json rename to .templates/leetcode/json_old/partition_equal_subset_sum.json diff --git a/.templates/leetcode/json/permutations.json b/.templates/leetcode/json_old/permutations.json similarity index 100% rename from .templates/leetcode/json/permutations.json rename to .templates/leetcode/json_old/permutations.json diff --git a/.templates/leetcode/json/product_of_array_except_self.json b/.templates/leetcode/json_old/product_of_array_except_self.json similarity index 100% rename from .templates/leetcode/json/product_of_array_except_self.json rename to .templates/leetcode/json_old/product_of_array_except_self.json diff --git a/.templates/leetcode/json/ransom_note.json b/.templates/leetcode/json_old/ransom_note.json similarity index 100% rename from .templates/leetcode/json/ransom_note.json rename to .templates/leetcode/json_old/ransom_note.json diff --git a/.templates/leetcode/json/reverse_linked_list.json b/.templates/leetcode/json_old/reverse_linked_list.json similarity index 100% rename from .templates/leetcode/json/reverse_linked_list.json rename to .templates/leetcode/json_old/reverse_linked_list.json diff --git a/.templates/leetcode/json/reverse_linked_list_ii.json b/.templates/leetcode/json_old/reverse_linked_list_ii.json similarity index 100% rename from .templates/leetcode/json/reverse_linked_list_ii.json rename to .templates/leetcode/json_old/reverse_linked_list_ii.json diff --git a/.templates/leetcode/json/rotting_oranges.json b/.templates/leetcode/json_old/rotting_oranges.json similarity index 100% rename from .templates/leetcode/json/rotting_oranges.json rename to .templates/leetcode/json_old/rotting_oranges.json diff --git a/.templates/leetcode/json/search_in_rotated_sorted_array.json b/.templates/leetcode/json_old/search_in_rotated_sorted_array.json similarity index 100% rename from .templates/leetcode/json/search_in_rotated_sorted_array.json rename to .templates/leetcode/json_old/search_in_rotated_sorted_array.json diff --git a/.templates/leetcode/json/serialize_and_deserialize_binary_tree.json b/.templates/leetcode/json_old/serialize_and_deserialize_binary_tree.json similarity index 100% rename from .templates/leetcode/json/serialize_and_deserialize_binary_tree.json rename to .templates/leetcode/json_old/serialize_and_deserialize_binary_tree.json diff --git a/.templates/leetcode/json/sort_colors.json b/.templates/leetcode/json_old/sort_colors.json similarity index 100% rename from .templates/leetcode/json/sort_colors.json rename to .templates/leetcode/json_old/sort_colors.json diff --git a/.templates/leetcode/json/spiral_matrix.json b/.templates/leetcode/json_old/spiral_matrix.json similarity index 100% rename from .templates/leetcode/json/spiral_matrix.json rename to .templates/leetcode/json_old/spiral_matrix.json diff --git a/.templates/leetcode/json/string_to_integer_atoi.json b/.templates/leetcode/json_old/string_to_integer_atoi.json similarity index 100% rename from .templates/leetcode/json/string_to_integer_atoi.json rename to .templates/leetcode/json_old/string_to_integer_atoi.json diff --git a/.templates/leetcode/json/task_scheduler.json b/.templates/leetcode/json_old/task_scheduler.json similarity index 100% rename from .templates/leetcode/json/task_scheduler.json rename to .templates/leetcode/json_old/task_scheduler.json diff --git a/.templates/leetcode/json/three_sum.json b/.templates/leetcode/json_old/three_sum.json similarity index 100% rename from .templates/leetcode/json/three_sum.json rename to .templates/leetcode/json_old/three_sum.json diff --git a/.templates/leetcode/json/time_based_key_value_store.json b/.templates/leetcode/json_old/time_based_key_value_store.json similarity index 100% rename from .templates/leetcode/json/time_based_key_value_store.json rename to .templates/leetcode/json_old/time_based_key_value_store.json diff --git a/.templates/leetcode/json/trapping_rain_water.json b/.templates/leetcode/json_old/trapping_rain_water.json similarity index 100% rename from .templates/leetcode/json/trapping_rain_water.json rename to .templates/leetcode/json_old/trapping_rain_water.json diff --git a/.templates/leetcode/json/valid_anagram.json b/.templates/leetcode/json_old/valid_anagram.json similarity index 100% rename from .templates/leetcode/json/valid_anagram.json rename to .templates/leetcode/json_old/valid_anagram.json diff --git a/.templates/leetcode/json/valid_palindrome.json b/.templates/leetcode/json_old/valid_palindrome.json similarity index 100% rename from .templates/leetcode/json/valid_palindrome.json rename to .templates/leetcode/json_old/valid_palindrome.json diff --git a/.templates/leetcode/json/valid_parentheses.json b/.templates/leetcode/json_old/valid_parentheses.json similarity index 100% rename from .templates/leetcode/json/valid_parentheses.json rename to .templates/leetcode/json_old/valid_parentheses.json diff --git a/.templates/leetcode/json/validate_binary_search_tree.json b/.templates/leetcode/json_old/validate_binary_search_tree.json similarity index 100% rename from .templates/leetcode/json/validate_binary_search_tree.json rename to .templates/leetcode/json_old/validate_binary_search_tree.json diff --git a/.templates/leetcode/json/word_break.json b/.templates/leetcode/json_old/word_break.json similarity index 100% rename from .templates/leetcode/json/word_break.json rename to .templates/leetcode/json_old/word_break.json diff --git a/.templates/leetcode/json/word_ladder.json b/.templates/leetcode/json_old/word_ladder.json similarity index 100% rename from .templates/leetcode/json/word_ladder.json rename to .templates/leetcode/json_old/word_ladder.json diff --git a/.templates/leetcode/json/zero_one_matrix.json b/.templates/leetcode/json_old/zero_one_matrix.json similarity index 100% rename from .templates/leetcode/json/zero_one_matrix.json rename to .templates/leetcode/json_old/zero_one_matrix.json diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/helpers.py b/.templates/leetcode/{{cookiecutter.problem_name}}/helpers.py new file mode 100644 index 0000000..2087e03 --- /dev/null +++ b/.templates/leetcode/{{cookiecutter.problem_name}}/helpers.py @@ -0,0 +1,21 @@ +{% if cookiecutter.helpers_imports -%} +{{cookiecutter.helpers_imports}} +{% endif -%} + + +{% if cookiecutter.helpers_content -%} +{{cookiecutter.helpers_content}} + + +{% endif -%} + +{% if cookiecutter.helpers_run_name -%} +def run_{{cookiecutter.helpers_run_name}}{{cookiecutter.helpers_run_signature}}: + {{cookiecutter.helpers_run_body}} + + +{% endif -%} +{% if cookiecutter.helpers_assert_name -%} +def assert_{{cookiecutter.helpers_assert_name}}{{cookiecutter.helpers_assert_signature}}: + {{cookiecutter.helpers_assert_body}} +{% endif -%} diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb b/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb index ac1863b..80c6881 100644 --- a/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb +++ b/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb @@ -14,23 +14,23 @@ "id": "setup", "metadata": {}, "outputs": [], - "source": ["{{ cookiecutter.playground_test_case | replace('\n', '\\n') }}"] + "source": ["{{ cookiecutter.playground_setup | replace('\n', '\\n') }}"] }, { "cell_type": "code", "execution_count": null, - "id": "execute", + "id": "run", "metadata": {}, "outputs": [], - "source": ["{{ cookiecutter.playground_execution | replace('\n', '\\n') }}"] + "source": ["{{ cookiecutter.playground_run | replace('\n', '\\n') }}"] }, { "cell_type": "code", "execution_count": null, - "id": "test", + "id": "assert", "metadata": {}, "outputs": [], - "source": ["{{ cookiecutter.playground_assertion | replace('\n', '\\n') }}"] + "source": ["{{ cookiecutter.playground_assert | replace('\n', '\\n') }}"] } ], "metadata": { @@ -48,7 +48,6 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", "version": "3.13.7" } }, diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py b/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py index a2014d3..03ab0c7 100644 --- a/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py +++ b/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py @@ -1,13 +1,30 @@ +{% if cookiecutter.solution_imports -%} {{cookiecutter.solution_imports}} -{# TODO: add helper class like class Node: .... #} + + +{% endif -%} +{% if cookiecutter.solution_contents -%} +{{cookiecutter.solution_contents}} +{% endif -%} +{% if cookiecutter.solution_class_name -%} + + class {{cookiecutter.solution_class_name}}: - {%- for _, methods in cookiecutter._solution_methods | dictsort %} - {%- for method in methods %} +{% if cookiecutter.solution_class_content -%} +{{cookiecutter.solution_class_content}} + +{% endif -%} +{% if cookiecutter.solution_methods -%} +{% for method in cookiecutter.solution_methods -%} # Time: O(?) - # Space: O(?){# TODO: add decorator // optional self. // optional return type // optional return #} - def {{method.name}}(self, {{method.parameters}}) -> {{method.return_type}}: - # TODO: Implement {{method.name}}{# TODO: add body #} - return {{method.dummy_return}} + # Space: O(?) +{% if method.decorator -%} + {{method.decorator}} +{% endif -%} + def {{method.name}}{{method.signature}}: + # TODO: Implement {{method.name}} + {{method.body}} - {%- endfor %} - {%- endfor %} +{% endfor -%} +{% endif -%} +{% endif -%} diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/test_solution.py b/.templates/leetcode/{{cookiecutter.problem_name}}/test_solution.py new file mode 100644 index 0000000..906272e --- /dev/null +++ b/.templates/leetcode/{{cookiecutter.problem_name}}/test_solution.py @@ -0,0 +1,35 @@ +{% if cookiecutter.test_imports -%} +{{cookiecutter.test_imports}} + + +{% endif -%} +{% if cookiecutter.test_content -%} +{{cookiecutter.test_content}} + + +{% endif -%} +{% if cookiecutter.test_class_name -%} +class Test{{cookiecutter.test_class_name}}: +{% if cookiecutter.test_class_content -%} + {{cookiecutter.test_class_content}} + +{% endif -%} +{% for method in cookiecutter.test_methods -%} +{% if method.decorator -%} + {{method.decorator}} +{% endif -%} +{% if method.test_decorator is defined -%} +{% if method.test_decorator -%} + {{method.test_decorator}} +{% endif -%} +{% else -%} + @logged_test +{% endif -%} +{% if method.parametrize -%} + @pytest.mark.parametrize("{{method.parametrize}}", {{method.test_cases}}) +{% endif -%} + def {{method.name}}{{method.signature}}: + {{method.body}} + +{% endfor -%} +{% endif -%} diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py b/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py deleted file mode 100644 index 014cc33..0000000 --- a/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py +++ /dev/null @@ -1,21 +0,0 @@ -{{cookiecutter.test_imports}} - - -class Test{{cookiecutter.test_class_name}}: - {%- for _, helper_methods in cookiecutter._test_helper_methods | dictsort %} - {%- for method in helper_methods %}{# TODO: add decorator // optional self. #} - def {{method.name}}(self{% if method.parameters %}, {{method.parameters}}{% endif %}): - {{method.body | indent(8, first=False)}} - - {%- endfor %} - {%- endfor %} - - {%- for _, test_methods in cookiecutter._test_methods | dictsort %} - {%- for method in test_methods %} - @pytest.mark.parametrize("{{method.parametrize}}", {{method.test_cases}}) - @logged_test - def {{method.name}}(self, {{method.parametrize_typed if method.parametrize_typed else method.parametrize}}): - {{method.body | indent(8, first=False)}} - - {%- endfor %} - {%- endfor %} diff --git a/Makefile b/Makefile index b32ae1c..eb15dbf 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VERSION = 3.13 -PROBLEM ?= partition_equal_subset_sum +PROBLEM ?= clone_graph FORCE ?= 0 COMMA := , diff --git a/leetcode_ideal/clone_graph/test_solution.py b/leetcode_ideal/clone_graph/test_solution.py index 30068c7..969fd08 100644 --- a/leetcode_ideal/clone_graph/test_solution.py +++ b/leetcode_ideal/clone_graph/test_solution.py @@ -3,41 +3,13 @@ from leetcode_py.test_utils import logged_test from .helpers import assert_clone_graph, create_graph, run_clone_graph +from .solution import Solution, SolutionBFS, SolutionDFS class TestCloneGraph: - @pytest.mark.parametrize( - "adj_list", - [ - [[2, 4], [1, 3], [2, 4], [1, 3]], - [[]], - [], - ], - ) - @logged_test - def test_clone_graph_recursive(self, adj_list: list[list[int]]): - from .solution import Solution - - result = run_clone_graph(Solution, adj_list) - expected = create_graph(adj_list) - assert_clone_graph(result, expected) - @pytest.mark.parametrize( - "adj_list", - [ - [[2, 4], [1, 3], [2, 4], [1, 3]], - [[]], - [], - ], - ) @logged_test - def test_clone_graph_dfs(self, adj_list: list[list[int]]): - from .solution import SolutionDFS - - result = run_clone_graph(SolutionDFS, adj_list) - expected = create_graph(adj_list) - assert_clone_graph(result, expected) - + @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS]) @pytest.mark.parametrize( "adj_list", [ @@ -46,10 +18,7 @@ def test_clone_graph_dfs(self, adj_list: list[list[int]]): [], ], ) - @logged_test - def test_clone_graph_bfs(self, adj_list: list[list[int]]): - from .solution import SolutionBFS - - result = run_clone_graph(SolutionBFS, adj_list) + def test_clone_graph(self, adj_list: list[list[int]], solution_class: type): + result = run_clone_graph(solution_class, adj_list) expected = create_graph(adj_list) assert_clone_graph(result, expected) diff --git a/leetcode_ideal/contains_duplicate/test_solution.py b/leetcode_ideal/contains_duplicate/test_solution.py index ceb3471..f2357a0 100644 --- a/leetcode_ideal/contains_duplicate/test_solution.py +++ b/leetcode_ideal/contains_duplicate/test_solution.py @@ -3,9 +3,12 @@ from leetcode_py.test_utils import logged_test from .helpers import assert_contains_duplicate, run_contains_duplicate +from .solution import Solution class TestContainsDuplicate: + + @logged_test @pytest.mark.parametrize( "nums, expected", [ @@ -23,9 +26,6 @@ class TestContainsDuplicate: ([1] * 1000, True), ], ) - @logged_test def test_contains_duplicate(self, nums: list[int], expected: bool): - from .solution import Solution - result = run_contains_duplicate(Solution, nums) assert_contains_duplicate(result, expected) diff --git a/leetcode_ideal/first_bad_version/test_solution.py b/leetcode_ideal/first_bad_version/test_solution.py index e97cf8c..ee779bd 100644 --- a/leetcode_ideal/first_bad_version/test_solution.py +++ b/leetcode_ideal/first_bad_version/test_solution.py @@ -8,10 +8,10 @@ class TestFirstBadVersion: + @logged_test @pytest.mark.parametrize( "n, bad, expected", [(5, 4, 4), (1, 1, 1), (3, 1, 1), (10, 7, 7), (5, -1, 1)] ) - @logged_test def test_first_bad_version(self, n: int, bad: int, expected: int): result = run_first_bad_version(Solution, n, bad) diff --git a/leetcode_ideal/implement_trie_prefix_tree/test_solution.py b/leetcode_ideal/implement_trie_prefix_tree/test_solution.py index 267b613..fabf0f1 100644 --- a/leetcode_ideal/implement_trie_prefix_tree/test_solution.py +++ b/leetcode_ideal/implement_trie_prefix_tree/test_solution.py @@ -3,9 +3,11 @@ from leetcode_py.test_utils import logged_test from .helpers import assert_trie, run_trie +from .solution import Trie class TestImplementTriePrefixTree: + @logged_test @pytest.mark.parametrize( "operations, inputs, expected", [ @@ -32,11 +34,8 @@ class TestImplementTriePrefixTree: (["Trie", "search", "starts_with"], [[], ["empty"], ["empty"]], [None, False, False]), ], ) - @logged_test def test_trie_operations( self, operations: list[str], inputs: list[list[str]], expected: list[bool | None] ): - from .solution import Trie - result, _ = run_trie(Trie, operations, inputs) assert_trie(result, expected) diff --git a/leetcode_ideal/invert_binary_tree/test_solution.py b/leetcode_ideal/invert_binary_tree/test_solution.py index b664517..f3b4bee 100644 --- a/leetcode_ideal/invert_binary_tree/test_solution.py +++ b/leetcode_ideal/invert_binary_tree/test_solution.py @@ -2,27 +2,13 @@ from leetcode_py.test_utils import logged_test -from .helpers import assert_invert_tree, run_invert_tree +from .helpers import assert_invert_tree, create_tree, run_invert_tree +from .solution import Solution, SolutionBFS, SolutionDFS class TestInvertBinaryTree: - @pytest.mark.parametrize( - "root_list, expected_list", - [ - ([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), - ([2, 1, 3], [2, 3, 1]), - ([], []), - ], - ) @logged_test - def test_invert_tree_recursive(self, root_list: list[int | None], expected_list: list[int | None]): - from .helpers import create_tree - from .solution import Solution - - result = run_invert_tree(Solution, root_list) - expected = create_tree(expected_list) - assert_invert_tree(result, expected) - + @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS]) @pytest.mark.parametrize( "root_list, expected_list", [ @@ -31,28 +17,9 @@ def test_invert_tree_recursive(self, root_list: list[int | None], expected_list: ([], []), ], ) - @logged_test - def test_invert_tree_dfs(self, root_list: list[int | None], expected_list: list[int | None]): - from .helpers import create_tree - from .solution import SolutionDFS - - result = run_invert_tree(SolutionDFS, root_list) - expected = create_tree(expected_list) - assert_invert_tree(result, expected) - - @pytest.mark.parametrize( - "root_list, expected_list", - [ - ([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), - ([2, 1, 3], [2, 3, 1]), - ([], []), - ], - ) - @logged_test - def test_invert_tree_bfs(self, root_list: list[int | None], expected_list: list[int | None]): - from .helpers import create_tree - from .solution import SolutionBFS - - result = run_invert_tree(SolutionBFS, root_list) + def test_invert_tree( + self, root_list: list[int | None], expected_list: list[int | None], solution_class: type + ): + result = run_invert_tree(solution_class, root_list) expected = create_tree(expected_list) assert_invert_tree(result, expected) diff --git a/leetcode_ideal/linked_list_cycle/test_solution.py b/leetcode_ideal/linked_list_cycle/test_solution.py index 652ebbc..e6c1b2a 100644 --- a/leetcode_ideal/linked_list_cycle/test_solution.py +++ b/leetcode_ideal/linked_list_cycle/test_solution.py @@ -8,6 +8,7 @@ class TestLinkedListCycle: + @logged_test @pytest.mark.parametrize( "values, pos, expected", [ @@ -31,7 +32,6 @@ class TestLinkedListCycle: ([100, 200, 300], 0, True), ], ) - @logged_test def test_has_cycle(self, values: list[int], pos: int, expected: bool): result = run_has_cycle(Solution, values, pos) diff --git a/leetcode_ideal/lru_cache/test_solution.py b/leetcode_ideal/lru_cache/test_solution.py index ed4cc3f..b0133f6 100644 --- a/leetcode_ideal/lru_cache/test_solution.py +++ b/leetcode_ideal/lru_cache/test_solution.py @@ -3,9 +3,12 @@ from leetcode_py.test_utils import logged_test from .helpers import assert_lru_cache, run_lru_cache +from .solution import LRUCache, LRUCacheWithDoublyList class TestLRUCache: + @logged_test + @pytest.mark.parametrize("solution_class", [LRUCache, LRUCacheWithDoublyList]) @pytest.mark.parametrize( "operations, inputs, expected", [ @@ -26,36 +29,12 @@ class TestLRUCache: ), ], ) - @logged_test def test_lru_cache( self, operations: list[str], inputs: list[list[int]], expected: list[int | None], + solution_class: type, ): - from .solution import LRUCache - - result, _ = run_lru_cache(LRUCache, operations, inputs) - assert_lru_cache(result, expected) - - @pytest.mark.parametrize( - "operations, inputs, expected", - [ - ( - ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"], - [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]], - [None, None, None, 1, None, -1, None, -1, 3, 4], - ), - ], - ) - @logged_test - def test_lru_cache_with_doubly_list( - self, - operations: list[str], - inputs: list[list[int]], - expected: list[int | None], - ): - from .solution import LRUCacheWithDoublyList - - result, _ = run_lru_cache(LRUCacheWithDoublyList, operations, inputs) + result, _ = run_lru_cache(solution_class, operations, inputs) assert_lru_cache(result, expected) diff --git a/leetcode_ideal/reverse_linked_list/test_solution.py b/leetcode_ideal/reverse_linked_list/test_solution.py index 3dd9237..d23dcdf 100644 --- a/leetcode_ideal/reverse_linked_list/test_solution.py +++ b/leetcode_ideal/reverse_linked_list/test_solution.py @@ -3,9 +3,11 @@ from leetcode_py.test_utils import logged_test from .helpers import assert_reverse_list, create_list, run_reverse_list +from .solution import Solution class TestReverseLinkedList: + @logged_test @pytest.mark.parametrize( "head_list, expected_list", [ @@ -21,10 +23,7 @@ class TestReverseLinkedList: ([1, 1, 1], [1, 1, 1]), ], ) - @logged_test def test_reverse_list(self, head_list: list[int], expected_list: list[int]): - from .solution import Solution - result = run_reverse_list(Solution, head_list) expected = create_list(expected_list) assert_reverse_list(result, expected) diff --git a/leetcode_ideal/serialize_and_deserialize_binary_tree/test_solution.py b/leetcode_ideal/serialize_and_deserialize_binary_tree/test_solution.py index 45c17ba..d92718b 100644 --- a/leetcode_ideal/serialize_and_deserialize_binary_tree/test_solution.py +++ b/leetcode_ideal/serialize_and_deserialize_binary_tree/test_solution.py @@ -3,9 +3,11 @@ from leetcode_py.test_utils import logged_test from .helpers import assert_serialize_deserialize, create_tree, run_serialize_deserialize +from .solution import Codec class TestSerializeAndDeserializeBinaryTree: + @logged_test @pytest.mark.parametrize( "root_list", [ @@ -34,10 +36,7 @@ class TestSerializeAndDeserializeBinaryTree: ([50, 30, 70, 20, 40, 60, 80, 10, 25, 35, 45]), ], ) - @logged_test def test_serialize_deserialize(self, root_list: list[int | None]): - from .solution import Codec - result = run_serialize_deserialize(Codec, root_list) expected = create_tree(root_list) assert_serialize_deserialize(result, expected) diff --git a/leetcode_ideal/valid_parentheses/test_solution.py b/leetcode_ideal/valid_parentheses/test_solution.py index a12d36b..4481f8f 100644 --- a/leetcode_ideal/valid_parentheses/test_solution.py +++ b/leetcode_ideal/valid_parentheses/test_solution.py @@ -3,9 +3,11 @@ from leetcode_py.test_utils import logged_test from .helpers import assert_is_valid, run_is_valid +from .solution import Solution class TestValidParentheses: + @logged_test @pytest.mark.parametrize( "s, expected", [ @@ -37,9 +39,6 @@ class TestValidParentheses: ("())", False), ], ) - @logged_test def test_is_valid(self, s: str, expected: bool): - from .solution import Solution - result = run_is_valid(Solution, s) assert_is_valid(result, expected) From fe56805b056835ceeee9d7e1bfbffa755d9db533 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 00:33:29 +0700 Subject: [PATCH 04/39] feat: complete first problem for new template --- .amazonq/plans/find_next_problem.py | 35 ++++ .../plans/migrate_problems_to_new_template.md | 193 ++++++++++++++++++ .templates/leetcode/cookiecutter.json | 36 ++-- .templates/leetcode/json/accounts_merge.json | 51 +++++ .templates/leetcode/json/test_simple.json | 48 +++++ .../{{cookiecutter.problem_name}}/helpers.py | 4 +- .../playground.ipynb | 8 +- .../{{cookiecutter.problem_name}}/solution.py | 17 +- .../test_solution.py | 26 +-- Makefile | 4 +- leetcode/accounts_merge/README.md | 41 ++++ leetcode/accounts_merge/__init__.py | 0 leetcode/accounts_merge/helpers.py | 11 + leetcode/accounts_merge/playground.ipynb | 102 +++++++++ leetcode/accounts_merge/solution.py | 35 ++++ leetcode/accounts_merge/test_solution.py | 50 +++++ 16 files changed, 620 insertions(+), 41 deletions(-) create mode 100644 .amazonq/plans/find_next_problem.py create mode 100644 .amazonq/plans/migrate_problems_to_new_template.md create mode 100644 .templates/leetcode/json/accounts_merge.json create mode 100644 .templates/leetcode/json/test_simple.json create mode 100644 leetcode/accounts_merge/README.md create mode 100644 leetcode/accounts_merge/__init__.py create mode 100644 leetcode/accounts_merge/helpers.py create mode 100644 leetcode/accounts_merge/playground.ipynb create mode 100644 leetcode/accounts_merge/solution.py create mode 100644 leetcode/accounts_merge/test_solution.py diff --git a/.amazonq/plans/find_next_problem.py b/.amazonq/plans/find_next_problem.py new file mode 100644 index 0000000..d91b37d --- /dev/null +++ b/.amazonq/plans/find_next_problem.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Find next problem to update from json_old to json.""" + +from pathlib import Path + + +def find_next_problem(): + """Return next problem from json_old that's not in json.""" + json_old_dir = Path(".templates/leetcode/json_old") + json_dir = Path(".templates/leetcode/json") + + if not json_old_dir.exists(): + return "json_old directory not found" + + if not json_dir.exists(): + return "json directory not found" + + # Get all problems in json_old + old_problems = {f.stem for f in json_old_dir.glob("*.json")} + + # Get all problems in json + new_problems = {f.stem for f in json_dir.glob("*.json")} + + # Find problems that need updating + missing_problems = old_problems - new_problems + + if not missing_problems: + return "All problems updated!" + + # Return first missing problem (sorted for consistency) + return sorted(missing_problems)[0] + + +if __name__ == "__main__": + print(find_next_problem()) diff --git a/.amazonq/plans/migrate_problems_to_new_template.md b/.amazonq/plans/migrate_problems_to_new_template.md new file mode 100644 index 0000000..3a18978 --- /dev/null +++ b/.amazonq/plans/migrate_problems_to_new_template.md @@ -0,0 +1,193 @@ +# Migrate Problems to New Template Format + +## Overview + +Migrate all problems from old JSON format (`.templates/leetcode/json_old/`) to new template format (`.templates/leetcode/json/`) one by one. + +## Prerequisites + +- New template system is ready in `.templates/leetcode/{{cookiecutter.problem_name}}/` +- Helper script exists: `.amazonq/plans/find_next_problem.py` + +## Process + +### Step 1: Find Next Problem + +```bash +python .amazonq/plans/find_next_problem.py +``` + +This returns the next problem to migrate (e.g., `accounts_merge`). + +### Step 2: Analyze Old JSON + +```bash +# Check the old format +cat .templates/leetcode/json_old/accounts_merge.json +``` + +Understand the structure and required variables. + +### Step 3: Create New JSON + +Create `.templates/leetcode/json/accounts_merge.json` with new template variables: + +**Required Variables:** + +- `problem_name` - Snake case name +- `solution_class_name` - Usually "Solution" +- `problem_number` - LeetCode number +- `problem_title` - Exact title +- `difficulty` - Easy/Medium/Hard +- `topics` - Comma-separated topics + +**Template-Specific Variables:** + +- `helpers_imports` - Imports for helpers.py +- `helpers_content` - Helper functions +- `helpers_run_name` - Main method name +- `helpers_run_signature` - Method signature +- `helpers_run_body` - Method implementation +- `helpers_assert_name` - Assert function name +- `helpers_assert_signature` - Assert signature +- `helpers_assert_body` - Assert implementation +- `solution_imports` - Imports for solution.py +- `solution_class_content` - Class-level content +- `solution_methods` - Array of method objects +- `test_imports` - Test imports +- `test_methods` - Array of test method objects +- `playground_imports` - Notebook imports +- `playground_setup` - Test case setup +- `playground_run` - Execution code +- `playground_assert` - Assertion code +- `readme_description` - Problem description +- `readme_constraints` - Constraints + +### Step 4: Generate and Test + +```bash +# Generate problem structure +make p-gen PROBLEM=accounts_merge + +# Lint to catch issues +make p-lint PROBLEM=accounts_merge + +# Fix any linting errors by updating JSON or generated files +``` + +### Step 5: Verify Generation + +Check generated files: + +- `leetcode/accounts_merge/README.md` +- `leetcode/accounts_merge/solution.py` +- `leetcode/accounts_merge/helpers.py` +- `leetcode/accounts_merge/test_solution.py` +- `leetcode/accounts_merge/playground.ipynb` + +### Step 6: Implement Solution + +```bash +# Copy implementation from old repository +# Find the solution in leetcode_old/ and implement in solution.py +# Replace TODO with actual working solution +``` + +### Step 7: Test Functionality + +```bash +# Run tests to ensure everything works +make p-test PROBLEM=accounts_merge +``` + +### Step 8: Repeat + +```bash +# Find next problem +python .amazonq/plans/find_next_problem.py +``` + +Repeat process until script returns "All problems updated!" + +## Migration Checklist + +For each problem: + +- [ ] Run `find_next_problem.py` to get next problem +- [ ] Analyze old JSON structure +- [ ] Create new JSON with all required variables +- [ ] Run `make p-gen PROBLEM=` +- [ ] Run `make p-lint PROBLEM=` and fix issues: + - If linting fails, fix the JSON template + - Re-run `make p-gen PROBLEM= FORCE=1` to regenerate + - Re-run `make p-lint PROBLEM=` to verify + - Iterate until linting passes to ensure reproducibility +- [ ] Implement solution from `leetcode_old/` repository +- [ ] Run `make p-test PROBLEM=` to verify tests pass +- [ ] Commit changes + +## Common Patterns + +**Basic Problem:** + +```json +{ + "problem_name": "two_sum", + "solution_class_name": "Solution", + "helpers_run_name": "two_sum", + "helpers_run_signature": "(solution_class: type, nums: list[int], target: int)", + "helpers_run_body": "return solution_class().two_sum(nums, target)", + "solution_methods": [ + { + "name": "two_sum", + "signature": "(self, nums: list[int], target: int) -> list[int]", + "body": "# TODO: Implement\nreturn []" + } + ] +} +``` + +**Tree Problem:** + +```json +{ + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "def create_tree(root_list: list[int | None]) -> TreeNode[int] | None:\n return TreeNode[int].from_list(root_list)", + "solution_imports": "from leetcode_py import TreeNode" +} +``` + +**Design Problem:** + +```json +{ + "solution_class_name": "LRUCache", + "solution_methods": [ + { + "name": "__init__", + "signature": "(self, capacity: int)", + "body": "# TODO: Initialize" + }, + { + "name": "get", + "signature": "(self, key: int) -> int", + "body": "# TODO: Implement\nreturn -1" + } + ] +} +``` + +## Progress Tracking + +Track progress by running the script periodically: + +```bash +# Check remaining problems +ls .templates/leetcode/json_old/ | wc -l # Total old problems +ls .templates/leetcode/json/ | wc -l # Migrated problems +python .amazonq/plans/find_next_problem.py # Next to migrate +``` + +## Completion + +When `find_next_problem.py` returns "All problems updated!", the migration is complete. diff --git a/.templates/leetcode/cookiecutter.json b/.templates/leetcode/cookiecutter.json index adc4fcb..4287ea1 100644 --- a/.templates/leetcode/cookiecutter.json +++ b/.templates/leetcode/cookiecutter.json @@ -18,20 +18,32 @@ "readme_constraints": "- 2 <= nums.length <= 10^4\n- -10^9 <= nums[i] <= 10^9\n- -10^9 <= target <= 10^9\n- Only one valid answer exists.", "readme_additional": "", + "helpers_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", + "helpers_content": "", + "helpers_run_name": "two_sum", + "helpers_run_signature": "(solution_class: type, nums: list[int], target: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.two_sum(nums, target)", + "helpers_assert_name": "two_sum", + "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_two_sum, run_two_sum\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "TwoSum", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", "_solution_methods": { "list": [ { "name": "two_sum", - "parameters": "nums: list[int], target: int", - "return_type": "list[int]", - "dummy_return": "[]" + "signature": "(self, nums: list[int], target: int) -> list[int]", + "body": " # TODO: Implement two_sum\n return []" } ] }, - - "test_imports": "import pytest\nfrom loguru import logger\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "TwoSum", "_test_helper_methods": { "list": [ { @@ -45,16 +57,16 @@ "list": [ { "name": "test_two_sum", + "signature": "(self, nums: list[int], target: int, expected: list[int])", "parametrize": "nums, target, expected", - "parametrize_typed": "nums: list[int], target: int, expected: list[int]", "test_cases": "[([2, 7, 11, 15], 9, [0, 1]), ([3, 2, 4], 6, [1, 2])]", - "body": "result = self.solution.two_sum(nums, target)\nassert result == expected" + "body": " result = run_two_sum(Solution, nums, target)\n assert_two_sum(result, expected)" } ] }, - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnums = [2, 7, 11, 15]\ntarget = 9\nexpected = [0, 1]", - "playground_execution": "result = Solution().two_sum(nums, target)\nresult", - "playground_assertion": "assert result == expected" + "playground_imports": "from helpers import run_two_sum, assert_two_sum\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [2, 7, 11, 15]\ntarget = 9\nexpected = [0, 1]", + "playground_run": "result = run_two_sum(Solution, nums, target)\nresult", + "playground_assert": "assert_two_sum(result, expected)" } diff --git a/.templates/leetcode/json/accounts_merge.json b/.templates/leetcode/json/accounts_merge.json new file mode 100644 index 0000000..f93e29f --- /dev/null +++ b/.templates/leetcode/json/accounts_merge.json @@ -0,0 +1,51 @@ +{ + "problem_name": "accounts_merge", + "solution_class_name": "Solution", + "problem_number": "721", + "problem_title": "Accounts Merge", + "difficulty": "Medium", + "topics": "Array, Hash Table, String, Depth-First Search, Breadth-First Search, Union Find, Sorting", + "tags": ["grind-75"], + "readme_description": "Given a list of `accounts` where each element `accounts[i]` is a list of strings, where the first element `accounts[i][0]` is a name, and the rest of the elements are **emails** representing emails of the account.\n\nNow, we would like to merge these accounts. Two accounts definitely belong to the same person if there is some common email to both accounts. Note that even if two accounts have the same name, they may belong to different people as people could have the same name. A person can have any number of accounts initially, but all of their accounts definitely have the same name.\n\nAfter merging the accounts, return the accounts in the following format: the first element of each account is the name, and the rest of the elements are emails **in sorted order**. The accounts themselves can be returned in **any order**.", + "readme_examples": [ + { + "content": "```\nInput: accounts = [[\"John\",\"johnsmith@mail.com\",\"john_newyork@mail.com\"],[\"John\",\"johnsmith@mail.com\",\"john00@mail.com\"],[\"Mary\",\"mary@mail.com\"],[\"John\",\"johnnybravo@mail.com\"]]\nOutput: [[\"John\",\"john00@mail.com\",\"john_newyork@mail.com\",\"johnsmith@mail.com\"],[\"Mary\",\"mary@mail.com\"],[\"John\",\"johnnybravo@mail.com\"]]\n```\n**Explanation:** The first and second John's are the same person as they have the common email \"johnsmith@mail.com\". The third John and Mary are different people as none of their email addresses are used by other accounts." + }, + { + "content": "```\nInput: accounts = [[\"Gabe\",\"Gabe0@m.co\",\"Gabe3@m.co\",\"Gabe1@m.co\"],[\"Kevin\",\"Kevin3@m.co\",\"Kevin5@m.co\",\"Kevin0@m.co\"],[\"Ethan\",\"Ethan5@m.co\",\"Ethan4@m.co\",\"Ethan0@m.co\"],[\"Hanzo\",\"Hanzo3@m.co\",\"Hanzo1@m.co\",\"Hanzo0@m.co\"],[\"Fern\",\"Fern5@m.co\",\"Fern1@m.co\",\"Fern0@m.co\"]]\nOutput: [[\"Ethan\",\"Ethan0@m.co\",\"Ethan4@m.co\",\"Ethan5@m.co\"],[\"Gabe\",\"Gabe0@m.co\",\"Gabe1@m.co\",\"Gabe3@m.co\"],[\"Hanzo\",\"Hanzo0@m.co\",\"Hanzo1@m.co\",\"Hanzo3@m.co\"],[\"Kevin\",\"Kevin0@m.co\",\"Kevin3@m.co\",\"Kevin5@m.co\"],[\"Fern\",\"Fern0@m.co\",\"Fern1@m.co\",\"Fern5@m.co\"]]\n```" + } + ], + "readme_constraints": "- `1 <= accounts.length <= 1000`\n- `2 <= accounts[i].length <= 10`\n- `1 <= accounts[i][j].length <= 30`\n- `accounts[i][0]` consists of English letters.\n- `accounts[i][j] (for j > 0)` is a valid email.", + "helpers_imports": "", + "helpers_run_name": "accounts_merge", + "helpers_run_signature": "(solution_class: type, accounts: list[list[str]])", + "helpers_run_body": " implementation = solution_class()\n return implementation.accounts_merge(accounts)", + "helpers_assert_name": "accounts_merge", + "helpers_assert_signature": "(result: list[list[str]], expected: list[list[str]]) -> bool", + "helpers_assert_body": " # Sort both result and expected for comparison since order doesn't matter\n result_sorted = [sorted(account) for account in sorted(result)]\n expected_sorted = [sorted(account) for account in sorted(expected)]\n assert result_sorted == expected_sorted\n return True", + "solution_methods": [ + { + "name": "accounts_merge", + "signature": "(self, accounts: list[list[str]]) -> list[list[str]]", + "body": " # TODO: Implement accounts_merge\n return []" + } + ], + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_accounts_merge, run_accounts_merge\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "AccountsMerge", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "test_helper_methods": [], + "test_methods": [ + { + "name": "test_accounts_merge", + "signature": "(self, accounts: list[list[str]], expected: list[list[str]])", + "parametrize": "accounts, expected", + "test_cases": "[([[\"John\", \"johnsmith@mail.com\", \"john_newyork@mail.com\"], [\"John\", \"johnsmith@mail.com\", \"john00@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]], [[\"John\", \"john00@mail.com\", \"john_newyork@mail.com\", \"johnsmith@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]), ([[\"Gabe\", \"Gabe0@m.co\", \"Gabe3@m.co\", \"Gabe1@m.co\"], [\"Kevin\", \"Kevin3@m.co\", \"Kevin5@m.co\", \"Kevin0@m.co\"], [\"Ethan\", \"Ethan5@m.co\", \"Ethan4@m.co\", \"Ethan0@m.co\"], [\"Hanzo\", \"Hanzo3@m.co\", \"Hanzo1@m.co\", \"Hanzo0@m.co\"], [\"Fern\", \"Fern5@m.co\", \"Fern1@m.co\", \"Fern0@m.co\"]], [[\"Ethan\", \"Ethan0@m.co\", \"Ethan4@m.co\", \"Ethan5@m.co\"], [\"Gabe\", \"Gabe0@m.co\", \"Gabe1@m.co\", \"Gabe3@m.co\"], [\"Hanzo\", \"Hanzo0@m.co\", \"Hanzo1@m.co\", \"Hanzo3@m.co\"], [\"Kevin\", \"Kevin0@m.co\", \"Kevin3@m.co\", \"Kevin5@m.co\"], [\"Fern\", \"Fern0@m.co\", \"Fern1@m.co\", \"Fern5@m.co\"]])]", + "body": " result = run_accounts_merge(Solution, accounts)\n assert_accounts_merge(result, expected)" + } + ], + "playground_imports": "from helpers import run_accounts_merge, assert_accounts_merge\nfrom solution import Solution", + "playground_setup": "# Example test case\naccounts = [[\"John\", \"johnsmith@mail.com\", \"john_newyork@mail.com\"], [\"John\", \"johnsmith@mail.com\", \"john00@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]\nexpected = [[\"John\", \"john00@mail.com\", \"john_newyork@mail.com\", \"johnsmith@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]", + "playground_run": "result = run_accounts_merge(Solution, accounts)\nresult", + "playground_assert": "assert_accounts_merge(result, expected)" +} diff --git a/.templates/leetcode/json/test_simple.json b/.templates/leetcode/json/test_simple.json new file mode 100644 index 0000000..9c96c62 --- /dev/null +++ b/.templates/leetcode/json/test_simple.json @@ -0,0 +1,48 @@ +{ + "problem_name": "test_simple", + "solution_class_name": "Solution", + "problem_number": "217", + "problem_title": "Contains Duplicate", + "difficulty": "Easy", + "topics": "Array, Hash Table, Sorting", + "tags": ["grind-75"], + "readme_description": "Given an integer array `nums`, return `true` if any value appears **at least twice** in the array, and return `false` if every element is distinct.", + "readme_examples": [ + { + "content": "```\nInput: nums = [1,2,3,1]\nOutput: true\n```\n**Explanation:** The element 1 occurs at the indices 0 and 3." + }, + { + "content": "```\nInput: nums = [1,2,3,4]\nOutput: false\n```\n**Explanation:** All elements are distinct." + }, + { "content": "```\nInput: nums = [1,1,1,3,3,4,3,2,4,2]\nOutput: true\n```" } + ], + "readme_constraints": "- 1 <= nums.length <= 10^5\n- -10^9 <= nums[i] <= 10^9", + "readme_additional": "", + "solution_imports": "", + "solution_methods": [ + { + "name": "contains_duplicate", + "parameters": "nums: list[int]", + "return_type": "bool", + "dummy_return": "False" + } + ], + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", + "test_class_name": "ContainsDuplicate", + "test_helper_methods": [ + { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } + ], + "test_methods": [ + { + "name": "test_contains_duplicate", + "parametrize": "nums, expected", + "parametrize_typed": "nums: list[int], expected: bool", + "test_cases": "[([1, 2, 3, 1], True), ([1, 2, 3, 4], False), ([1, 1, 1, 3, 3, 4, 3, 2, 4, 2], True)]", + "body": "result = self.solution.contains_duplicate(nums)\nassert result == expected" + } + ], + "playground_imports": "from solution import Solution", + "playground_test_case": "# Example test case\nnums = [1, 2, 3, 1]\nexpected = True", + "playground_execution": "result = Solution().contains_duplicate(nums)\nresult", + "playground_assertion": "assert result == expected" +} diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/helpers.py b/.templates/leetcode/{{cookiecutter.problem_name}}/helpers.py index 2087e03..5ac7695 100644 --- a/.templates/leetcode/{{cookiecutter.problem_name}}/helpers.py +++ b/.templates/leetcode/{{cookiecutter.problem_name}}/helpers.py @@ -11,11 +11,11 @@ {% if cookiecutter.helpers_run_name -%} def run_{{cookiecutter.helpers_run_name}}{{cookiecutter.helpers_run_signature}}: - {{cookiecutter.helpers_run_body}} +{{cookiecutter.helpers_run_body}} {% endif -%} {% if cookiecutter.helpers_assert_name -%} def assert_{{cookiecutter.helpers_assert_name}}{{cookiecutter.helpers_assert_signature}}: - {{cookiecutter.helpers_assert_body}} +{{cookiecutter.helpers_assert_body}} {% endif -%} diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb b/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb index 80c6881..074410c 100644 --- a/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb +++ b/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb @@ -6,7 +6,7 @@ "id": "imports", "metadata": {}, "outputs": [], - "source": ["{{ cookiecutter.playground_imports | replace('\n', '\\n') }}"] + "source": ["{{ cookiecutter.playground_imports | replace('\n', '\\n') | replace('"', '\\"') }}"] }, { "cell_type": "code", @@ -14,7 +14,7 @@ "id": "setup", "metadata": {}, "outputs": [], - "source": ["{{ cookiecutter.playground_setup | replace('\n', '\\n') }}"] + "source": ["{{ cookiecutter.playground_setup | replace('\n', '\\n') | replace('"', '\\"') }}"] }, { "cell_type": "code", @@ -22,7 +22,7 @@ "id": "run", "metadata": {}, "outputs": [], - "source": ["{{ cookiecutter.playground_run | replace('\n', '\\n') }}"] + "source": ["{{ cookiecutter.playground_run | replace('\n', '\\n') | replace('"', '\\"') }}"] }, { "cell_type": "code", @@ -30,7 +30,7 @@ "id": "assert", "metadata": {}, "outputs": [], - "source": ["{{ cookiecutter.playground_assert | replace('\n', '\\n') }}"] + "source": ["{{ cookiecutter.playground_assert | replace('\n', '\\n') | replace('"', '\\"') }}"] } ], "metadata": { diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py b/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py index 03ab0c7..fbc266c 100644 --- a/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py +++ b/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py @@ -14,17 +14,16 @@ class {{cookiecutter.solution_class_name}}: {{cookiecutter.solution_class_content}} {% endif -%} -{% if cookiecutter.solution_methods -%} -{% for method in cookiecutter.solution_methods -%} +{% if cookiecutter._solution_methods -%} +{% for _, methods in cookiecutter._solution_methods | dictsort %} +{% for method in methods %} # Time: O(?) # Space: O(?) -{% if method.decorator -%} - {{method.decorator}} -{% endif -%} - def {{method.name}}{{method.signature}}: - # TODO: Implement {{method.name}} - {{method.body}} +{% if method.decorator is defined %} {{method.decorator}} +{% endif %} def {{method.name}}{{method.signature}}: +{{method.body}} -{% endfor -%} +{% endfor %} +{% endfor %} {% endif -%} {% endif -%} diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/test_solution.py b/.templates/leetcode/{{cookiecutter.problem_name}}/test_solution.py index 906272e..11fc019 100644 --- a/.templates/leetcode/{{cookiecutter.problem_name}}/test_solution.py +++ b/.templates/leetcode/{{cookiecutter.problem_name}}/test_solution.py @@ -14,22 +14,24 @@ class Test{{cookiecutter.test_class_name}}: {{cookiecutter.test_class_content}} {% endif -%} -{% for method in cookiecutter.test_methods -%} -{% if method.decorator -%} +{% for _, methods in cookiecutter._test_methods | dictsort %} +{% for method in methods %} +{% if method.decorator is defined %} {{method.decorator}} -{% endif -%} -{% if method.test_decorator is defined -%} -{% if method.test_decorator -%} +{% endif %} +{% if method.test_decorator is defined %} +{% if method.test_decorator %} {{method.test_decorator}} -{% endif -%} -{% else -%} +{% endif %} +{% else %} @logged_test -{% endif -%} -{% if method.parametrize -%} +{% endif %} +{% if method.parametrize %} @pytest.mark.parametrize("{{method.parametrize}}", {{method.test_cases}}) -{% endif -%} +{% endif %} def {{method.name}}{{method.signature}}: - {{method.body}} +{{method.body}} -{% endfor -%} +{% endfor %} +{% endfor %} {% endif -%} diff --git a/Makefile b/Makefile index eb15dbf..4d16584 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VERSION = 3.13 -PROBLEM ?= clone_graph +PROBLEM ?= accounts_merge FORCE ?= 0 COMMA := , @@ -61,7 +61,7 @@ p-test: echo "Error: Problem '$(PROBLEM)' not found in leetcode/ directory"; \ exit 1; \ fi - poetry run pytest leetcode/$(PROBLEM)/tests.py -v -s + poetry run pytest leetcode/$(PROBLEM)/test_solution.py -v -s p-lint: @echo "Linting problem: $(PROBLEM)" diff --git a/leetcode/accounts_merge/README.md b/leetcode/accounts_merge/README.md new file mode 100644 index 0000000..53a64a3 --- /dev/null +++ b/leetcode/accounts_merge/README.md @@ -0,0 +1,41 @@ +# Accounts Merge + +**Difficulty:** Medium +**Topics:** Array, Hash Table, String, Depth-First Search, Breadth-First Search, Union Find, Sorting +**Tags:** grind-75 + +**LeetCode:** [Problem 721](https://leetcode.com/problems/accounts-merge/description/) + +## Problem Description + +Given a list of `accounts` where each element `accounts[i]` is a list of strings, where the first element `accounts[i][0]` is a name, and the rest of the elements are **emails** representing emails of the account. + +Now, we would like to merge these accounts. Two accounts definitely belong to the same person if there is some common email to both accounts. Note that even if two accounts have the same name, they may belong to different people as people could have the same name. A person can have any number of accounts initially, but all of their accounts definitely have the same name. + +After merging the accounts, return the accounts in the following format: the first element of each account is the name, and the rest of the elements are emails **in sorted order**. The accounts themselves can be returned in **any order**. + +## Examples + +### Example 1: + +``` +Input: accounts = [["John","johnsmith@mail.com","john_newyork@mail.com"],["John","johnsmith@mail.com","john00@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]] +Output: [["John","john00@mail.com","john_newyork@mail.com","johnsmith@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]] +``` + +**Explanation:** The first and second John's are the same person as they have the common email "johnsmith@mail.com". The third John and Mary are different people as none of their email addresses are used by other accounts. + +### Example 2: + +``` +Input: accounts = [["Gabe","Gabe0@m.co","Gabe3@m.co","Gabe1@m.co"],["Kevin","Kevin3@m.co","Kevin5@m.co","Kevin0@m.co"],["Ethan","Ethan5@m.co","Ethan4@m.co","Ethan0@m.co"],["Hanzo","Hanzo3@m.co","Hanzo1@m.co","Hanzo0@m.co"],["Fern","Fern5@m.co","Fern1@m.co","Fern0@m.co"]] +Output: [["Ethan","Ethan0@m.co","Ethan4@m.co","Ethan5@m.co"],["Gabe","Gabe0@m.co","Gabe1@m.co","Gabe3@m.co"],["Hanzo","Hanzo0@m.co","Hanzo1@m.co","Hanzo3@m.co"],["Kevin","Kevin0@m.co","Kevin3@m.co","Kevin5@m.co"],["Fern","Fern0@m.co","Fern1@m.co","Fern5@m.co"]] +``` + +## Constraints + +- `1 <= accounts.length <= 1000` +- `2 <= accounts[i].length <= 10` +- `1 <= accounts[i][j].length <= 30` +- `accounts[i][0]` consists of English letters. +- `accounts[i][j] (for j > 0)` is a valid email. diff --git a/leetcode/accounts_merge/__init__.py b/leetcode/accounts_merge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/accounts_merge/helpers.py b/leetcode/accounts_merge/helpers.py new file mode 100644 index 0000000..e163f17 --- /dev/null +++ b/leetcode/accounts_merge/helpers.py @@ -0,0 +1,11 @@ +def run_accounts_merge(solution_class: type, accounts: list[list[str]]): + implementation = solution_class() + return implementation.accounts_merge(accounts) + + +def assert_accounts_merge(result: list[list[str]], expected: list[list[str]]) -> bool: + # Sort both result and expected for comparison since order doesn't matter + result_sorted = [sorted(account) for account in sorted(result)] + expected_sorted = [sorted(account) for account in sorted(expected)] + assert result_sorted == expected_sorted + return True diff --git a/leetcode/accounts_merge/playground.ipynb b/leetcode/accounts_merge/playground.ipynb new file mode 100644 index 0000000..f0dcd43 --- /dev/null +++ b/leetcode/accounts_merge/playground.ipynb @@ -0,0 +1,102 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_accounts_merge, run_accounts_merge\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "accounts = [\n", + " [\"John\", \"johnsmith@mail.com\", \"john_newyork@mail.com\"],\n", + " [\"John\", \"johnsmith@mail.com\", \"john00@mail.com\"],\n", + " [\"Mary\", \"mary@mail.com\"],\n", + " [\"John\", \"johnnybravo@mail.com\"],\n", + "]\n", + "expected = [\n", + " [\"John\", \"john00@mail.com\", \"john_newyork@mail.com\", \"johnsmith@mail.com\"],\n", + " [\"Mary\", \"mary@mail.com\"],\n", + " [\"John\", \"johnnybravo@mail.com\"],\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "run", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[['John', 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com'],\n", + " ['Mary', 'mary@mail.com'],\n", + " ['John', 'johnnybravo@mail.com']]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_accounts_merge(Solution, accounts)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "assert", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_accounts_merge(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/accounts_merge/solution.py b/leetcode/accounts_merge/solution.py new file mode 100644 index 0000000..b971177 --- /dev/null +++ b/leetcode/accounts_merge/solution.py @@ -0,0 +1,35 @@ +class Solution: + + # Time: O(N * M) where N is accounts, M is max emails per account + # Space: O(N * M) + def accounts_merge(self, accounts: list[list[str]]) -> list[list[str]]: + email_to_accounts: dict[str, list[int]] = {} + + for i, account in enumerate(accounts): + for email in account[1:]: + if email not in email_to_accounts: + email_to_accounts[email] = [] + email_to_accounts[email].append(i) + + visited: set[int] = set() + result = [] + + def dfs(account_idx: int, emails: set[str]) -> None: + if account_idx in visited: + return + visited.add(account_idx) + + for email in accounts[account_idx][1:]: + emails.add(email) + for neighbor_idx in email_to_accounts[email]: + dfs(neighbor_idx, emails) + + for i in range(len(accounts)): + if i in visited: + continue + + emails: set[str] = set() + dfs(i, emails) + result.append([accounts[i][0]] + sorted(emails)) + + return result diff --git a/leetcode/accounts_merge/test_solution.py b/leetcode/accounts_merge/test_solution.py new file mode 100644 index 0000000..89cc9cb --- /dev/null +++ b/leetcode/accounts_merge/test_solution.py @@ -0,0 +1,50 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_accounts_merge, run_accounts_merge +from .solution import Solution + + +class TestAccountsMerge: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "accounts, expected", + [ + ( + [ + ["John", "johnsmith@mail.com", "john_newyork@mail.com"], + ["John", "johnsmith@mail.com", "john00@mail.com"], + ["Mary", "mary@mail.com"], + ["John", "johnnybravo@mail.com"], + ], + [ + ["John", "john00@mail.com", "john_newyork@mail.com", "johnsmith@mail.com"], + ["Mary", "mary@mail.com"], + ["John", "johnnybravo@mail.com"], + ], + ), + ( + [ + ["Gabe", "Gabe0@m.co", "Gabe3@m.co", "Gabe1@m.co"], + ["Kevin", "Kevin3@m.co", "Kevin5@m.co", "Kevin0@m.co"], + ["Ethan", "Ethan5@m.co", "Ethan4@m.co", "Ethan0@m.co"], + ["Hanzo", "Hanzo3@m.co", "Hanzo1@m.co", "Hanzo0@m.co"], + ["Fern", "Fern5@m.co", "Fern1@m.co", "Fern0@m.co"], + ], + [ + ["Ethan", "Ethan0@m.co", "Ethan4@m.co", "Ethan5@m.co"], + ["Gabe", "Gabe0@m.co", "Gabe1@m.co", "Gabe3@m.co"], + ["Hanzo", "Hanzo0@m.co", "Hanzo1@m.co", "Hanzo3@m.co"], + ["Kevin", "Kevin0@m.co", "Kevin3@m.co", "Kevin5@m.co"], + ["Fern", "Fern0@m.co", "Fern1@m.co", "Fern5@m.co"], + ], + ), + ], + ) + def test_accounts_merge(self, accounts: list[list[str]], expected: list[list[str]]): + result = run_accounts_merge(Solution, accounts) + assert_accounts_merge(result, expected) From 348af7e79b2b4310aeae896762f1bf4686a45180 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 07:18:59 +0700 Subject: [PATCH 05/39] feat: complete accounts_merge --- .templates/leetcode/json/accounts_merge.json | 2 +- .templates/leetcode/json/test_simple.json | 48 -------------------- leetcode/accounts_merge/test_solution.py | 23 ++++++++++ 3 files changed, 24 insertions(+), 49 deletions(-) delete mode 100644 .templates/leetcode/json/test_simple.json diff --git a/.templates/leetcode/json/accounts_merge.json b/.templates/leetcode/json/accounts_merge.json index f93e29f..436e531 100644 --- a/.templates/leetcode/json/accounts_merge.json +++ b/.templates/leetcode/json/accounts_merge.json @@ -40,7 +40,7 @@ "name": "test_accounts_merge", "signature": "(self, accounts: list[list[str]], expected: list[list[str]])", "parametrize": "accounts, expected", - "test_cases": "[([[\"John\", \"johnsmith@mail.com\", \"john_newyork@mail.com\"], [\"John\", \"johnsmith@mail.com\", \"john00@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]], [[\"John\", \"john00@mail.com\", \"john_newyork@mail.com\", \"johnsmith@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]), ([[\"Gabe\", \"Gabe0@m.co\", \"Gabe3@m.co\", \"Gabe1@m.co\"], [\"Kevin\", \"Kevin3@m.co\", \"Kevin5@m.co\", \"Kevin0@m.co\"], [\"Ethan\", \"Ethan5@m.co\", \"Ethan4@m.co\", \"Ethan0@m.co\"], [\"Hanzo\", \"Hanzo3@m.co\", \"Hanzo1@m.co\", \"Hanzo0@m.co\"], [\"Fern\", \"Fern5@m.co\", \"Fern1@m.co\", \"Fern0@m.co\"]], [[\"Ethan\", \"Ethan0@m.co\", \"Ethan4@m.co\", \"Ethan5@m.co\"], [\"Gabe\", \"Gabe0@m.co\", \"Gabe1@m.co\", \"Gabe3@m.co\"], [\"Hanzo\", \"Hanzo0@m.co\", \"Hanzo1@m.co\", \"Hanzo3@m.co\"], [\"Kevin\", \"Kevin0@m.co\", \"Kevin3@m.co\", \"Kevin5@m.co\"], [\"Fern\", \"Fern0@m.co\", \"Fern1@m.co\", \"Fern5@m.co\"]])]", + "test_cases": "[([[\"John\", \"johnsmith@mail.com\", \"john_newyork@mail.com\"], [\"John\", \"johnsmith@mail.com\", \"john00@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]], [[\"John\", \"john00@mail.com\", \"john_newyork@mail.com\", \"johnsmith@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]), ([[\"Gabe\", \"Gabe0@m.co\", \"Gabe3@m.co\", \"Gabe1@m.co\"], [\"Kevin\", \"Kevin3@m.co\", \"Kevin5@m.co\", \"Kevin0@m.co\"], [\"Ethan\", \"Ethan5@m.co\", \"Ethan4@m.co\", \"Ethan0@m.co\"], [\"Hanzo\", \"Hanzo3@m.co\", \"Hanzo1@m.co\", \"Hanzo0@m.co\"], [\"Fern\", \"Fern5@m.co\", \"Fern1@m.co\", \"Fern0@m.co\"]], [[\"Ethan\", \"Ethan0@m.co\", \"Ethan4@m.co\", \"Ethan5@m.co\"], [\"Gabe\", \"Gabe0@m.co\", \"Gabe1@m.co\", \"Gabe3@m.co\"], [\"Hanzo\", \"Hanzo0@m.co\", \"Hanzo1@m.co\", \"Hanzo3@m.co\"], [\"Kevin\", \"Kevin0@m.co\", \"Kevin3@m.co\", \"Kevin5@m.co\"], [\"Fern\", \"Fern0@m.co\", \"Fern1@m.co\", \"Fern5@m.co\"]]), ([[\"John\", \"john@mail.com\"]], [[\"John\", \"john@mail.com\"]]), ([[\"John\"]], [[\"John\"]]), ([[\"John\", \"john1@mail.com\"], [\"John\", \"john2@mail.com\"], [\"John\", \"john3@mail.com\"]], [[\"John\", \"john1@mail.com\"], [\"John\", \"john2@mail.com\"], [\"John\", \"john3@mail.com\"]]), ([[\"John\", \"a@mail.com\", \"b@mail.com\"], [\"John\", \"b@mail.com\", \"c@mail.com\"], [\"John\", \"d@mail.com\"]], [[\"John\", \"a@mail.com\", \"b@mail.com\", \"c@mail.com\"], [\"John\", \"d@mail.com\"]]), ([[\"Alice\", \"alice@mail.com\", \"alice1@mail.com\"], [\"Alice\", \"alice2@mail.com\", \"alice3@mail.com\"], [\"Alice\", \"alice1@mail.com\", \"alice2@mail.com\"]], [[\"Alice\", \"alice1@mail.com\", \"alice2@mail.com\", \"alice3@mail.com\", \"alice@mail.com\"]]), ([[\"John\", \"shared@mail.com\"], [\"Jane\", \"shared@mail.com\"]], [[\"John\", \"shared@mail.com\"]])]", "body": " result = run_accounts_merge(Solution, accounts)\n assert_accounts_merge(result, expected)" } ], diff --git a/.templates/leetcode/json/test_simple.json b/.templates/leetcode/json/test_simple.json deleted file mode 100644 index 9c96c62..0000000 --- a/.templates/leetcode/json/test_simple.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "problem_name": "test_simple", - "solution_class_name": "Solution", - "problem_number": "217", - "problem_title": "Contains Duplicate", - "difficulty": "Easy", - "topics": "Array, Hash Table, Sorting", - "tags": ["grind-75"], - "readme_description": "Given an integer array `nums`, return `true` if any value appears **at least twice** in the array, and return `false` if every element is distinct.", - "readme_examples": [ - { - "content": "```\nInput: nums = [1,2,3,1]\nOutput: true\n```\n**Explanation:** The element 1 occurs at the indices 0 and 3." - }, - { - "content": "```\nInput: nums = [1,2,3,4]\nOutput: false\n```\n**Explanation:** All elements are distinct." - }, - { "content": "```\nInput: nums = [1,1,1,3,3,4,3,2,4,2]\nOutput: true\n```" } - ], - "readme_constraints": "- 1 <= nums.length <= 10^5\n- -10^9 <= nums[i] <= 10^9", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "contains_duplicate", - "parameters": "nums: list[int]", - "return_type": "bool", - "dummy_return": "False" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "ContainsDuplicate", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_contains_duplicate", - "parametrize": "nums, expected", - "parametrize_typed": "nums: list[int], expected: bool", - "test_cases": "[([1, 2, 3, 1], True), ([1, 2, 3, 4], False), ([1, 1, 1, 3, 3, 4, 3, 2, 4, 2], True)]", - "body": "result = self.solution.contains_duplicate(nums)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnums = [1, 2, 3, 1]\nexpected = True", - "playground_execution": "result = Solution().contains_duplicate(nums)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/leetcode/accounts_merge/test_solution.py b/leetcode/accounts_merge/test_solution.py index 89cc9cb..5b98579 100644 --- a/leetcode/accounts_merge/test_solution.py +++ b/leetcode/accounts_merge/test_solution.py @@ -43,6 +43,29 @@ def setup_method(self): ["Fern", "Fern0@m.co", "Fern1@m.co", "Fern5@m.co"], ], ), + ([["John", "john@mail.com"]], [["John", "john@mail.com"]]), + ([["John"]], [["John"]]), + ( + [["John", "john1@mail.com"], ["John", "john2@mail.com"], ["John", "john3@mail.com"]], + [["John", "john1@mail.com"], ["John", "john2@mail.com"], ["John", "john3@mail.com"]], + ), + ( + [ + ["John", "a@mail.com", "b@mail.com"], + ["John", "b@mail.com", "c@mail.com"], + ["John", "d@mail.com"], + ], + [["John", "a@mail.com", "b@mail.com", "c@mail.com"], ["John", "d@mail.com"]], + ), + ( + [ + ["Alice", "alice@mail.com", "alice1@mail.com"], + ["Alice", "alice2@mail.com", "alice3@mail.com"], + ["Alice", "alice1@mail.com", "alice2@mail.com"], + ], + [["Alice", "alice1@mail.com", "alice2@mail.com", "alice3@mail.com", "alice@mail.com"]], + ), + ([["John", "shared@mail.com"], ["Jane", "shared@mail.com"]], [["John", "shared@mail.com"]]), ], ) def test_accounts_merge(self, accounts: list[list[str]], expected: list[list[str]]): From 8dd27096feba8342d4763b887b771d0f7a92fb8b Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 07:36:04 +0700 Subject: [PATCH 06/39] feat: finish example plan --- .amazonq/plans/create_template_example.md | 51 +++++ .../plans/migrate_problems_to_new_template.md | 197 ++---------------- 2 files changed, 63 insertions(+), 185 deletions(-) create mode 100644 .amazonq/plans/create_template_example.md diff --git a/.amazonq/plans/create_template_example.md b/.amazonq/plans/create_template_example.md new file mode 100644 index 0000000..c1523bc --- /dev/null +++ b/.amazonq/plans/create_template_example.md @@ -0,0 +1,51 @@ +# Create Template Example + +Create single comprehensive example file with all template patterns. + +## Goal + +Replace multiple examples with one `example.json5` containing `valid_anagram` as base with rich comments for all variations. + +## Structure + +``` +.templates/leetcode/examples/ +└── example.json5 # Comprehensive template with all patterns +``` + +## Content Plan + +Use `valid_anagram` as working example with comments showing all patterns. + +## Reference Problems (5 total) + +1. **`valid_anagram`** - Basic: string parameters, boolean return +2. **`invert_binary_tree`** - Tree: TreeNode imports/parameters +3. **`merge_two_sorted_lists`** - LinkedList: ListNode imports/parameters +4. **`lru_cache`** - Design: custom class, multiple methods, operations +5. **`implement_trie_prefix_tree`** - Trie: DictTree inheritance + +## Implementation + +**Prerequisites:** Complete these 5 problems first using `.amazonq/plans/migrate_problems_to_new_template.md`: + +1. **`valid_anagram`** - Follow migration steps 1-8 +2. **`invert_binary_tree`** - Follow migration steps 1-8 +3. **`merge_two_sorted_lists`** - Follow migration steps 1-8 +4. **`lru_cache`** - Follow migration steps 1-8 +5. **`implement_trie_prefix_tree`** - Follow migration steps 1-8 + +**Then create example:** + +1. Create `.templates/leetcode/examples/example.json5` +2. Use `valid_anagram` as base structure +3. Add comprehensive comments showing variations from the other 4 problems +4. Test generation works with the example + +## Key Sections + +- Problem identification (basic vs design class names) +- Imports (empty, TreeNode, ListNode, DictTree) +- Methods (single vs multiple, **init** patterns) +- Tests (simple vs operations-based) +- Playground (single quotes for strings) diff --git a/.amazonq/plans/migrate_problems_to_new_template.md b/.amazonq/plans/migrate_problems_to_new_template.md index 3a18978..83ac6c9 100644 --- a/.amazonq/plans/migrate_problems_to_new_template.md +++ b/.amazonq/plans/migrate_problems_to_new_template.md @@ -1,193 +1,20 @@ # Migrate Problems to New Template Format -## Overview +Migrate problems from `.templates/leetcode/json_old/` to `.templates/leetcode/json/` using the new template system. -Migrate all problems from old JSON format (`.templates/leetcode/json_old/`) to new template format (`.templates/leetcode/json/`) one by one. +## Migration Steps -## Prerequisites +1. **Find next**: `python .amazonq/plans/find_next_problem.py` +2. **Create JSON**: Analyze old format, create new template in `.templates/leetcode/json/` +3. **Generate**: `make p-gen PROBLEM=` +4. **Lint**: `make p-lint PROBLEM=` (fix JSON if fails, regenerate with `FORCE=1`) +5. **Implement**: Copy solution from `leetcode_old/` +6. **Test**: `make p-test PROBLEM=` +7. **Enhance tests**: If only 2-3 cases, add edge cases (update JSON → regenerate → lint → test) +8. **Commit**: Save changes -- New template system is ready in `.templates/leetcode/{{cookiecutter.problem_name}}/` -- Helper script exists: `.amazonq/plans/find_next_problem.py` - -## Process - -### Step 1: Find Next Problem - -```bash -python .amazonq/plans/find_next_problem.py -``` - -This returns the next problem to migrate (e.g., `accounts_merge`). - -### Step 2: Analyze Old JSON - -```bash -# Check the old format -cat .templates/leetcode/json_old/accounts_merge.json -``` - -Understand the structure and required variables. - -### Step 3: Create New JSON - -Create `.templates/leetcode/json/accounts_merge.json` with new template variables: - -**Required Variables:** - -- `problem_name` - Snake case name -- `solution_class_name` - Usually "Solution" -- `problem_number` - LeetCode number -- `problem_title` - Exact title -- `difficulty` - Easy/Medium/Hard -- `topics` - Comma-separated topics - -**Template-Specific Variables:** - -- `helpers_imports` - Imports for helpers.py -- `helpers_content` - Helper functions -- `helpers_run_name` - Main method name -- `helpers_run_signature` - Method signature -- `helpers_run_body` - Method implementation -- `helpers_assert_name` - Assert function name -- `helpers_assert_signature` - Assert signature -- `helpers_assert_body` - Assert implementation -- `solution_imports` - Imports for solution.py -- `solution_class_content` - Class-level content -- `solution_methods` - Array of method objects -- `test_imports` - Test imports -- `test_methods` - Array of test method objects -- `playground_imports` - Notebook imports -- `playground_setup` - Test case setup -- `playground_run` - Execution code -- `playground_assert` - Assertion code -- `readme_description` - Problem description -- `readme_constraints` - Constraints - -### Step 4: Generate and Test - -```bash -# Generate problem structure -make p-gen PROBLEM=accounts_merge - -# Lint to catch issues -make p-lint PROBLEM=accounts_merge - -# Fix any linting errors by updating JSON or generated files -``` - -### Step 5: Verify Generation - -Check generated files: - -- `leetcode/accounts_merge/README.md` -- `leetcode/accounts_merge/solution.py` -- `leetcode/accounts_merge/helpers.py` -- `leetcode/accounts_merge/test_solution.py` -- `leetcode/accounts_merge/playground.ipynb` - -### Step 6: Implement Solution - -```bash -# Copy implementation from old repository -# Find the solution in leetcode_old/ and implement in solution.py -# Replace TODO with actual working solution -``` - -### Step 7: Test Functionality - -```bash -# Run tests to ensure everything works -make p-test PROBLEM=accounts_merge -``` - -### Step 8: Repeat - -```bash -# Find next problem -python .amazonq/plans/find_next_problem.py -``` - -Repeat process until script returns "All problems updated!" - -## Migration Checklist - -For each problem: - -- [ ] Run `find_next_problem.py` to get next problem -- [ ] Analyze old JSON structure -- [ ] Create new JSON with all required variables -- [ ] Run `make p-gen PROBLEM=` -- [ ] Run `make p-lint PROBLEM=` and fix issues: - - If linting fails, fix the JSON template - - Re-run `make p-gen PROBLEM= FORCE=1` to regenerate - - Re-run `make p-lint PROBLEM=` to verify - - Iterate until linting passes to ensure reproducibility -- [ ] Implement solution from `leetcode_old/` repository -- [ ] Run `make p-test PROBLEM=` to verify tests pass -- [ ] Commit changes - -## Common Patterns - -**Basic Problem:** - -```json -{ - "problem_name": "two_sum", - "solution_class_name": "Solution", - "helpers_run_name": "two_sum", - "helpers_run_signature": "(solution_class: type, nums: list[int], target: int)", - "helpers_run_body": "return solution_class().two_sum(nums, target)", - "solution_methods": [ - { - "name": "two_sum", - "signature": "(self, nums: list[int], target: int) -> list[int]", - "body": "# TODO: Implement\nreturn []" - } - ] -} -``` - -**Tree Problem:** - -```json -{ - "helpers_imports": "from leetcode_py import TreeNode", - "helpers_content": "def create_tree(root_list: list[int | None]) -> TreeNode[int] | None:\n return TreeNode[int].from_list(root_list)", - "solution_imports": "from leetcode_py import TreeNode" -} -``` - -**Design Problem:** - -```json -{ - "solution_class_name": "LRUCache", - "solution_methods": [ - { - "name": "__init__", - "signature": "(self, capacity: int)", - "body": "# TODO: Initialize" - }, - { - "name": "get", - "signature": "(self, key: int) -> int", - "body": "# TODO: Implement\nreturn -1" - } - ] -} -``` - -## Progress Tracking - -Track progress by running the script periodically: +## Progress ```bash -# Check remaining problems -ls .templates/leetcode/json_old/ | wc -l # Total old problems -ls .templates/leetcode/json/ | wc -l # Migrated problems -python .amazonq/plans/find_next_problem.py # Next to migrate +python .amazonq/plans/find_next_problem.py # Next problem or "All problems updated!" ``` - -## Completion - -When `find_next_problem.py` returns "All problems updated!", the migration is complete. From 6f628c30e4848c7c83e4f765e1935f9e1c661bad Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 08:20:08 +0700 Subject: [PATCH 07/39] feat: completed example problems --- .../plans/migrate_problems_to_new_template.md | 17 +- .../json/implement_trie_prefix_tree.json | 74 +++++++ .../leetcode/json/invert_binary_tree.json | 61 ++++++ .templates/leetcode/json/lru_cache.json | 69 ++++++ .../leetcode/json/merge_two_sorted_lists.json | 63 ++++++ .templates/leetcode/json/valid_anagram.json | 60 ++++++ leetcode/implement_trie_prefix_tree/README.md | 48 +++++ .../implement_trie_prefix_tree/__init__.py | 0 .../implement_trie_prefix_tree/helpers.py | 20 ++ .../playground.ipynb | 204 ++++++++++++++++++ .../implement_trie_prefix_tree/solution.py | 40 ++++ .../test_solution.py | 42 ++++ leetcode/invert_binary_tree/README.md | 39 ++++ leetcode/invert_binary_tree/__init__.py | 0 leetcode/invert_binary_tree/helpers.py | 13 ++ leetcode/invert_binary_tree/playground.ipynb | 70 ++++++ leetcode/invert_binary_tree/solution.py | 59 +++++ leetcode/invert_binary_tree/test_solution.py | 32 +++ leetcode/lru_cache/README.md | 50 +++++ leetcode/lru_cache/__init__.py | 0 leetcode/lru_cache/helpers.py | 18 ++ leetcode/lru_cache/playground.ipynb | 70 ++++++ leetcode/lru_cache/solution.py | 108 ++++++++++ leetcode/lru_cache/test_solution.py | 41 ++++ leetcode/merge_two_sorted_lists/README.md | 46 ++++ leetcode/merge_two_sorted_lists/__init__.py | 0 leetcode/merge_two_sorted_lists/helpers.py | 14 ++ .../merge_two_sorted_lists/playground.ipynb | 172 +++++++++++++++ leetcode/merge_two_sorted_lists/solution.py | 24 +++ .../merge_two_sorted_lists/test_solution.py | 34 +++ leetcode/valid_anagram/README.md | 34 +++ leetcode/valid_anagram/__init__.py | 0 leetcode/valid_anagram/helpers.py | 8 + leetcode/valid_anagram/playground.ipynb | 92 ++++++++ leetcode/valid_anagram/solution.py | 8 + leetcode/valid_anagram/test_solution.py | 39 ++++ 36 files changed, 1661 insertions(+), 8 deletions(-) create mode 100644 .templates/leetcode/json/implement_trie_prefix_tree.json create mode 100644 .templates/leetcode/json/invert_binary_tree.json create mode 100644 .templates/leetcode/json/lru_cache.json create mode 100644 .templates/leetcode/json/merge_two_sorted_lists.json create mode 100644 .templates/leetcode/json/valid_anagram.json create mode 100644 leetcode/implement_trie_prefix_tree/README.md create mode 100644 leetcode/implement_trie_prefix_tree/__init__.py create mode 100644 leetcode/implement_trie_prefix_tree/helpers.py create mode 100644 leetcode/implement_trie_prefix_tree/playground.ipynb create mode 100644 leetcode/implement_trie_prefix_tree/solution.py create mode 100644 leetcode/implement_trie_prefix_tree/test_solution.py create mode 100644 leetcode/invert_binary_tree/README.md create mode 100644 leetcode/invert_binary_tree/__init__.py create mode 100644 leetcode/invert_binary_tree/helpers.py create mode 100644 leetcode/invert_binary_tree/playground.ipynb create mode 100644 leetcode/invert_binary_tree/solution.py create mode 100644 leetcode/invert_binary_tree/test_solution.py create mode 100644 leetcode/lru_cache/README.md create mode 100644 leetcode/lru_cache/__init__.py create mode 100644 leetcode/lru_cache/helpers.py create mode 100644 leetcode/lru_cache/playground.ipynb create mode 100644 leetcode/lru_cache/solution.py create mode 100644 leetcode/lru_cache/test_solution.py create mode 100644 leetcode/merge_two_sorted_lists/README.md create mode 100644 leetcode/merge_two_sorted_lists/__init__.py create mode 100644 leetcode/merge_two_sorted_lists/helpers.py create mode 100644 leetcode/merge_two_sorted_lists/playground.ipynb create mode 100644 leetcode/merge_two_sorted_lists/solution.py create mode 100644 leetcode/merge_two_sorted_lists/test_solution.py create mode 100644 leetcode/valid_anagram/README.md create mode 100644 leetcode/valid_anagram/__init__.py create mode 100644 leetcode/valid_anagram/helpers.py create mode 100644 leetcode/valid_anagram/playground.ipynb create mode 100644 leetcode/valid_anagram/solution.py create mode 100644 leetcode/valid_anagram/test_solution.py diff --git a/.amazonq/plans/migrate_problems_to_new_template.md b/.amazonq/plans/migrate_problems_to_new_template.md index 83ac6c9..bd20faf 100644 --- a/.amazonq/plans/migrate_problems_to_new_template.md +++ b/.amazonq/plans/migrate_problems_to_new_template.md @@ -4,14 +4,15 @@ Migrate problems from `.templates/leetcode/json_old/` to `.templates/leetcode/js ## Migration Steps -1. **Find next**: `python .amazonq/plans/find_next_problem.py` -2. **Create JSON**: Analyze old format, create new template in `.templates/leetcode/json/` -3. **Generate**: `make p-gen PROBLEM=` -4. **Lint**: `make p-lint PROBLEM=` (fix JSON if fails, regenerate with `FORCE=1`) -5. **Implement**: Copy solution from `leetcode_old/` -6. **Test**: `make p-test PROBLEM=` -7. **Enhance tests**: If only 2-3 cases, add edge cases (update JSON → regenerate → lint → test) -8. **Commit**: Save changes +1. **Create JSON**: Analyze old format, create new template in `.templates/leetcode/json/` +2. **Generate**: `make p-gen PROBLEM=` +3. **Lint**: `make p-lint PROBLEM=` (fix JSON if fails, regenerate with `FORCE=1`) +4. **Implement**: Look at `leetcode_old/` code - copy solution with all comments/notes and review test cases + - **Important**: Re-copy solution after `p-gen` since it overwrites with TODO placeholders + - **Multiple solutions**: If old code has alternative implementations (e.g., Solution, SolutionDFS, SolutionBFS), + add parametrize to test all classes (see lru_cache as example) +5. **Test**: `make p-test PROBLEM=` +6. **Enhance tests**: If only 2-3 cases, add edge cases (update JSON → regenerate → lint → test) ## Progress diff --git a/.templates/leetcode/json/implement_trie_prefix_tree.json b/.templates/leetcode/json/implement_trie_prefix_tree.json new file mode 100644 index 0000000..dcc8fa0 --- /dev/null +++ b/.templates/leetcode/json/implement_trie_prefix_tree.json @@ -0,0 +1,74 @@ +{ + "problem_name": "implement_trie_prefix_tree", + "solution_class_name": "Trie(DictTree[str])", + "problem_number": "208", + "problem_title": "Implement Trie (Prefix Tree)", + "difficulty": "Medium", + "topics": "Hash Table, String, Design, Trie", + "_tags": { "list": ["grind-75"] }, + "readme_description": "A **trie** (pronounced as \"try\") or **prefix tree** is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker.\n\nImplement the Trie class:\n\n- `Trie()` Initializes the trie object.\n- `void insert(String word)` Inserts the string `word` into the trie.\n- `boolean search(String word)` Returns `true` if the string `word` is in the trie (i.e., was inserted before), and `false` otherwise.\n- `boolean startsWith(String prefix)` Returns `true` if there is a previously inserted string `word` that has the prefix `prefix`, and `false` otherwise.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput\n[\"Trie\", \"insert\", \"search\", \"search\", \"startsWith\", \"insert\", \"search\"]\n[[], [\"apple\"], [\"apple\"], [\"app\"], [\"app\"], [\"app\"], [\"app\"]]\nOutput\n[null, null, true, false, true, null, true]\n```\n\n**Explanation:**\n```python\ntrie = Trie()\ntrie.insert(\"apple\")\ntrie.search(\"apple\") # return True\ntrie.search(\"app\") # return False\ntrie.starts_with(\"app\") # return True\ntrie.insert(\"app\")\ntrie.search(\"app\") # return True\n```" + } + ] + }, + "readme_constraints": "- `1 <= word.length, prefix.length <= 2000`\n- `word` and `prefix` consist only of lowercase English letters.\n- At most `3 * 10^4` calls **in total** will be made to `insert`, `search`, and `starts_with`.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "trie_operations", + "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[str]])", + "helpers_run_body": " trie = None\n results: list[bool | None] = []\n for i, op in enumerate(operations):\n if op == 'Trie':\n trie = solution_class()\n results.append(None)\n elif op == 'insert' and trie is not None:\n trie.insert(inputs[i][0])\n results.append(None)\n elif op == 'search' and trie is not None:\n results.append(trie.search(inputs[i][0]))\n elif op == 'starts_with' and trie is not None:\n results.append(trie.starts_with(inputs[i][0]))\n return results, trie", + "helpers_assert_name": "trie_operations", + "helpers_assert_signature": "(result: list[bool | None], expected: list[bool | None]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "from leetcode_py.data_structures import DictTree, RecursiveDict", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_trie_operations, run_trie_operations\nfrom .solution import Trie", + "test_content": "", + "test_class_name": "ImplementTriePrefixTree", + "test_class_content": "", + "_solution_methods": { + "list": [ + { + "name": "__init__", + "signature": "(self) -> None", + "body": " super().__init__()\n self.root: RecursiveDict[str] = {}" + }, + { + "name": "insert", + "signature": "(self, word: str) -> None", + "body": " # TODO: Implement insert\n pass" + }, + { + "name": "search", + "signature": "(self, word: str) -> bool", + "body": " # TODO: Implement search\n return False" + }, + { + "name": "starts_with", + "signature": "(self, prefix: str) -> bool", + "body": " # TODO: Implement starts_with\n return False" + } + ] + }, + "_test_helper_methods": { "list": [] }, + "_test_methods": { + "list": [ + { + "name": "test_trie_operations", + "signature": "(self, operations: list[str], inputs: list[list[str]], expected: list[bool | None])", + "parametrize": "operations, inputs, expected", + "test_cases": "[(['Trie', 'insert', 'search', 'search', 'starts_with', 'insert', 'search'], [[], ['apple'], ['apple'], ['app'], ['app'], ['app'], ['app']], [None, None, True, False, True, None, True]), (['Trie', 'insert', 'insert', 'search', 'search', 'starts_with', 'starts_with'], [[], ['hello'], ['world'], ['hello'], ['hi'], ['hel'], ['wor']], [None, None, None, True, False, True, True]), (['Trie', 'insert', 'insert', 'search', 'search', 'starts_with', 'starts_with'], [[], ['a'], ['aa'], ['a'], ['aa'], ['a'], ['aa']], [None, None, None, True, True, True, True]), (['Trie', 'insert', 'search', 'starts_with', 'insert', 'search', 'starts_with'], [[], ['test'], ['testing'], ['test'], ['testing'], ['testing'], ['test']], [None, None, False, True, None, True, True]), (['Trie', 'search', 'starts_with'], [[], ['empty'], ['empty']], [None, False, False])]", + "body": " result, _ = run_trie_operations(Trie, operations, inputs)\n assert_trie_operations(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_trie_operations, assert_trie_operations\nfrom solution import Trie", + "playground_setup": "# Example test case\noperations = ['Trie', 'insert', 'search', 'search', 'starts_with', 'insert', 'search']\ninputs = [[], ['apple'], ['apple'], ['app'], ['app'], ['app'], ['app']]\nexpected = [None, None, True, False, True, None, True]", + "playground_run": "result, trie = run_trie_operations(Trie, operations, inputs)\nprint(result)\ntrie", + "playground_assert": "assert_trie_operations(result, expected)" +} diff --git a/.templates/leetcode/json/invert_binary_tree.json b/.templates/leetcode/json/invert_binary_tree.json new file mode 100644 index 0000000..d95e35c --- /dev/null +++ b/.templates/leetcode/json/invert_binary_tree.json @@ -0,0 +1,61 @@ +{ + "problem_name": "invert_binary_tree", + "solution_class_name": "Solution", + "problem_number": "226", + "problem_title": "Invert Binary Tree", + "difficulty": "Easy", + "topics": "Tree, Depth-First Search, Breadth-First Search, Binary Tree", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given the `root` of a binary tree, invert the tree, and return its root.", + "_readme_examples": { + "list": [ + { "content": "```\nInput: root = [4,2,7,1,3,6,9]\nOutput: [4,7,2,9,6,3,1]\n```" }, + { "content": "```\nInput: root = [2,1,3]\nOutput: [2,3,1]\n```" }, + { "content": "```\nInput: root = []\nOutput: []\n```" } + ] + }, + "readme_constraints": "- The number of nodes in the tree is in the range [0, 100]\n- -100 <= Node.val <= 100", + "readme_additional": "", + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "", + "helpers_run_name": "invert_tree", + "helpers_run_signature": "(solution_class: type, root_list: list[int | None])", + "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n implementation = solution_class()\n return implementation.invert_tree(root)", + "helpers_assert_name": "invert_tree", + "helpers_assert_signature": "(result: TreeNode[int] | None, expected_list: list[int | None]) -> bool", + "helpers_assert_body": " expected = TreeNode[int].from_list(expected_list)\n assert result == expected\n return True", + "solution_imports": "from leetcode_py import TreeNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_invert_tree, run_invert_tree\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "InvertBinaryTree", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "invert_tree", + "signature": "(self, root: TreeNode[int] | None) -> TreeNode[int] | None", + "body": " # TODO: Implement invert_tree\n return None" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_invert_tree", + "signature": "(self, root_list: list[int | None], expected_list: list[int | None])", + "parametrize": "root_list, expected_list", + "test_cases": "[([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), ([2, 1, 3], [2, 3, 1]), ([], []), ([1], [1]), ([1, 2], [1, None, 2]), ([1, None, 2], [1, 2]), ([1, 2, 3, 4, 5], [1, 3, 2, None, None, 5, 4]), ([1, 2, 3, None, None, 4, 5], [1, 3, 2, 5, 4])]", + "body": " result = run_invert_tree(Solution, root_list)\n assert_invert_tree(result, expected_list)" + } + ] + }, + "playground_imports": "from helpers import run_invert_tree, assert_invert_tree\nfrom solution import Solution\nfrom leetcode_py import TreeNode", + "playground_setup": "# Example test case\nroot_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\nexpected_list: list[int | None] = [4, 7, 2, 9, 6, 3, 1]", + "playground_run": "result = run_invert_tree(Solution, root_list)\nresult", + "playground_assert": "assert_invert_tree(result, expected_list)" +} diff --git a/.templates/leetcode/json/lru_cache.json b/.templates/leetcode/json/lru_cache.json new file mode 100644 index 0000000..3bc81d9 --- /dev/null +++ b/.templates/leetcode/json/lru_cache.json @@ -0,0 +1,69 @@ +{ + "problem_name": "lru_cache", + "solution_class_name": "LRUCache", + "problem_number": "146", + "problem_title": "LRU Cache", + "difficulty": "Medium", + "topics": "Hash Table, Linked List, Design, Doubly-Linked List", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.\n\nImplement the `LRUCache` class:\n\n- `LRUCache(int capacity)` Initialize the LRU cache with positive size capacity\n- `int get(int key)` Return the value of the key if the key exists, otherwise return -1\n- `void put(int key, int value)` Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key\n\nThe functions `get` and `put` must each run in `O(1)` average time complexity.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput\n[\"LRUCache\", \"put\", \"put\", \"get\", \"put\", \"get\", \"put\", \"get\", \"get\", \"get\"]\n[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\nOutput\n[null, null, null, 1, null, -1, null, -1, 3, 4]\n\nExplanation\nLRUCache lRUCache = new LRUCache(2);\nlRUCache.put(1, 1); // cache is {1=1}\nlRUCache.put(2, 2); // cache is {1=1, 2=2}\nlRUCache.get(1); // return 1\nlRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}\nlRUCache.get(2); // returns -1 (not found)\nlRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}\nlRUCache.get(1); // return -1 (not found)\nlRUCache.get(3); // return 3\nlRUCache.get(4); // return 4\n```" + } + ] + }, + "readme_constraints": "- 1 <= capacity <= 3000\n- 0 <= key <= 10^4\n- 0 <= value <= 10^5\n- At most 2 * 10^5 calls will be made to get and put", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "lru_cache", + "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[int]])", + "helpers_run_body": " cache = None\n results: list[int | None] = []\n for i, op in enumerate(operations):\n if op == 'LRUCache':\n cache = solution_class(inputs[i][0])\n results.append(None)\n elif op == 'get' and cache is not None:\n results.append(cache.get(inputs[i][0]))\n elif op == 'put' and cache is not None:\n cache.put(inputs[i][0], inputs[i][1])\n results.append(None)\n return results, cache", + "helpers_assert_name": "lru_cache", + "helpers_assert_signature": "(result: list[int | None], expected: list[int | None]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_lru_cache, run_lru_cache\nfrom .solution import LRUCache", + "test_content": "", + "test_class_name": "LRUCache", + "test_class_content": "", + "_solution_methods": { + "list": [ + { + "name": "__init__", + "signature": "(self, capacity: int) -> None", + "body": " # TODO: Initialize LRU cache\n pass" + }, + { + "name": "get", + "signature": "(self, key: int) -> int", + "body": " # TODO: Implement get\n return -1" + }, + { + "name": "put", + "signature": "(self, key: int, value: int) -> None", + "body": " # TODO: Implement put\n pass" + } + ] + }, + "_test_helper_methods": { "list": [] }, + "_test_methods": { + "list": [ + { + "name": "test_lru_cache", + "signature": "(self, operations: list[str], inputs: list[list[int]], expected: list[int | None])", + "parametrize": "operations, inputs, expected", + "test_cases": "[(['LRUCache', 'put', 'put', 'get', 'put', 'get', 'put', 'get', 'get', 'get'], [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]], [None, None, None, 1, None, -1, None, -1, 3, 4]), (['LRUCache', 'get', 'put', 'get', 'put', 'put', 'get', 'get'], [[2], [2], [2, 6], [1], [1, 5], [1, 2], [1], [2]], [None, -1, None, -1, None, None, 2, 6]), (['LRUCache', 'put', 'get', 'put', 'get', 'get'], [[1], [2, 1], [2], [3, 2], [2], [3]], [None, None, 1, None, -1, 2])]", + "body": " result, _ = run_lru_cache(LRUCache, operations, inputs)\n assert_lru_cache(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_lru_cache, assert_lru_cache\nfrom solution import LRUCache", + "playground_setup": "# Example test case\noperations = ['LRUCache', 'put', 'put', 'get', 'put', 'get', 'put', 'get', 'get', 'get']\ninputs = [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\nexpected = [None, None, None, 1, None, -1, None, -1, 3, 4]", + "playground_run": "result, cache = run_lru_cache(LRUCache, operations, inputs)\nprint(result)\ncache", + "playground_assert": "assert_lru_cache(result, expected)" +} diff --git a/.templates/leetcode/json/merge_two_sorted_lists.json b/.templates/leetcode/json/merge_two_sorted_lists.json new file mode 100644 index 0000000..d9c2b1c --- /dev/null +++ b/.templates/leetcode/json/merge_two_sorted_lists.json @@ -0,0 +1,63 @@ +{ + "problem_name": "merge_two_sorted_lists", + "solution_class_name": "Solution", + "problem_number": "21", + "problem_title": "Merge Two Sorted Lists", + "difficulty": "Easy", + "topics": "Linked List, Recursion", + "_tags": { "list": ["grind-75"] }, + "readme_description": "You are given the heads of two sorted linked lists `list1` and `list2`.\n\nMerge the two lists into one **sorted** list. The list should be made by splicing together the nodes of the first two lists.\n\nReturn *the head of the merged linked list*.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2020/10/03/merge_ex1.jpg)\n\n```\nInput: list1 = [1,2,4], list2 = [1,3,4]\nOutput: [1,1,2,3,4,4]\n```" + }, + { "content": "```\nInput: list1 = [], list2 = []\nOutput: []\n```" }, + { "content": "```\nInput: list1 = [], list2 = [0]\nOutput: [0]\n```" } + ] + }, + "readme_constraints": "- The number of nodes in both lists is in the range `[0, 50]`.\n- `-100 <= Node.val <= 100`\n- Both `list1` and `list2` are sorted in **non-decreasing** order.", + "readme_additional": "", + "helpers_imports": "from leetcode_py import ListNode", + "helpers_content": "", + "helpers_run_name": "merge_two_lists", + "helpers_run_signature": "(solution_class: type, list1_vals: list[int], list2_vals: list[int])", + "helpers_run_body": " list1 = ListNode[int].from_list(list1_vals)\n list2 = ListNode[int].from_list(list2_vals)\n implementation = solution_class()\n return implementation.merge_two_lists(list1, list2)", + "helpers_assert_name": "merge_two_lists", + "helpers_assert_signature": "(result: ListNode[int] | None, expected_vals: list[int]) -> bool", + "helpers_assert_body": " expected = ListNode[int].from_list(expected_vals)\n assert result == expected\n return True", + "solution_imports": "from leetcode_py import ListNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_merge_two_lists, run_merge_two_lists\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MergeTwoSortedLists", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "merge_two_lists", + "signature": "(self, list1: ListNode[int] | None, list2: ListNode[int] | None) -> ListNode[int] | None", + "body": " # TODO: Implement merge_two_lists\n return None" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_merge_two_lists", + "signature": "(self, list1_vals: list[int], list2_vals: list[int], expected_vals: list[int])", + "parametrize": "list1_vals, list2_vals, expected_vals", + "test_cases": "[([1, 2, 4], [1, 3, 4], [1, 1, 2, 3, 4, 4]), ([], [], []), ([], [0], [0]), ([0], [], [0]), ([1], [2], [1, 2]), ([2], [1], [1, 2]), ([1, 1, 1], [2, 2, 2], [1, 1, 1, 2, 2, 2]), ([1, 3, 5], [2, 4, 6], [1, 2, 3, 4, 5, 6]), ([-10, -5, 0], [-8, -3, 1], [-10, -8, -5, -3, 0, 1]), ([5], [1, 2, 3, 4], [1, 2, 3, 4, 5]), ([1, 2, 3, 4], [5], [1, 2, 3, 4, 5])]", + "body": " result = run_merge_two_lists(Solution, list1_vals, list2_vals)\n assert_merge_two_lists(result, expected_vals)" + } + ] + }, + "playground_imports": "from helpers import run_merge_two_lists, assert_merge_two_lists\nfrom solution import Solution", + "playground_setup": "# Example test case\nlist1_vals = [1, 2, 4]\nlist2_vals = [1, 3, 4]\nexpected_vals = [1, 1, 2, 3, 4, 4]", + "playground_run": "result = run_merge_two_lists(Solution, list1_vals, list2_vals)\nresult", + "playground_assert": "assert_merge_two_lists(result, expected_vals)" +} diff --git a/.templates/leetcode/json/valid_anagram.json b/.templates/leetcode/json/valid_anagram.json new file mode 100644 index 0000000..2a0c3ce --- /dev/null +++ b/.templates/leetcode/json/valid_anagram.json @@ -0,0 +1,60 @@ +{ + "problem_name": "valid_anagram", + "solution_class_name": "Solution", + "problem_number": "242", + "problem_title": "Valid Anagram", + "difficulty": "Easy", + "topics": "Hash Table, String, Sorting", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given two strings `s` and `t`, return `true` if `t` is an anagram of `s`, and `false` otherwise.", + "_readme_examples": { + "list": [ + { "content": "```\nInput: s = \"anagram\", t = \"nagaram\"\nOutput: true\n```" }, + { "content": "```\nInput: s = \"rat\", t = \"car\"\nOutput: false\n```" } + ] + }, + "readme_constraints": "- 1 <= s.length, t.length <= 5 * 10^4\n- s and t consist of lowercase English letters.", + "readme_additional": "**Follow up:** What if the inputs contain Unicode characters? How would you adapt your solution to such a case?", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "is_anagram", + "helpers_run_signature": "(solution_class: type, s: str, t: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.is_anagram(s, t)", + "helpers_assert_name": "is_anagram", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_is_anagram, run_is_anagram\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ValidAnagram", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "is_anagram", + "signature": "(self, s: str, t: str) -> bool", + "body": " # TODO: Implement is_anagram\n return False" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_is_anagram", + "signature": "(self, s: str, t: str, expected: bool)", + "parametrize": "s, t, expected", + "test_cases": "[('anagram', 'nagaram', True), ('rat', 'car', False), ('listen', 'silent', True), ('hello', 'bello', False), ('', '', True), ('a', 'a', True), ('a', 'b', False), ('ab', 'ba', True), ('abc', 'bca', True), ('abc', 'def', False), ('aab', 'abb', False), ('aabbcc', 'abcabc', True), ('abcd', 'abcde', False), ('race', 'care', True), ('elbow', 'below', True), ('study', 'dusty', True), ('night', 'thing', True), ('stressed', 'desserts', True)]", + "body": " result = run_is_anagram(Solution, s, t)\n assert_is_anagram(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_is_anagram, assert_is_anagram\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = 'anagram'\nt = 'nagaram'\nexpected = True", + "playground_run": "result = run_is_anagram(Solution, s, t)\nresult", + "playground_assert": "assert_is_anagram(result, expected)" +} diff --git a/leetcode/implement_trie_prefix_tree/README.md b/leetcode/implement_trie_prefix_tree/README.md new file mode 100644 index 0000000..7e0cd3c --- /dev/null +++ b/leetcode/implement_trie_prefix_tree/README.md @@ -0,0 +1,48 @@ +# Implement Trie (Prefix Tree) + +**Difficulty:** Medium +**Topics:** Hash Table, String, Design, Trie +**Tags:** grind-75 + +**LeetCode:** [Problem 208](https://leetcode.com/problems/implement-trie-prefix-tree/description/) + +## Problem Description + +A **trie** (pronounced as "try") or **prefix tree** is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker. + +Implement the Trie class: + +- `Trie()` Initializes the trie object. +- `void insert(String word)` Inserts the string `word` into the trie. +- `boolean search(String word)` Returns `true` if the string `word` is in the trie (i.e., was inserted before), and `false` otherwise. +- `boolean startsWith(String prefix)` Returns `true` if there is a previously inserted string `word` that has the prefix `prefix`, and `false` otherwise. + +## Examples + +### Example 1: + +``` +Input +["Trie", "insert", "search", "search", "startsWith", "insert", "search"] +[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]] +Output +[null, null, true, false, true, null, true] +``` + +**Explanation:** + +```python +trie = Trie() +trie.insert("apple") +trie.search("apple") # return True +trie.search("app") # return False +trie.starts_with("app") # return True +trie.insert("app") +trie.search("app") # return True +``` + +## Constraints + +- `1 <= word.length, prefix.length <= 2000` +- `word` and `prefix` consist only of lowercase English letters. +- At most `3 * 10^4` calls **in total** will be made to `insert`, `search`, and `starts_with`. diff --git a/leetcode/implement_trie_prefix_tree/__init__.py b/leetcode/implement_trie_prefix_tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/implement_trie_prefix_tree/helpers.py b/leetcode/implement_trie_prefix_tree/helpers.py new file mode 100644 index 0000000..17ae31a --- /dev/null +++ b/leetcode/implement_trie_prefix_tree/helpers.py @@ -0,0 +1,20 @@ +def run_trie_operations(solution_class: type, operations: list[str], inputs: list[list[str]]): + trie = None + results: list[bool | None] = [] + for i, op in enumerate(operations): + if op == "Trie": + trie = solution_class() + results.append(None) + elif op == "insert" and trie is not None: + trie.insert(inputs[i][0]) + results.append(None) + elif op == "search" and trie is not None: + results.append(trie.search(inputs[i][0])) + elif op == "starts_with" and trie is not None: + results.append(trie.starts_with(inputs[i][0])) + return results, trie + + +def assert_trie_operations(result: list[bool | None], expected: list[bool | None]) -> bool: + assert result == expected + return True diff --git a/leetcode/implement_trie_prefix_tree/playground.ipynb b/leetcode/implement_trie_prefix_tree/playground.ipynb new file mode 100644 index 0000000..c509af4 --- /dev/null +++ b/leetcode/implement_trie_prefix_tree/playground.ipynb @@ -0,0 +1,204 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_trie_operations, run_trie_operations\n", + "from solution import Trie" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "operations = [\"Trie\", \"insert\", \"search\", \"search\", \"starts_with\", \"insert\", \"search\"]\n", + "inputs = [[], [\"apple\"], [\"apple\"], [\"app\"], [\"app\"], [\"app\"], [\"app\"]]\n", + "expected = [None, None, True, False, True, None, True]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "run", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[None, None, True, False, True, None, True]\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root\n", + "\n", + "root\n", + "\n", + "\n", + "\n", + "root_0\n", + "\n", + "a\n", + "\n", + "\n", + "\n", + "root->root_0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root_0_0\n", + "\n", + "p\n", + "\n", + "\n", + "\n", + "root_0->root_0_0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root_0_0_0\n", + "\n", + "p\n", + "\n", + "\n", + "\n", + "root_0_0->root_0_0_0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root_0_0_0_0\n", + "\n", + "l\n", + "\n", + "\n", + "\n", + "root_0_0_0->root_0_0_0_0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root_0_0_0_leaf_1\n", + "\n", + "#: True\n", + "\n", + "\n", + "\n", + "root_0_0_0->root_0_0_0_leaf_1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root_0_0_0_0_0\n", + "\n", + "e\n", + "\n", + "\n", + "\n", + "root_0_0_0_0->root_0_0_0_0_0\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "root_0_0_0_0_0_leaf_0\n", + "\n", + "#: True\n", + "\n", + "\n", + "\n", + "root_0_0_0_0_0->root_0_0_0_0_0_leaf_0\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result, trie = run_trie_operations(Trie, operations, inputs)\n", + "print(result)\n", + "trie" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "assert", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_trie_operations(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/implement_trie_prefix_tree/solution.py b/leetcode/implement_trie_prefix_tree/solution.py new file mode 100644 index 0000000..35ea50b --- /dev/null +++ b/leetcode/implement_trie_prefix_tree/solution.py @@ -0,0 +1,40 @@ +from leetcode_py.data_structures import DictTree, RecursiveDict + + +class Trie(DictTree[str]): + END_OF_WORD = "#" + + # Time: O(1) + # Space: O(1) + def __init__(self) -> None: + self.root: RecursiveDict[str] = {} + + # Time: O(m) where m is word length + # Space: O(m) + def insert(self, word: str) -> None: + node = self.root + for char in word: + if char not in node: + node[char] = {} + node = node[char] + node[self.END_OF_WORD] = True + + # Time: O(m) where m is word length + # Space: O(1) + def search(self, word: str) -> bool: + node = self.root + for char in word: + if char not in node: + return False + node = node[char] + return self.END_OF_WORD in node + + # Time: O(m) where m is prefix length + # Space: O(1) + def starts_with(self, prefix: str) -> bool: + node = self.root + for char in prefix: + if char not in node: + return False + node = node[char] + return True diff --git a/leetcode/implement_trie_prefix_tree/test_solution.py b/leetcode/implement_trie_prefix_tree/test_solution.py new file mode 100644 index 0000000..48a4c13 --- /dev/null +++ b/leetcode/implement_trie_prefix_tree/test_solution.py @@ -0,0 +1,42 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_trie_operations, run_trie_operations +from .solution import Trie + + +class TestImplementTriePrefixTree: + + @logged_test + @pytest.mark.parametrize( + "operations, inputs, expected", + [ + ( + ["Trie", "insert", "search", "search", "starts_with", "insert", "search"], + [[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]], + [None, None, True, False, True, None, True], + ), + ( + ["Trie", "insert", "insert", "search", "search", "starts_with", "starts_with"], + [[], ["hello"], ["world"], ["hello"], ["hi"], ["hel"], ["wor"]], + [None, None, None, True, False, True, True], + ), + ( + ["Trie", "insert", "insert", "search", "search", "starts_with", "starts_with"], + [[], ["a"], ["aa"], ["a"], ["aa"], ["a"], ["aa"]], + [None, None, None, True, True, True, True], + ), + ( + ["Trie", "insert", "search", "starts_with", "insert", "search", "starts_with"], + [[], ["test"], ["testing"], ["test"], ["testing"], ["testing"], ["test"]], + [None, None, False, True, None, True, True], + ), + (["Trie", "search", "starts_with"], [[], ["empty"], ["empty"]], [None, False, False]), + ], + ) + def test_trie_operations( + self, operations: list[str], inputs: list[list[str]], expected: list[bool | None] + ): + result, _ = run_trie_operations(Trie, operations, inputs) + assert_trie_operations(result, expected) diff --git a/leetcode/invert_binary_tree/README.md b/leetcode/invert_binary_tree/README.md new file mode 100644 index 0000000..a2b7ded --- /dev/null +++ b/leetcode/invert_binary_tree/README.md @@ -0,0 +1,39 @@ +# Invert Binary Tree + +**Difficulty:** Easy +**Topics:** Tree, Depth-First Search, Breadth-First Search, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 226](https://leetcode.com/problems/invert-binary-tree/description/) + +## Problem Description + +Given the `root` of a binary tree, invert the tree, and return its root. + +## Examples + +### Example 1: + +``` +Input: root = [4,2,7,1,3,6,9] +Output: [4,7,2,9,6,3,1] +``` + +### Example 2: + +``` +Input: root = [2,1,3] +Output: [2,3,1] +``` + +### Example 3: + +``` +Input: root = [] +Output: [] +``` + +## Constraints + +- The number of nodes in the tree is in the range [0, 100] +- -100 <= Node.val <= 100 diff --git a/leetcode/invert_binary_tree/__init__.py b/leetcode/invert_binary_tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/invert_binary_tree/helpers.py b/leetcode/invert_binary_tree/helpers.py new file mode 100644 index 0000000..5af07e8 --- /dev/null +++ b/leetcode/invert_binary_tree/helpers.py @@ -0,0 +1,13 @@ +from leetcode_py import TreeNode + + +def run_invert_tree(solution_class: type, root_list: list[int | None]): + root = TreeNode[int].from_list(root_list) + implementation = solution_class() + return implementation.invert_tree(root) + + +def assert_invert_tree(result: TreeNode[int] | None, expected_list: list[int | None]) -> bool: + expected = TreeNode[int].from_list(expected_list) + assert result == expected + return True diff --git a/leetcode/invert_binary_tree/playground.ipynb b/leetcode/invert_binary_tree/playground.ipynb new file mode 100644 index 0000000..eb5d963 --- /dev/null +++ b/leetcode/invert_binary_tree/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_invert_tree, run_invert_tree\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import TreeNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\n", + "expected_list: list[int | None] = [4, 7, 2, 9, 6, 3, 1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_invert_tree(Solution, root_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_invert_tree(result, expected_list)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/invert_binary_tree/solution.py b/leetcode/invert_binary_tree/solution.py new file mode 100644 index 0000000..1e11b18 --- /dev/null +++ b/leetcode/invert_binary_tree/solution.py @@ -0,0 +1,59 @@ +from collections import deque + +from leetcode_py import TreeNode + +# Note: "Fringe" is the general CS term for the data structure holding nodes to be explored. +# Stack (LIFO) → DFS, Queue (FIFO) → BFS, Priority Queue → A*/Best-first search + + +class Solution: + # DFS recursive + # Time: O(n) + # Space: O(h) where h is height of tree + def invert_tree(self, root: TreeNode[int] | None) -> TreeNode[int] | None: + if not root: + return None + + root.left, root.right = self.invert_tree(root.right), self.invert_tree(root.left) + return root + + +class SolutionDFS: + # DFS iterative + # Time: O(n) + # Space: O(h) where h is height of tree + def invert_tree(self, root: TreeNode[int] | None) -> TreeNode[int] | None: + if not root: + return None + + stack: list[TreeNode[int] | None] = [root] + while stack: + node = stack.pop() + if node is None: + continue + node.left, node.right = node.right, node.left + + stack.append(node.left) + stack.append(node.right) + + return root + + +class SolutionBFS: + # Time: O(n) + # Space: O(w) where w is maximum width of tree + def invert_tree(self, root: TreeNode[int] | None) -> TreeNode[int] | None: + if not root: + return None + + queue: deque[TreeNode[int] | None] = deque([root]) + while queue: + node = queue.popleft() + if node is None: + continue + node.left, node.right = node.right, node.left + + queue.append(node.left) + queue.append(node.right) + + return root diff --git a/leetcode/invert_binary_tree/test_solution.py b/leetcode/invert_binary_tree/test_solution.py new file mode 100644 index 0000000..9e8cf8e --- /dev/null +++ b/leetcode/invert_binary_tree/test_solution.py @@ -0,0 +1,32 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_invert_tree, run_invert_tree +from .solution import Solution, SolutionBFS, SolutionDFS + + +class TestInvertBinaryTree: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS]) + @pytest.mark.parametrize( + "root_list, expected_list", + [ + ([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), + ([2, 1, 3], [2, 3, 1]), + ([], []), + ([1], [1]), + ([1, 2], [1, None, 2]), + ([1, None, 2], [1, 2]), + ([1, 2, 3, 4, 5], [1, 3, 2, None, None, 5, 4]), + ([1, 2, 3, None, None, 4, 5], [1, 3, 2, 5, 4]), + ], + ) + def test_invert_tree( + self, root_list: list[int | None], expected_list: list[int | None], solution_class: type + ): + result = run_invert_tree(solution_class, root_list) + assert_invert_tree(result, expected_list) diff --git a/leetcode/lru_cache/README.md b/leetcode/lru_cache/README.md new file mode 100644 index 0000000..7037dbc --- /dev/null +++ b/leetcode/lru_cache/README.md @@ -0,0 +1,50 @@ +# LRU Cache + +**Difficulty:** Medium +**Topics:** Hash Table, Linked List, Design, Doubly-Linked List +**Tags:** grind-75 + +**LeetCode:** [Problem 146](https://leetcode.com/problems/lru-cache/description/) + +## Problem Description + +Design a data structure that follows the constraints of a Least Recently Used (LRU) cache. + +Implement the `LRUCache` class: + +- `LRUCache(int capacity)` Initialize the LRU cache with positive size capacity +- `int get(int key)` Return the value of the key if the key exists, otherwise return -1 +- `void put(int key, int value)` Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key + +The functions `get` and `put` must each run in `O(1)` average time complexity. + +## Examples + +### Example 1: + +``` +Input +["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"] +[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]] +Output +[null, null, null, 1, null, -1, null, -1, 3, 4] + +Explanation +LRUCache lRUCache = new LRUCache(2); +lRUCache.put(1, 1); // cache is {1=1} +lRUCache.put(2, 2); // cache is {1=1, 2=2} +lRUCache.get(1); // return 1 +lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3} +lRUCache.get(2); // returns -1 (not found) +lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3} +lRUCache.get(1); // return -1 (not found) +lRUCache.get(3); // return 3 +lRUCache.get(4); // return 4 +``` + +## Constraints + +- 1 <= capacity <= 3000 +- 0 <= key <= 10^4 +- 0 <= value <= 10^5 +- At most 2 \* 10^5 calls will be made to get and put diff --git a/leetcode/lru_cache/__init__.py b/leetcode/lru_cache/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/lru_cache/helpers.py b/leetcode/lru_cache/helpers.py new file mode 100644 index 0000000..3751626 --- /dev/null +++ b/leetcode/lru_cache/helpers.py @@ -0,0 +1,18 @@ +def run_lru_cache(solution_class: type, operations: list[str], inputs: list[list[int]]): + cache = None + results: list[int | None] = [] + for i, op in enumerate(operations): + if op == "LRUCache": + cache = solution_class(inputs[i][0]) + results.append(None) + elif op == "get" and cache is not None: + results.append(cache.get(inputs[i][0])) + elif op == "put" and cache is not None: + cache.put(inputs[i][0], inputs[i][1]) + results.append(None) + return results, cache + + +def assert_lru_cache(result: list[int | None], expected: list[int | None]) -> bool: + assert result == expected + return True diff --git a/leetcode/lru_cache/playground.ipynb b/leetcode/lru_cache/playground.ipynb new file mode 100644 index 0000000..0387f35 --- /dev/null +++ b/leetcode/lru_cache/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_lru_cache, run_lru_cache\n", + "from solution import LRUCache" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "operations = [\"LRUCache\", \"put\", \"put\", \"get\", \"put\", \"get\", \"put\", \"get\", \"get\", \"get\"]\n", + "inputs = [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\n", + "expected = [None, None, None, 1, None, -1, None, -1, 3, 4]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result, cache = run_lru_cache(LRUCache, operations, inputs)\n", + "print(result)\n", + "cache" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_lru_cache(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/lru_cache/solution.py b/leetcode/lru_cache/solution.py new file mode 100644 index 0000000..c0bcd7a --- /dev/null +++ b/leetcode/lru_cache/solution.py @@ -0,0 +1,108 @@ +from collections import OrderedDict + +from leetcode_py.data_structures.doubly_list_node import DoublyListNode + + +class LRUCache: + # Space: O(capacity) + def __init__(self, capacity: int) -> None: + self.capacity = capacity + self.cache: OrderedDict[int, int] = OrderedDict() + + # Time: O(1) + # Space: O(1) + def get(self, key: int) -> int: + if key not in self.cache: + return -1 + + # Move to end (most recent) + self.cache.move_to_end(key) + return self.cache[key] + + # Time: O(1) + # Space: O(1) + def put(self, key: int, value: int) -> None: + if key in self.cache: + # Update existing and move to end + self.cache[key] = value + self.cache.move_to_end(key) + else: + # Add new + if len(self.cache) >= self.capacity: + # Remove LRU (first item) + self.cache.popitem(last=False) + + self.cache[key] = value + + +class CacheNode(DoublyListNode[int]): + def __init__(self, key: int = 0, val: int = 0) -> None: + super().__init__(val) + self.key = key + + +class LRUCacheWithDoublyList: + def __init__(self, capacity: int) -> None: + self.capacity = capacity + self.cache: dict[int, CacheNode] = {} + + # Dummy head and tail nodes + self.head = CacheNode() + self.tail = CacheNode() + self.head.next = self.tail + self.tail.prev = self.head + + def _add_node(self, node: CacheNode) -> None: + """Add node right after head""" + node.prev = self.head + node.next = self.head.next + if self.head.next: + self.head.next.prev = node + self.head.next = node + + def _remove_node(self, node: CacheNode) -> None: + """Remove node from list""" + if node.prev: + node.prev.next = node.next + if node.next: + node.next.prev = node.prev + + def _move_to_head(self, node: CacheNode) -> None: + """Move node to head (most recent)""" + self._remove_node(node) + self._add_node(node) + + def _pop_tail(self) -> CacheNode: + """Remove last node before tail""" + last_node = self.tail.prev + assert isinstance(last_node, CacheNode), "Expected CacheNode" + self._remove_node(last_node) + return last_node + + def get(self, key: int) -> int: + node = self.cache.get(key) + if not node: + return -1 + + # Move to head (most recent) + self._move_to_head(node) + return node.val + + def put(self, key: int, value: int) -> None: + node = self.cache.get(key) + + if node: + # Update existing + node.val = value + self._move_to_head(node) + else: + # Add new + new_node = CacheNode(key, value) + + if len(self.cache) >= self.capacity: + # Remove LRU + tail = self._pop_tail() + del self.cache[tail.key] + + self.cache[key] = new_node + self._add_node(new_node) diff --git a/leetcode/lru_cache/test_solution.py b/leetcode/lru_cache/test_solution.py new file mode 100644 index 0000000..5c709c9 --- /dev/null +++ b/leetcode/lru_cache/test_solution.py @@ -0,0 +1,41 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_lru_cache, run_lru_cache +from .solution import LRUCache, LRUCacheWithDoublyList + + +class TestLRUCache: + + @logged_test + @pytest.mark.parametrize("solution_class", [LRUCache, LRUCacheWithDoublyList]) + @pytest.mark.parametrize( + "operations, inputs, expected", + [ + ( + ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"], + [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]], + [None, None, None, 1, None, -1, None, -1, 3, 4], + ), + ( + ["LRUCache", "get", "put", "get", "put", "put", "get", "get"], + [[2], [2], [2, 6], [1], [1, 5], [1, 2], [1], [2]], + [None, -1, None, -1, None, None, 2, 6], + ), + ( + ["LRUCache", "put", "get", "put", "get", "get"], + [[1], [2, 1], [2], [3, 2], [2], [3]], + [None, None, 1, None, -1, 2], + ), + ], + ) + def test_lru_cache( + self, + operations: list[str], + inputs: list[list[int]], + expected: list[int | None], + solution_class: type, + ): + result, _ = run_lru_cache(solution_class, operations, inputs) + assert_lru_cache(result, expected) diff --git a/leetcode/merge_two_sorted_lists/README.md b/leetcode/merge_two_sorted_lists/README.md new file mode 100644 index 0000000..a9721a5 --- /dev/null +++ b/leetcode/merge_two_sorted_lists/README.md @@ -0,0 +1,46 @@ +# Merge Two Sorted Lists + +**Difficulty:** Easy +**Topics:** Linked List, Recursion +**Tags:** grind-75 + +**LeetCode:** [Problem 21](https://leetcode.com/problems/merge-two-sorted-lists/description/) + +## Problem Description + +You are given the heads of two sorted linked lists `list1` and `list2`. + +Merge the two lists into one **sorted** list. The list should be made by splicing together the nodes of the first two lists. + +Return _the head of the merged linked list_. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2020/10/03/merge_ex1.jpg) + +``` +Input: list1 = [1,2,4], list2 = [1,3,4] +Output: [1,1,2,3,4,4] +``` + +### Example 2: + +``` +Input: list1 = [], list2 = [] +Output: [] +``` + +### Example 3: + +``` +Input: list1 = [], list2 = [0] +Output: [0] +``` + +## Constraints + +- The number of nodes in both lists is in the range `[0, 50]`. +- `-100 <= Node.val <= 100` +- Both `list1` and `list2` are sorted in **non-decreasing** order. diff --git a/leetcode/merge_two_sorted_lists/__init__.py b/leetcode/merge_two_sorted_lists/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/merge_two_sorted_lists/helpers.py b/leetcode/merge_two_sorted_lists/helpers.py new file mode 100644 index 0000000..1951bed --- /dev/null +++ b/leetcode/merge_two_sorted_lists/helpers.py @@ -0,0 +1,14 @@ +from leetcode_py import ListNode + + +def run_merge_two_lists(solution_class: type, list1_vals: list[int], list2_vals: list[int]): + list1 = ListNode[int].from_list(list1_vals) + list2 = ListNode[int].from_list(list2_vals) + implementation = solution_class() + return implementation.merge_two_lists(list1, list2) + + +def assert_merge_two_lists(result: ListNode[int] | None, expected_vals: list[int]) -> bool: + expected = ListNode[int].from_list(expected_vals) + assert result == expected + return True diff --git a/leetcode/merge_two_sorted_lists/playground.ipynb b/leetcode/merge_two_sorted_lists/playground.ipynb new file mode 100644 index 0000000..ee8a46d --- /dev/null +++ b/leetcode/merge_two_sorted_lists/playground.ipynb @@ -0,0 +1,172 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_merge_two_lists, run_merge_two_lists\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "list1_vals = [1, 2, 4]\n", + "list2_vals = [1, 3, 4]\n", + "expected_vals = [1, 1, 2, 3, 4, 4]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "run", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_0\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "node_1\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "node_0->node_1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_2\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "node_1->node_2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_3\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "node_2->node_3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_4\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "node_3->node_4\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_5\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "node_4->node_5\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "ListNode([1, 1, 2, 3, 4, 4])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_merge_two_lists(Solution, list1_vals, list2_vals)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "assert", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_merge_two_lists(result, expected_vals)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/merge_two_sorted_lists/solution.py b/leetcode/merge_two_sorted_lists/solution.py new file mode 100644 index 0000000..b61d652 --- /dev/null +++ b/leetcode/merge_two_sorted_lists/solution.py @@ -0,0 +1,24 @@ +from leetcode_py import ListNode + + +class Solution: + # Time: O(m + n) + # Space: O(1) + def merge_two_lists( + self, list1: ListNode[int] | None, list2: ListNode[int] | None + ) -> ListNode[int] | None: + + dummy = ListNode(0) + current = dummy + + while list1 and list2: + if list1.val <= list2.val: + current.next = list1 + list1 = list1.next + else: + current.next = list2 + list2 = list2.next + current = current.next + + current.next = list1 or list2 + return dummy.next diff --git a/leetcode/merge_two_sorted_lists/test_solution.py b/leetcode/merge_two_sorted_lists/test_solution.py new file mode 100644 index 0000000..92b63ad --- /dev/null +++ b/leetcode/merge_two_sorted_lists/test_solution.py @@ -0,0 +1,34 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_merge_two_lists, run_merge_two_lists +from .solution import Solution + + +class TestMergeTwoSortedLists: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "list1_vals, list2_vals, expected_vals", + [ + ([1, 2, 4], [1, 3, 4], [1, 1, 2, 3, 4, 4]), + ([], [], []), + ([], [0], [0]), + ([0], [], [0]), + ([1], [2], [1, 2]), + ([2], [1], [1, 2]), + ([1, 1, 1], [2, 2, 2], [1, 1, 1, 2, 2, 2]), + ([1, 3, 5], [2, 4, 6], [1, 2, 3, 4, 5, 6]), + ([-10, -5, 0], [-8, -3, 1], [-10, -8, -5, -3, 0, 1]), + ([5], [1, 2, 3, 4], [1, 2, 3, 4, 5]), + ([1, 2, 3, 4], [5], [1, 2, 3, 4, 5]), + ], + ) + def test_merge_two_lists( + self, list1_vals: list[int], list2_vals: list[int], expected_vals: list[int] + ): + result = run_merge_two_lists(Solution, list1_vals, list2_vals) + assert_merge_two_lists(result, expected_vals) diff --git a/leetcode/valid_anagram/README.md b/leetcode/valid_anagram/README.md new file mode 100644 index 0000000..1d0cc68 --- /dev/null +++ b/leetcode/valid_anagram/README.md @@ -0,0 +1,34 @@ +# Valid Anagram + +**Difficulty:** Easy +**Topics:** Hash Table, String, Sorting +**Tags:** grind-75 + +**LeetCode:** [Problem 242](https://leetcode.com/problems/valid-anagram/description/) + +## Problem Description + +Given two strings `s` and `t`, return `true` if `t` is an anagram of `s`, and `false` otherwise. + +## Examples + +### Example 1: + +``` +Input: s = "anagram", t = "nagaram" +Output: true +``` + +### Example 2: + +``` +Input: s = "rat", t = "car" +Output: false +``` + +## Constraints + +- 1 <= s.length, t.length <= 5 \* 10^4 +- s and t consist of lowercase English letters. + +**Follow up:** What if the inputs contain Unicode characters? How would you adapt your solution to such a case? diff --git a/leetcode/valid_anagram/__init__.py b/leetcode/valid_anagram/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/valid_anagram/helpers.py b/leetcode/valid_anagram/helpers.py new file mode 100644 index 0000000..6c4f5fe --- /dev/null +++ b/leetcode/valid_anagram/helpers.py @@ -0,0 +1,8 @@ +def run_is_anagram(solution_class: type, s: str, t: str): + implementation = solution_class() + return implementation.is_anagram(s, t) + + +def assert_is_anagram(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/valid_anagram/playground.ipynb b/leetcode/valid_anagram/playground.ipynb new file mode 100644 index 0000000..c178ebe --- /dev/null +++ b/leetcode/valid_anagram/playground.ipynb @@ -0,0 +1,92 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_is_anagram, run_is_anagram\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "s = \"anagram\"\n", + "t = \"nagaram\"\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "run", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_is_anagram(Solution, s, t)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "assert", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_is_anagram(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/valid_anagram/solution.py b/leetcode/valid_anagram/solution.py new file mode 100644 index 0000000..ce6f797 --- /dev/null +++ b/leetcode/valid_anagram/solution.py @@ -0,0 +1,8 @@ +from collections import Counter + + +class Solution: + # Time: O(n) + # Space: O(1) - at most 26 unique characters + def is_anagram(self, s: str, t: str) -> bool: + return Counter(s) == Counter(t) diff --git a/leetcode/valid_anagram/test_solution.py b/leetcode/valid_anagram/test_solution.py new file mode 100644 index 0000000..8a46646 --- /dev/null +++ b/leetcode/valid_anagram/test_solution.py @@ -0,0 +1,39 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_is_anagram, run_is_anagram +from .solution import Solution + + +class TestValidAnagram: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, t, expected", + [ + ("anagram", "nagaram", True), + ("rat", "car", False), + ("listen", "silent", True), + ("hello", "bello", False), + ("", "", True), + ("a", "a", True), + ("a", "b", False), + ("ab", "ba", True), + ("abc", "bca", True), + ("abc", "def", False), + ("aab", "abb", False), + ("aabbcc", "abcabc", True), + ("abcd", "abcde", False), + ("race", "care", True), + ("elbow", "below", True), + ("study", "dusty", True), + ("night", "thing", True), + ("stressed", "desserts", True), + ], + ) + def test_is_anagram(self, s: str, t: str, expected: bool): + result = run_is_anagram(Solution, s, t) + assert_is_anagram(result, expected) From 5815e757909e390c87f7f91d3333aa8ac012c0ff Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 08:26:19 +0700 Subject: [PATCH 08/39] feat: add example.json5 --- .templates/leetcode/examples/example.json5 | 196 +++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 .templates/leetcode/examples/example.json5 diff --git a/.templates/leetcode/examples/example.json5 b/.templates/leetcode/examples/example.json5 new file mode 100644 index 0000000..09c1c24 --- /dev/null +++ b/.templates/leetcode/examples/example.json5 @@ -0,0 +1,196 @@ +{ + // ============================================================================ + // COMPREHENSIVE LEETCODE TEMPLATE EXAMPLE + // ============================================================================ + // This example demonstrates ALL template patterns using valid_anagram as base + // with comprehensive comments showing variations for different problem types. + // + // REFERENCE PROBLEMS (see .templates/leetcode/json/ for complete examples): + // 1. valid_anagram - Basic: string parameters, boolean return + // 2. invert_binary_tree - Tree: TreeNode imports/parameters + // 3. merge_two_sorted_lists - LinkedList: ListNode imports/parameters + // 4. lru_cache - Design: custom class, multiple methods, operations + // 5. implement_trie_prefix_tree - Trie: DictTree inheritance + // ============================================================================ + + // === PROBLEM IDENTIFICATION === + "problem_name": "valid_anagram", // snake_case: used for directory/file names + "solution_class_name": "Solution", // "Solution" for basic problems + // "LRUCache" for design problems + // "Trie(DictTree[str])" for inheritance + "problem_number": "242", // LeetCode problem number as string + "problem_title": "Valid Anagram", // Exact title from LeetCode + "difficulty": "Easy", // Easy, Medium, Hard + "topics": "Hash Table, String, Sorting", // Comma-separated topics from LeetCode + "_tags": { "list": ["grind-75"] }, // Optional: common problem set tags + // Use _tags wrapper for cookiecutter lists + + // === README CONTENT === + // IMPORTANT: Preserve rich HTML content from LeetCode including: + // - Code snippets with backticks: `code` + // - Bold text: **bold** or bold + // - Italic text: *italic* or italic + // - Images: ![Example](https://assets.leetcode.com/uploads/...) + // - HTML formatting:

,
,

    ,
  • , etc. + // - Mathematical expressions and special characters + "readme_description": "Given two strings `s` and `t`, return `true` if `t` is an anagram of `s`, and `false` otherwise.", + + "_readme_examples": { // Use _readme_examples wrapper for cookiecutter lists + "list": [ + { "content": "```\nInput: s = \"anagram\", t = \"nagaram\"\nOutput: true\n```" }, + { "content": "```\nInput: s = \"rat\", t = \"car\"\nOutput: false\n```" } + // For tree problems: Include images + // { "content": "![Example 1](https://assets.leetcode.com/uploads/2021/02/19/tree1.jpg)\n\n```\nInput: root = [4,2,7,1,3,6,9]\nOutput: [4,7,2,9,6,3,1]\n```" } + ] + }, + + "readme_constraints": "- 1 <= s.length, t.length <= 5 * 10^4\n- s and t consist of lowercase English letters.", + "readme_additional": "**Follow up:** What if the inputs contain Unicode characters? How would you adapt your solution to such a case?", + + // === HELPER FUNCTIONS === + // New template system uses helper functions for cleaner test organization + "helpers_imports": "", // Empty for basic problems + // "from leetcode_py import TreeNode" for tree problems + // "from leetcode_py import ListNode" for linked list problems + "helpers_content": "", // Additional helper content if needed + "helpers_run_name": "is_anagram", // Function name matching main method + "helpers_run_signature": "(solution_class: type, s: str, t: str)", + // For tree: "(solution_class: type, root_list: list[int | None])" + // For linked list: "(solution_class: type, list1_vals: list[int], list2_vals: list[int])" + // For design: "(solution_class: type, operations: list[str], inputs: list[list[int]])" + "helpers_run_body": " implementation = solution_class()\n return implementation.is_anagram(s, t)", + // For tree: " root = TreeNode[int].from_list(root_list)\n implementation = solution_class()\n return implementation.invert_tree(root)" + // For design: " cache = None\n results: list[int | None] = []\n # ... operation loop ...\n return results, cache" + "helpers_assert_name": "is_anagram", // Function name matching main method + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + // For tree: "(result: TreeNode[int] | None, expected_list: list[int | None]) -> bool" + // For design: "(result: list[int | None], expected: list[int | None]) -> bool" + "helpers_assert_body": " assert result == expected\n return True", + // For tree: " expected = TreeNode[int].from_list(expected_list)\n assert result == expected\n return True" + + // === SOLUTION TEMPLATE === + "solution_imports": "", // Empty for basic problems + // "from leetcode_py import TreeNode" for tree problems + // "from leetcode_py import ListNode" for linked list problems + // "from leetcode_py.data_structures import DictTree, RecursiveDict" for trie problems + "solution_contents": "", // Additional content before class definition + "solution_class_content": "", // Content inside class definition (usually empty) + + // === TEST CONFIGURATION === + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_is_anagram, run_is_anagram\nfrom .solution import Solution", + // For design: "from .solution import LRUCache" instead of Solution + "test_content": "", // Additional test content + "test_class_name": "ValidAnagram", // PascalCase: TestClassName for pytest class + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + // Empty for design problems: "" + + // === SOLUTION METHODS === + "_solution_methods": { // Use _solution_methods wrapper for cookiecutter lists + "list": [ + { + "name": "is_anagram", // snake_case method name + "signature": "(self, s: str, t: str) -> bool", // Full method signature with type hints + // For tree: "(self, root: TreeNode[int] | None) -> TreeNode[int] | None" + // For linked list: "(self, list1: ListNode[int] | None, list2: ListNode[int] | None) -> ListNode[int] | None" + "body": " # TODO: Implement is_anagram\n return False" + // For design problems with __init__: + // { "name": "__init__", "signature": "(self, capacity: int) -> None", "body": " # TODO: Initialize\n pass" } + } + ] + }, + + // === TEST HELPER METHODS === + "_test_helper_methods": { // Use _test_helper_methods wrapper for cookiecutter lists + "list": [ + { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } + // Empty list for design problems: [] + ] + }, + + // === TEST METHODS === + "_test_methods": { // Use _test_methods wrapper for cookiecutter lists + "list": [ + { + "name": "test_is_anagram", // test_{method_name} + "signature": "(self, s: str, t: str, expected: bool)", // Method signature with type hints + "parametrize": "s, t, expected", // pytest parametrize parameters + // For tree: "root_list, expected_list" + // For design: "operations, inputs, expected" + "test_cases": "[('anagram', 'nagaram', True), ('rat', 'car', False), ('listen', 'silent', True), ('hello', 'bello', False), ('', '', True), ('a', 'a', True), ('a', 'b', False), ('ab', 'ba', True), ('abc', 'bca', True), ('abc', 'def', False), ('aab', 'abb', False), ('aabbcc', 'abcabc', True), ('abcd', 'abcde', False), ('race', 'care', True), ('elbow', 'below', True), ('study', 'dusty', True), ('night', 'thing', True), ('stressed', 'desserts', True)]", + // For tree: "[([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), ([2, 1, 3], [2, 3, 1]), ([], [])]" + // For design: "[(['LRUCache', 'put', 'get'], [[2], [1, 1], [1]], [None, None, 1])]" + "body": " result = run_is_anagram(Solution, s, t)\n assert_is_anagram(result, expected)" + // For tree: " result = run_invert_tree(Solution, root_list)\n assert_invert_tree(result, expected_list)" + // For design: " result, _ = run_lru_cache(LRUCache, operations, inputs)\n assert_lru_cache(result, expected)" + } + ] + }, + + // === PLAYGROUND NOTEBOOK === + // CRITICAL: Use single quotes for Python strings to avoid JSON escaping issues with Jupyter notebooks + // Double quotes in JSON + cookiecutter + Jupyter notebook = triple escaping issues + // ALWAYS use single quotes: s = 'hello', not s = "hello" + "playground_imports": "from helpers import run_is_anagram, assert_is_anagram\nfrom solution import Solution", + // For tree: "from helpers import run_invert_tree, assert_invert_tree\nfrom solution import Solution\nfrom leetcode_py import TreeNode" + // For design: "from helpers import run_lru_cache, assert_lru_cache\nfrom solution import LRUCache" + "playground_setup": "# Example test case\ns = 'anagram'\nt = 'nagaram'\nexpected = True", + // For tree: "# Example test case\nroot_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\nexpected_list: list[int | None] = [4, 7, 2, 9, 6, 3, 1]" + // For design: "# Example test case\noperations = ['LRUCache', 'put', 'get']\ninputs = [[2], [1, 1], [1]]\nexpected = [None, None, 1]" + "playground_run": "result = run_is_anagram(Solution, s, t)\nresult", + // For tree: "result = run_invert_tree(Solution, root_list)\nresult" + // For design: "result, cache = run_lru_cache(LRUCache, operations, inputs)\nprint(result)\ncache" + "playground_assert": "assert_is_anagram(result, expected)" + // For tree: "assert_invert_tree(result, expected_list)" + // For design: "assert_lru_cache(result, expected)" + + // ============================================================================ + // PROBLEM TYPE VARIATIONS SUMMARY: + // ============================================================================ + // + // BASIC PROBLEMS (valid_anagram): + // - solution_class_name: "Solution" + // - solution_imports: "" + // - Simple method signatures: "(self, s: str, t: str) -> bool" + // - Basic test cases: string/number parameters + // - Playground: single quotes for strings + // + // TREE PROBLEMS (invert_binary_tree): + // - solution_class_name: "Solution" + // - solution_imports: "from leetcode_py import TreeNode" + // - Tree method signatures: "(self, root: TreeNode[int] | None) -> TreeNode[int] | None" + // - Helper functions use TreeNode.from_list() + // - Test cases: list representations of trees + // - Playground: TreeNode imports and list conversions + // + // LINKED LIST PROBLEMS (merge_two_sorted_lists): + // - solution_class_name: "Solution" + // - solution_imports: "from leetcode_py import ListNode" + // - List method signatures: "(self, list1: ListNode[int] | None, list2: ListNode[int] | None) -> ListNode[int] | None" + // - Helper functions use ListNode.from_list() + // - Test cases: list representations of linked lists + // - Playground: ListNode imports and list conversions + // + // DESIGN PROBLEMS (lru_cache): + // - solution_class_name: "LRUCache" (custom class name) + // - Multiple methods including __init__ + // - Operations-based testing: operations, inputs, expected arrays + // - Complex test body with operation loops + // - Helper functions return (results, instance) for debugging + // - Playground: print results, return instance + // - test_class_content: "" (no setup_method) + // + // INHERITANCE PROBLEMS (implement_trie_prefix_tree): + // - solution_class_name: "Trie(DictTree[str])" (with inheritance) + // - solution_imports: "from leetcode_py.data_structures import DictTree, RecursiveDict" + // - Custom class with inheritance from DictTree + // - Operations-based testing like design problems + // - Helper functions return (results, instance) for debugging + // + // MULTIPLE SOLUTIONS (invert_binary_tree, lru_cache): + // - Add parametrize for solution classes in test files: + // @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS]) + // @pytest.mark.parametrize("solution_class", [LRUCache, LRUCacheWithDoublyList]) + // - Update test method signature to include solution_class parameter + // - Import all solution classes in test file + // ============================================================================ +} From eae0327073a00130aa29957ab59cb1b7a454d2e1 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 08:31:41 +0700 Subject: [PATCH 09/39] ci: add test-reproducibility --- .../workflows/ci-template-reproducibility.yml | 60 +++++++++++++++++++ Makefile | 4 +- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci-template-reproducibility.yml diff --git a/.github/workflows/ci-template-reproducibility.yml b/.github/workflows/ci-template-reproducibility.yml new file mode 100644 index 0000000..60d8230 --- /dev/null +++ b/.github/workflows/ci-template-reproducibility.yml @@ -0,0 +1,60 @@ +name: ci + +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize, reopened] + +jobs: + test-reproducibility: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Set up Python + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + with: + python-version: "3.13" + + - name: Install Poetry + uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + + - name: Load cached venv + id: cached-poetry-dependencies + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: .venv + key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies + if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' + run: poetry install --no-interaction --no-ansi + + - name: Cache Graphviz + id: cache-graphviz + uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + with: + path: /usr/bin/dot + key: graphviz-${{ runner.os }} + + - name: Install Graphviz + if: steps.cache-graphviz.outputs.cache-hit != 'true' + run: sudo apt-get update && sudo apt-get install -y graphviz + + - name: Delete existing problems + run: rm -rf leetcode/*/ + + - name: Regenerate all problems from templates + run: make gen-all-problems FORCE=1 + env: + # Skip interactive confirmation + CI: true + + - name: Run linting to verify reproducibility + run: make lint diff --git a/Makefile b/Makefile index 4d16584..be89194 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,9 @@ p-del: # Generate All Problems - useful for people who fork this repo gen-all-problems: @echo "This will DELETE all existing problems and regenerate from JSON templates." - @read -p "Are you sure? (y/N): " confirm && [ "$$confirm" = "y" ] || exit 1 + @if [ "$$CI" != "true" ]; then \ + read -p "Are you sure? (y/N): " confirm && [ "$$confirm" = "y" ] || exit 1; \ + fi @echo "Deleting existing problems..." @rm -rf leetcode/*/ @echo "Generating all problems..." From 45c04710b97d9c12596dc4e6882d6629d132992a Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 08:33:58 +0700 Subject: [PATCH 10/39] ci: add test-reproducibility --- ...i-template-reproducibility.yml => ci-test-reproducibility.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{ci-template-reproducibility.yml => ci-test-reproducibility.yml} (100%) diff --git a/.github/workflows/ci-template-reproducibility.yml b/.github/workflows/ci-test-reproducibility.yml similarity index 100% rename from .github/workflows/ci-template-reproducibility.yml rename to .github/workflows/ci-test-reproducibility.yml From e0344b448047ccb9bfad68f5c7cd710a472fe858 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 08:37:45 +0700 Subject: [PATCH 11/39] ci: add test-reproducibility --- .github/workflows/ci-test-reproducibility.yml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci-test-reproducibility.yml b/.github/workflows/ci-test-reproducibility.yml index 60d8230..9a8c546 100644 --- a/.github/workflows/ci-test-reproducibility.yml +++ b/.github/workflows/ci-test-reproducibility.yml @@ -36,16 +36,16 @@ jobs: if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-ansi - - name: Cache Graphviz - id: cache-graphviz - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 - with: - path: /usr/bin/dot - key: graphviz-${{ runner.os }} - - - name: Install Graphviz - if: steps.cache-graphviz.outputs.cache-hit != 'true' - run: sudo apt-get update && sudo apt-get install -y graphviz + # - name: Cache Graphviz + # id: cache-graphviz + # uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + # with: + # path: /usr/bin/dot + # key: graphviz-${{ runner.os }} + + # - name: Install Graphviz + # if: steps.cache-graphviz.outputs.cache-hit != 'true' + # run: sudo apt-get update && sudo apt-get install -y graphviz - name: Delete existing problems run: rm -rf leetcode/*/ From fd9db3d4ea5a25fdd44f30b6df26fdaa9e1e4fde Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 08:40:19 +0700 Subject: [PATCH 12/39] ci: add test-reproducibility --- .github/workflows/ci-test-reproducibility.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci-test-reproducibility.yml b/.github/workflows/ci-test-reproducibility.yml index 9a8c546..f41574a 100644 --- a/.github/workflows/ci-test-reproducibility.yml +++ b/.github/workflows/ci-test-reproducibility.yml @@ -33,7 +33,6 @@ jobs: key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} - name: Install dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-ansi # - name: Cache Graphviz From 4e259b527d7c0dcc28c860a7dbd25cb03c923d6a Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 13:15:58 +0700 Subject: [PATCH 13/39] feat: update --- .../plans/migrate_problems_to_new_template.md | 9 ++- .github/workflows/ci-test-reproducibility.yml | 11 --- .templates/leetcode/json/add_binary.json | 60 ++++++++++++++++ Makefile | 2 +- leetcode/add_binary/README.md | 33 +++++++++ leetcode/add_binary/__init__.py | 0 leetcode/add_binary/helpers.py | 8 +++ leetcode/add_binary/playground.ipynb | 69 +++++++++++++++++++ leetcode/add_binary/solution.py | 26 +++++++ leetcode/add_binary/test_solution.py | 32 +++++++++ 10 files changed, 236 insertions(+), 14 deletions(-) create mode 100644 .templates/leetcode/json/add_binary.json create mode 100644 leetcode/add_binary/README.md create mode 100644 leetcode/add_binary/__init__.py create mode 100644 leetcode/add_binary/helpers.py create mode 100644 leetcode/add_binary/playground.ipynb create mode 100644 leetcode/add_binary/solution.py create mode 100644 leetcode/add_binary/test_solution.py diff --git a/.amazonq/plans/migrate_problems_to_new_template.md b/.amazonq/plans/migrate_problems_to_new_template.md index bd20faf..1220f11 100644 --- a/.amazonq/plans/migrate_problems_to_new_template.md +++ b/.amazonq/plans/migrate_problems_to_new_template.md @@ -4,7 +4,7 @@ Migrate problems from `.templates/leetcode/json_old/` to `.templates/leetcode/js ## Migration Steps -1. **Create JSON**: Analyze old format, create new template in `.templates/leetcode/json/` +1. **Create JSON**: Analyze old format, create new template in `.templates/leetcode/json/` (reference `.templates/leetcode/examples/` for format) 2. **Generate**: `make p-gen PROBLEM=` 3. **Lint**: `make p-lint PROBLEM=` (fix JSON if fails, regenerate with `FORCE=1`) 4. **Implement**: Look at `leetcode_old/` code - copy solution with all comments/notes and review test cases @@ -12,7 +12,12 @@ Migrate problems from `.templates/leetcode/json_old/` to `.templates/leetcode/js - **Multiple solutions**: If old code has alternative implementations (e.g., Solution, SolutionDFS, SolutionBFS), add parametrize to test all classes (see lru_cache as example) 5. **Test**: `make p-test PROBLEM=` -6. **Enhance tests**: If only 2-3 cases, add edge cases (update JSON → regenerate → lint → test) +6. **Enhance tests**: Review test coverage - check if cases cover edge scenarios: + - Boundary values (min/max constraints, empty inputs) + - Different input sizes/lengths + - Zero/null cases, single elements + - Error conditions, special values + - If only 2-3 basic cases, add comprehensive edge cases (update JSON → regenerate with `FORCE=1` → lint → re-copy solution → test) ## Progress diff --git a/.github/workflows/ci-test-reproducibility.yml b/.github/workflows/ci-test-reproducibility.yml index f41574a..42c71c2 100644 --- a/.github/workflows/ci-test-reproducibility.yml +++ b/.github/workflows/ci-test-reproducibility.yml @@ -35,17 +35,6 @@ jobs: - name: Install dependencies run: poetry install --no-interaction --no-ansi - # - name: Cache Graphviz - # id: cache-graphviz - # uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 - # with: - # path: /usr/bin/dot - # key: graphviz-${{ runner.os }} - - # - name: Install Graphviz - # if: steps.cache-graphviz.outputs.cache-hit != 'true' - # run: sudo apt-get update && sudo apt-get install -y graphviz - - name: Delete existing problems run: rm -rf leetcode/*/ diff --git a/.templates/leetcode/json/add_binary.json b/.templates/leetcode/json/add_binary.json new file mode 100644 index 0000000..5b2e293 --- /dev/null +++ b/.templates/leetcode/json/add_binary.json @@ -0,0 +1,60 @@ +{ + "problem_name": "add_binary", + "solution_class_name": "Solution", + "problem_number": "67", + "problem_title": "Add Binary", + "difficulty": "Easy", + "topics": "Math, String, Bit Manipulation, Simulation", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given two binary strings `a` and `b`, return *their sum as a binary string*.", + "_readme_examples": { + "list": [ + { "content": "```\nInput: a = \"11\", b = \"1\"\nOutput: \"100\"\n```" }, + { "content": "```\nInput: a = \"1010\", b = \"1011\"\nOutput: \"10101\"\n```" } + ] + }, + "readme_constraints": "- `1 <= a.length, b.length <= 10^4`\n- `a` and `b` consist only of `'0'` or `'1'` characters.\n- Each string does not contain leading zeros except for the zero itself.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "add_binary", + "helpers_run_signature": "(solution_class: type, a: str, b: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.add_binary(a, b)", + "helpers_assert_name": "add_binary", + "helpers_assert_signature": "(result: str, expected: str) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_add_binary, run_add_binary\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "AddBinary", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "add_binary", + "signature": "(self, a: str, b: str) -> str", + "body": " # TODO: Implement add_binary\n return \"\"" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_add_binary", + "signature": "(self, a: str, b: str, expected: str)", + "parametrize": "a, b, expected", + "test_cases": "[('11', '1', '100'), ('1010', '1011', '10101'), ('0', '0', '0'), ('1', '1', '10'), ('1111', '1111', '11110'), ('1', '0', '1'), ('0', '1', '1'), ('1', '111', '1000'), ('111', '1', '1000'), ('1010', '1', '1011'), ('1111', '1', '10000')]", + "body": " result = run_add_binary(Solution, a, b)\n assert_add_binary(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_add_binary, assert_add_binary\nfrom solution import Solution", + "playground_setup": "# Example test case\na = '11'\nb = '1'\nexpected = '100'", + "playground_run": "result = run_add_binary(Solution, a, b)\nresult", + "playground_assert": "assert_add_binary(result, expected)" +} diff --git a/Makefile b/Makefile index be89194..33b7819 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VERSION = 3.13 -PROBLEM ?= accounts_merge +PROBLEM ?= add_binary FORCE ?= 0 COMMA := , diff --git a/leetcode/add_binary/README.md b/leetcode/add_binary/README.md new file mode 100644 index 0000000..92224a0 --- /dev/null +++ b/leetcode/add_binary/README.md @@ -0,0 +1,33 @@ +# Add Binary + +**Difficulty:** Easy +**Topics:** Math, String, Bit Manipulation, Simulation +**Tags:** grind-75 + +**LeetCode:** [Problem 67](https://leetcode.com/problems/add-binary/description/) + +## Problem Description + +Given two binary strings `a` and `b`, return _their sum as a binary string_. + +## Examples + +### Example 1: + +``` +Input: a = "11", b = "1" +Output: "100" +``` + +### Example 2: + +``` +Input: a = "1010", b = "1011" +Output: "10101" +``` + +## Constraints + +- `1 <= a.length, b.length <= 10^4` +- `a` and `b` consist only of `'0'` or `'1'` characters. +- Each string does not contain leading zeros except for the zero itself. diff --git a/leetcode/add_binary/__init__.py b/leetcode/add_binary/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/add_binary/helpers.py b/leetcode/add_binary/helpers.py new file mode 100644 index 0000000..b59b32c --- /dev/null +++ b/leetcode/add_binary/helpers.py @@ -0,0 +1,8 @@ +def run_add_binary(solution_class: type, a: str, b: str): + implementation = solution_class() + return implementation.add_binary(a, b) + + +def assert_add_binary(result: str, expected: str) -> bool: + assert result == expected + return True diff --git a/leetcode/add_binary/playground.ipynb b/leetcode/add_binary/playground.ipynb new file mode 100644 index 0000000..0b2a93d --- /dev/null +++ b/leetcode/add_binary/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_add_binary, run_add_binary\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "a = \"11\"\n", + "b = \"1\"\n", + "expected = \"100\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_add_binary(Solution, a, b)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_add_binary(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/add_binary/solution.py b/leetcode/add_binary/solution.py new file mode 100644 index 0000000..36ae857 --- /dev/null +++ b/leetcode/add_binary/solution.py @@ -0,0 +1,26 @@ +class Solution: + # Time: O(len(a) + len(b)) + # Space: O(len(a) + len(b)) + def add_binary(self, a: str, b: str) -> str: + # int(a, 2) converts binary string to decimal: int("11", 2) → 3 + # bin() converts decimal to binary string with prefix: bin(3) → '0b11' + # [2:] removes the '0b' prefix to get just binary digits + return bin(int(a, 2) + int(b, 2))[2:] + + +# Python Base Conversion: +# +# String → Integer (using int() with base parameter): +# - int("1010", 2) → 10 (binary to decimal) +# - int("ff", 16) → 255 (hex to decimal) +# - int("10", 8) → 8 (octal to decimal) +# +# Integer → String (conversion functions add prefixes): +# - bin(10) → '0b1010' (binary with '0b' prefix) +# - hex(255) → '0xff' (hex with '0x' prefix) +# - oct(8) → '0o10' (octal with '0o' prefix) +# +# These prefixes match Python literal syntax: +# - 0b1010 = 10, 0xff = 255, 0o10 = 8 +# +# For string problems, slice off the prefix: bin(n)[2:] gives just the digits. diff --git a/leetcode/add_binary/test_solution.py b/leetcode/add_binary/test_solution.py new file mode 100644 index 0000000..585a246 --- /dev/null +++ b/leetcode/add_binary/test_solution.py @@ -0,0 +1,32 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_add_binary, run_add_binary +from .solution import Solution + + +class TestAddBinary: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "a, b, expected", + [ + ("11", "1", "100"), + ("1010", "1011", "10101"), + ("0", "0", "0"), + ("1", "1", "10"), + ("1111", "1111", "11110"), + ("1", "0", "1"), + ("0", "1", "1"), + ("1", "111", "1000"), + ("111", "1", "1000"), + ("1010", "1", "1011"), + ("1111", "1", "10000"), + ], + ) + def test_add_binary(self, a: str, b: str, expected: str): + result = run_add_binary(Solution, a, b) + assert_add_binary(result, expected) From 816db0aa7bbb8c27ce8476c8823fda65deeaefc9 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 14:09:54 +0700 Subject: [PATCH 14/39] feat: add more problems --- .amazonq/plans/create_template_example.md | 51 ----- .../plans/migrate_problems_to_new_template.md | 16 +- .../leetcode/json/balanced_binary_tree.json | 65 +++++++ .../leetcode/json/basic_calculator.json | 61 ++++++ .../json/best_time_to_buy_and_sell_stock.json | 64 ++++++ .templates/leetcode/json/binary_search.json | 64 ++++++ .../binary_tree_level_order_traversal.json | 63 ++++++ .../json/binary_tree_right_side_view.json | 66 +++++++ Makefile | 6 +- leetcode/balanced_binary_tree/README.md | 45 +++++ leetcode/balanced_binary_tree/__init__.py | 0 leetcode/balanced_binary_tree/helpers.py | 12 ++ .../balanced_binary_tree/playground.ipynb | 183 ++++++++++++++++++ leetcode/balanced_binary_tree/solution.py | 20 ++ .../balanced_binary_tree/test_solution.py | 29 +++ leetcode/basic_calculator/README.md | 46 +++++ leetcode/basic_calculator/__init__.py | 0 leetcode/basic_calculator/helpers.py | 8 + leetcode/basic_calculator/playground.ipynb | 91 +++++++++ leetcode/basic_calculator/solution.py | 61 ++++++ leetcode/basic_calculator/test_solution.py | 67 +++++++ .../best_time_to_buy_and_sell_stock/README.md | 41 ++++ .../__init__.py | 0 .../helpers.py | 8 + .../playground.ipynb | 91 +++++++++ .../solution.py | 12 ++ .../test_solution.py | 29 +++ leetcode/binary_search/README.md | 40 ++++ leetcode/binary_search/__init__.py | 0 leetcode/binary_search/helpers.py | 8 + leetcode/binary_search/playground.ipynb | 69 +++++++ leetcode/binary_search/solution.py | 17 ++ leetcode/binary_search/test_solution.py | 34 ++++ .../README.md | 41 ++++ .../__init__.py | 0 .../helpers.py | 12 ++ .../playground.ipynb | 70 +++++++ .../solution.py | 31 +++ .../test_solution.py | 37 ++++ .../binary_tree_right_side_view/README.md | 50 +++++ .../binary_tree_right_side_view/__init__.py | 0 .../binary_tree_right_side_view/helpers.py | 12 ++ .../playground.ipynb | 93 +++++++++ .../binary_tree_right_side_view/solution.py | 67 +++++++ .../test_solution.py | 28 +++ 45 files changed, 1748 insertions(+), 60 deletions(-) delete mode 100644 .amazonq/plans/create_template_example.md create mode 100644 .templates/leetcode/json/balanced_binary_tree.json create mode 100644 .templates/leetcode/json/basic_calculator.json create mode 100644 .templates/leetcode/json/best_time_to_buy_and_sell_stock.json create mode 100644 .templates/leetcode/json/binary_search.json create mode 100644 .templates/leetcode/json/binary_tree_level_order_traversal.json create mode 100644 .templates/leetcode/json/binary_tree_right_side_view.json create mode 100644 leetcode/balanced_binary_tree/README.md create mode 100644 leetcode/balanced_binary_tree/__init__.py create mode 100644 leetcode/balanced_binary_tree/helpers.py create mode 100644 leetcode/balanced_binary_tree/playground.ipynb create mode 100644 leetcode/balanced_binary_tree/solution.py create mode 100644 leetcode/balanced_binary_tree/test_solution.py create mode 100644 leetcode/basic_calculator/README.md create mode 100644 leetcode/basic_calculator/__init__.py create mode 100644 leetcode/basic_calculator/helpers.py create mode 100644 leetcode/basic_calculator/playground.ipynb create mode 100644 leetcode/basic_calculator/solution.py create mode 100644 leetcode/basic_calculator/test_solution.py create mode 100644 leetcode/best_time_to_buy_and_sell_stock/README.md create mode 100644 leetcode/best_time_to_buy_and_sell_stock/__init__.py create mode 100644 leetcode/best_time_to_buy_and_sell_stock/helpers.py create mode 100644 leetcode/best_time_to_buy_and_sell_stock/playground.ipynb create mode 100644 leetcode/best_time_to_buy_and_sell_stock/solution.py create mode 100644 leetcode/best_time_to_buy_and_sell_stock/test_solution.py create mode 100644 leetcode/binary_search/README.md create mode 100644 leetcode/binary_search/__init__.py create mode 100644 leetcode/binary_search/helpers.py create mode 100644 leetcode/binary_search/playground.ipynb create mode 100644 leetcode/binary_search/solution.py create mode 100644 leetcode/binary_search/test_solution.py create mode 100644 leetcode/binary_tree_level_order_traversal/README.md create mode 100644 leetcode/binary_tree_level_order_traversal/__init__.py create mode 100644 leetcode/binary_tree_level_order_traversal/helpers.py create mode 100644 leetcode/binary_tree_level_order_traversal/playground.ipynb create mode 100644 leetcode/binary_tree_level_order_traversal/solution.py create mode 100644 leetcode/binary_tree_level_order_traversal/test_solution.py create mode 100644 leetcode/binary_tree_right_side_view/README.md create mode 100644 leetcode/binary_tree_right_side_view/__init__.py create mode 100644 leetcode/binary_tree_right_side_view/helpers.py create mode 100644 leetcode/binary_tree_right_side_view/playground.ipynb create mode 100644 leetcode/binary_tree_right_side_view/solution.py create mode 100644 leetcode/binary_tree_right_side_view/test_solution.py diff --git a/.amazonq/plans/create_template_example.md b/.amazonq/plans/create_template_example.md deleted file mode 100644 index c1523bc..0000000 --- a/.amazonq/plans/create_template_example.md +++ /dev/null @@ -1,51 +0,0 @@ -# Create Template Example - -Create single comprehensive example file with all template patterns. - -## Goal - -Replace multiple examples with one `example.json5` containing `valid_anagram` as base with rich comments for all variations. - -## Structure - -``` -.templates/leetcode/examples/ -└── example.json5 # Comprehensive template with all patterns -``` - -## Content Plan - -Use `valid_anagram` as working example with comments showing all patterns. - -## Reference Problems (5 total) - -1. **`valid_anagram`** - Basic: string parameters, boolean return -2. **`invert_binary_tree`** - Tree: TreeNode imports/parameters -3. **`merge_two_sorted_lists`** - LinkedList: ListNode imports/parameters -4. **`lru_cache`** - Design: custom class, multiple methods, operations -5. **`implement_trie_prefix_tree`** - Trie: DictTree inheritance - -## Implementation - -**Prerequisites:** Complete these 5 problems first using `.amazonq/plans/migrate_problems_to_new_template.md`: - -1. **`valid_anagram`** - Follow migration steps 1-8 -2. **`invert_binary_tree`** - Follow migration steps 1-8 -3. **`merge_two_sorted_lists`** - Follow migration steps 1-8 -4. **`lru_cache`** - Follow migration steps 1-8 -5. **`implement_trie_prefix_tree`** - Follow migration steps 1-8 - -**Then create example:** - -1. Create `.templates/leetcode/examples/example.json5` -2. Use `valid_anagram` as base structure -3. Add comprehensive comments showing variations from the other 4 problems -4. Test generation works with the example - -## Key Sections - -- Problem identification (basic vs design class names) -- Imports (empty, TreeNode, ListNode, DictTree) -- Methods (single vs multiple, **init** patterns) -- Tests (simple vs operations-based) -- Playground (single quotes for strings) diff --git a/.amazonq/plans/migrate_problems_to_new_template.md b/.amazonq/plans/migrate_problems_to_new_template.md index 1220f11..2153a13 100644 --- a/.amazonq/plans/migrate_problems_to_new_template.md +++ b/.amazonq/plans/migrate_problems_to_new_template.md @@ -4,20 +4,24 @@ Migrate problems from `.templates/leetcode/json_old/` to `.templates/leetcode/js ## Migration Steps +**Note**: Commands require UI confirmation popup - click "Run" when prompted for `make` commands. + 1. **Create JSON**: Analyze old format, create new template in `.templates/leetcode/json/` (reference `.templates/leetcode/examples/` for format) 2. **Generate**: `make p-gen PROBLEM=` 3. **Lint**: `make p-lint PROBLEM=` (fix JSON if fails, regenerate with `FORCE=1`) -4. **Implement**: Look at `leetcode_old/` code - copy solution with all comments/notes and review test cases - - **Important**: Re-copy solution after `p-gen` since it overwrites with TODO placeholders - - **Multiple solutions**: If old code has alternative implementations (e.g., Solution, SolutionDFS, SolutionBFS), - add parametrize to test all classes (see lru_cache as example) -5. **Test**: `make p-test PROBLEM=` -6. **Enhance tests**: Review test coverage - check if cases cover edge scenarios: +4. **Enhance tests**: Review test coverage - check if cases cover edge scenarios: + - **Check old tests**: Examine `leetcode_old//tests.py` for comprehensive test coverage + - **Update JSON**: Add relevant/generalizable test cases to JSON template (exclude problem-specific error handling) - Boundary values (min/max constraints, empty inputs) - Different input sizes/lengths - Zero/null cases, single elements - Error conditions, special values - If only 2-3 basic cases, add comprehensive edge cases (update JSON → regenerate with `FORCE=1` → lint → re-copy solution → test) +5. **Implement**: Look at `leetcode_old/` code - copy solution with all comments/notes + - **Important**: Re-copy solution after `p-gen` since it overwrites with TODO placeholders + - **Multiple solutions**: If old code has alternative implementations (e.g., Solution, SolutionDFS, SolutionBFS), + add parametrize to test all classes (see lru_cache as example) +6. **Test**: `make p-test PROBLEM=` (edit JSON and revalidate for reproducibility if needed) ## Progress diff --git a/.templates/leetcode/json/balanced_binary_tree.json b/.templates/leetcode/json/balanced_binary_tree.json new file mode 100644 index 0000000..1d9955d --- /dev/null +++ b/.templates/leetcode/json/balanced_binary_tree.json @@ -0,0 +1,65 @@ +{ + "problem_name": "balanced_binary_tree", + "solution_class_name": "Solution", + "problem_number": "110", + "problem_title": "Balanced Binary Tree", + "difficulty": "Easy", + "topics": "Tree, Depth-First Search, Binary Tree", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given a binary tree, determine if it is **height-balanced**.\n\nA height-balanced binary tree is a binary tree in which the depth of the two subtrees of every node never differs by more than one.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2020/10/06/balance_1.jpg)\n\n```\nInput: root = [3,9,20,null,null,15,7]\nOutput: true\n```" + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2020/10/06/balance_2.jpg)\n\n```\nInput: root = [1,2,2,3,3,null,null,4,4]\nOutput: false\n```" + }, + { "content": "```\nInput: root = []\nOutput: true\n```" } + ] + }, + "readme_constraints": "- The number of nodes in the tree is in the range `[0, 5000]`.\n- `-10^4 <= Node.val <= 10^4`", + "readme_additional": "", + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "", + "helpers_run_name": "is_balanced", + "helpers_run_signature": "(solution_class: type, root_list: list[int | None])", + "helpers_run_body": " implementation = solution_class()\n root = TreeNode.from_list(root_list)\n return implementation.is_balanced(root)", + "helpers_assert_name": "is_balanced", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "from leetcode_py import TreeNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_is_balanced, run_is_balanced\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "BalancedBinaryTree", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "is_balanced", + "signature": "(self, root: TreeNode[int] | None) -> bool", + "body": " # TODO: Implement is_balanced\n return False" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_is_balanced", + "signature": "(self, root_list: list[int | None], expected: bool)", + "parametrize": "root_list, expected", + "test_cases": "[([3, 9, 20, None, None, 15, 7], True), ([1, 2, 2, 3, 3, None, None, 4, 4], False), ([], True), ([1], True), ([1, 2], True), ([1, None, 2], True), ([1, 2, 3, 4], True), ([1, 2, 2, 3, None, None, 3, 4, None, None, 4], False)]", + "body": " result = run_is_balanced(Solution, root_list)\n assert_is_balanced(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_is_balanced, assert_is_balanced\nfrom solution import Solution\nfrom leetcode_py import TreeNode", + "playground_setup": "# Example test case\nroot_list: list[int | None] = [3, 9, 20, None, None, 15, 7]\nexpected = True", + "playground_run": "result = run_is_balanced(Solution, root_list)\nresult", + "playground_assert": "assert_is_balanced(result, expected)" +} diff --git a/.templates/leetcode/json/basic_calculator.json b/.templates/leetcode/json/basic_calculator.json new file mode 100644 index 0000000..6644911 --- /dev/null +++ b/.templates/leetcode/json/basic_calculator.json @@ -0,0 +1,61 @@ +{ + "problem_name": "basic_calculator", + "solution_class_name": "Solution", + "problem_number": "224", + "problem_title": "Basic Calculator", + "difficulty": "Hard", + "topics": "Math, String, Stack, Recursion", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given a string `s` representing a valid expression, implement a basic calculator to evaluate it, and return the result of the evaluation.\n\n**Note:** You are **not** allowed to use any built-in function which evaluates strings as mathematical expressions, such as `eval()`.", + "_readme_examples": { + "list": [ + { "content": "```\nInput: s = \"1 + 1\"\nOutput: 2\n```" }, + { "content": "```\nInput: s = \" 2-1 + 2 \"\nOutput: 3\n```" }, + { "content": "```\nInput: s = \"(1+(4+5+2)-3)+(6+8)\"\nOutput: 23\n```" } + ] + }, + "readme_constraints": "- `1 <= s.length <= 3 * 10^5`\n- `s` consists of digits, `'+'`, `'-'`, `'('`, `')'`, and `' '`.\n- `s` represents a valid expression.\n- `'+'` is **not** used as a unary operation (i.e., `\"+1\"` and `\"+(2 + 3)\"` is invalid).\n- `'-'` could be used as a unary operation (i.e., `\"-1\"` and `\"-(2 + 3)\"` is valid).\n- There will be no two consecutive operators in the input.\n- Every number and running calculation will fit in a signed 32-bit integer.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "calculate", + "helpers_run_signature": "(solution_class: type, s: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.calculate(s)", + "helpers_assert_name": "calculate", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_calculate, run_calculate\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "BasicCalculator", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "calculate", + "signature": "(self, s: str) -> int", + "body": " # TODO: Implement calculate\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_calculate", + "signature": "(self, s: str, expected: int)", + "parametrize": "s, expected", + "test_cases": "[('1 + 1', 2), (' 2-1 + 2 ', 3), ('(1+(4+5+2)-3)+(6+8)', 23), ('1', 1), ('-1', -1), ('-(1+2)', -3), ('2147483647', 2147483647), ('1-1+1', 1), ('0', 0), ('-0', 0), ('1+(2+3)', 6), ('(1+2)+3', 6), ('1-(2+3)', -4), ('(1-2)+3', 2), ('-(-1)', 1), ('-(-(-1))', -1), ('1000000-999999', 1), ('10+20-30+40', 40), ('((1+2)+(3+4))', 10), ('1+(2-(3+4))', -4), ('-(1+(2+3))', -6), (' 1 + 2 ', 3), ('123+456', 579), ('-2147483648', -2147483648)]", + "body": " result = run_calculate(Solution, s)\n assert_calculate(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_calculate, assert_calculate\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = '(1+(4+5+2)-3)+(6+8)'\nexpected = 23", + "playground_run": "result = run_calculate(Solution, s)\nresult", + "playground_assert": "assert_calculate(result, expected)" +} diff --git a/.templates/leetcode/json/best_time_to_buy_and_sell_stock.json b/.templates/leetcode/json/best_time_to_buy_and_sell_stock.json new file mode 100644 index 0000000..1cd4038 --- /dev/null +++ b/.templates/leetcode/json/best_time_to_buy_and_sell_stock.json @@ -0,0 +1,64 @@ +{ + "problem_name": "best_time_to_buy_and_sell_stock", + "solution_class_name": "Solution", + "problem_number": "121", + "problem_title": "Best Time to Buy and Sell Stock", + "difficulty": "Easy", + "topics": "Array, Dynamic Programming", + "_tags": { "list": ["grind-75"] }, + "readme_description": "You are given an array `prices` where `prices[i]` is the price of a given stock on the ith day.\n\nYou want to maximize your profit by choosing a **single day** to buy one stock and choosing a **different day in the future** to sell that stock.\n\nReturn *the maximum profit you can achieve from this transaction*. If you cannot achieve any profit, return `0`.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: prices = [7,1,5,3,6,4]\nOutput: 5\n```\n**Explanation:** Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.\nNote that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell." + }, + { + "content": "```\nInput: prices = [7,6,4,3,1]\nOutput: 0\n```\n**Explanation:** In this case, no transactions are done and the max profit = 0." + } + ] + }, + "readme_constraints": "- 1 <= prices.length <= 10^5\n- 0 <= prices[i] <= 10^4", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "max_profit", + "helpers_run_signature": "(solution_class: type, prices: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.max_profit(prices)", + "helpers_assert_name": "max_profit", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_max_profit, run_max_profit\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "BestTimeToBuyAndSellStock", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "max_profit", + "signature": "(self, prices: list[int]) -> int", + "body": " # TODO: Implement max_profit\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_max_profit", + "signature": "(self, prices: list[int], expected: int)", + "parametrize": "prices, expected", + "test_cases": "[([7, 1, 5, 3, 6, 4], 5), ([7, 6, 4, 3, 1], 0), ([1, 2, 3, 4, 5], 4), ([5, 4, 3, 2, 1], 0), ([1], 0), ([2, 1], 0), ([1, 2], 1), ([3, 2, 6, 5, 0, 3], 4)]", + "body": " result = run_max_profit(Solution, prices)\n assert_max_profit(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_max_profit, assert_max_profit\nfrom solution import Solution", + "playground_setup": "# Example test case\nprices = [7, 1, 5, 3, 6, 4]\nexpected = 5", + "playground_run": "result = run_max_profit(Solution, prices)\nresult", + "playground_assert": "assert_max_profit(result, expected)" +} diff --git a/.templates/leetcode/json/binary_search.json b/.templates/leetcode/json/binary_search.json new file mode 100644 index 0000000..8a15f0d --- /dev/null +++ b/.templates/leetcode/json/binary_search.json @@ -0,0 +1,64 @@ +{ + "problem_name": "binary_search", + "solution_class_name": "Solution", + "problem_number": "704", + "problem_title": "Binary Search", + "difficulty": "Easy", + "topics": "Array, Binary Search", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an array of integers `nums` which is sorted in ascending order, and an integer `target`, write a function to search `target` in `nums`. If `target` exists, then return its index. Otherwise, return `-1`.\n\nYou must write an algorithm with `O(log n)` runtime complexity.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums = [-1,0,3,5,9,12], target = 9\nOutput: 4\n```\n**Explanation:** 9 exists in nums and its index is 4" + }, + { + "content": "```\nInput: nums = [-1,0,3,5,9,12], target = 2\nOutput: -1\n```\n**Explanation:** 2 does not exist in nums so return -1" + } + ] + }, + "readme_constraints": "- `1 <= nums.length <= 10^4`\n- `-10^4 < nums[i], target < 10^4`\n- All the integers in `nums` are **unique**.\n- `nums` is sorted in ascending order.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "search", + "helpers_run_signature": "(solution_class: type, nums: list[int], target: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.search(nums, target)", + "helpers_assert_name": "search", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_search, run_search\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "BinarySearch", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "search", + "signature": "(self, nums: list[int], target: int) -> int", + "body": " # TODO: Implement search\n return -1" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_search", + "signature": "(self, nums: list[int], target: int, expected: int)", + "parametrize": "nums, target, expected", + "test_cases": "[([-1, 0, 3, 5, 9, 12], 9, 4), ([-1, 0, 3, 5, 9, 12], 2, -1), ([5], 5, 0), ([5], -5, -1), ([1, 3, 5, 7, 9], 1, 0), ([1, 3, 5, 7, 9], 9, 4), ([1, 3, 5, 7, 9], 4, -1), ([1, 3], 1, 0), ([1, 3], 3, 1), ([1, 3], 2, -1), ([-5, -2, 0, 3, 7], -2, 1), ([-5, -2, 0, 3, 7], 0, 2), ([-5, -2, 0, 3, 7], -1, -1)]", + "body": " result = run_search(Solution, nums, target)\n assert_search(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_search, assert_search\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [-1, 0, 3, 5, 9, 12]\ntarget = 9\nexpected = 4", + "playground_run": "result = run_search(Solution, nums, target)\nresult", + "playground_assert": "assert_search(result, expected)" +} diff --git a/.templates/leetcode/json/binary_tree_level_order_traversal.json b/.templates/leetcode/json/binary_tree_level_order_traversal.json new file mode 100644 index 0000000..ba006e5 --- /dev/null +++ b/.templates/leetcode/json/binary_tree_level_order_traversal.json @@ -0,0 +1,63 @@ +{ + "problem_name": "binary_tree_level_order_traversal", + "solution_class_name": "Solution", + "problem_number": "102", + "problem_title": "Binary Tree Level Order Traversal", + "difficulty": "Medium", + "topics": "Tree, Breadth-First Search, Binary Tree", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given the `root` of a binary tree, return the level order traversal of its nodes' values. (i.e., from left to right, level by level).", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2021/02/19/tree1.jpg)\n\n```\nInput: root = [3,9,20,null,null,15,7]\nOutput: [[3],[9,20],[15,7]]\n```" + }, + { "content": "```\nInput: root = [1]\nOutput: [[1]]\n```" }, + { "content": "```\nInput: root = []\nOutput: []\n```" } + ] + }, + "readme_constraints": "- The number of nodes in the tree is in the range [0, 2000]\n- -1000 <= Node.val <= 1000", + "readme_additional": "", + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "", + "helpers_run_name": "level_order", + "helpers_run_signature": "(solution_class: type, root_list: list[int | None])", + "helpers_run_body": " implementation = solution_class()\n root = TreeNode.from_list(root_list) if root_list else None\n return implementation.level_order(root)", + "helpers_assert_name": "level_order", + "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "from leetcode_py import TreeNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_level_order, run_level_order\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "BinaryTreeLevelOrderTraversal", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "level_order", + "signature": "(self, root: TreeNode[int] | None) -> list[list[int]]", + "body": " # TODO: Implement level_order\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_level_order", + "signature": "(self, root_list: list[int | None], expected: list[list[int]])", + "parametrize": "root_list, expected", + "test_cases": "[([3, 9, 20, None, None, 15, 7], [[3], [9, 20], [15, 7]]), ([1], [[1]]), ([], []), ([1, 2, 3, 4, 5, 6, 7], [[1], [2, 3], [4, 5, 6, 7]]), ([1, 2, None, 3, None, 4, None, 5], [[1], [2], [3], [4], [5]]), ([1, None, 2, None, 3], [[1], [2], [3]]), ([1, 2, None, 3, None], [[1], [2], [3]]), ([0], [[0]]), ([-1, -2, -3], [[-1], [-2, -3]]), ([1, 2, 3, None, None, None, 4], [[1], [2, 3], [4]]), ([5, 4, 8, 11, None, 13, 4, 7, 2, None, None, None, 1], [[5], [4, 8], [11, 13, 4], [7, 2, 1]]), ([1, 2, 2, 3, 3, 3, 3], [[1], [2, 2], [3, 3, 3, 3]]), ([1, None, None], [[1]])]", + "body": " result = run_level_order(Solution, root_list)\n assert_level_order(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_level_order, assert_level_order\nfrom solution import Solution\nfrom leetcode_py import TreeNode", + "playground_setup": "# Example test case\nroot_list: list[int | None] = [3, 9, 20, None, None, 15, 7]\nexpected = [[3], [9, 20], [15, 7]]", + "playground_run": "result = run_level_order(Solution, root_list)\nresult", + "playground_assert": "assert_level_order(result, expected)" +} diff --git a/.templates/leetcode/json/binary_tree_right_side_view.json b/.templates/leetcode/json/binary_tree_right_side_view.json new file mode 100644 index 0000000..56eba70 --- /dev/null +++ b/.templates/leetcode/json/binary_tree_right_side_view.json @@ -0,0 +1,66 @@ +{ + "problem_name": "binary_tree_right_side_view", + "solution_class_name": "Solution", + "problem_number": "199", + "problem_title": "Binary Tree Right Side View", + "difficulty": "Medium", + "topics": "Tree, Depth-First Search, Breadth-First Search, Binary Tree", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given the `root` of a binary tree, imagine yourself standing on the **right side** of it, return *the values of the nodes you can see ordered from top to bottom*.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2024/11/24/tmpd5jn43fs-1.png)\n\n```\nInput: root = [1,2,3,null,5,null,4]\nOutput: [1,3,4]\n```" + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2024/11/24/tmpkpe40xeh-1.png)\n\n```\nInput: root = [1,2,3,4,null,null,null,5]\nOutput: [1,3,4,5]\n```" + }, + { "content": "```\nInput: root = [1,null,3]\nOutput: [1,3]\n```" }, + { "content": "```\nInput: root = []\nOutput: []\n```" } + ] + }, + "readme_constraints": "- The number of nodes in the tree is in the range `[0, 100]`.\n- `-100 <= Node.val <= 100`", + "readme_additional": "", + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "", + "helpers_run_name": "right_side_view", + "helpers_run_signature": "(solution_class: type, root_list: list[int | None])", + "helpers_run_body": " implementation = solution_class()\n root = TreeNode.from_list(root_list) if root_list else None\n return implementation.right_side_view(root)", + "helpers_assert_name": "right_side_view", + "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "from leetcode_py import TreeNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_right_side_view, run_right_side_view\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "BinaryTreeRightSideView", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "right_side_view", + "signature": "(self, root: TreeNode[int] | None) -> list[int]", + "body": " # TODO: Implement right_side_view\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_right_side_view", + "signature": "(self, root_list: list[int | None], expected: list[int])", + "parametrize": "root_list, expected", + "test_cases": "[([1, 2, 3, None, 5, None, 4], [1, 3, 4]), ([1, 2, 3, 4, None, None, None, 5], [1, 3, 4, 5]), ([1, None, 3], [1, 3]), ([], []), ([1], [1]), ([1, 2], [1, 2]), ([1, None, 2], [1, 2])]", + "body": " result = run_right_side_view(Solution, root_list)\n assert_right_side_view(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_right_side_view, assert_right_side_view\nfrom solution import Solution\nfrom leetcode_py import TreeNode", + "playground_setup": "# Example test case\nroot_list: list[int | None] = [1, 2, 3, None, 5, None, 4]\nexpected = [1, 3, 4]", + "playground_run": "result = run_right_side_view(Solution, root_list)\nresult", + "playground_assert": "assert_right_side_view(result, expected)" +} diff --git a/Makefile b/Makefile index 33b7819..7c12c81 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VERSION = 3.13 -PROBLEM ?= add_binary +PROBLEM ?= binary_tree_right_side_view FORCE ?= 0 COMMA := , @@ -48,12 +48,12 @@ lint: test: - poetry run pytest leetcode_old/ leetcode_ideal/ tests/ \ + poetry run pytest leetcode/ leetcode_old/ leetcode_ideal/ tests/ \ -v --cov=leetcode --cov=leetcode_py \ --cov-report=term-missing \ --cov-report=xml \ --ignore=.templates \ - --ignore=leetcode/__pycache__ + --ignore=leetcode/__pycache__,leetcode_ideal/__pycache__ p-test: @echo "Testing problem: $(PROBLEM)" diff --git a/leetcode/balanced_binary_tree/README.md b/leetcode/balanced_binary_tree/README.md new file mode 100644 index 0000000..776d6aa --- /dev/null +++ b/leetcode/balanced_binary_tree/README.md @@ -0,0 +1,45 @@ +# Balanced Binary Tree + +**Difficulty:** Easy +**Topics:** Tree, Depth-First Search, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 110](https://leetcode.com/problems/balanced-binary-tree/description/) + +## Problem Description + +Given a binary tree, determine if it is **height-balanced**. + +A height-balanced binary tree is a binary tree in which the depth of the two subtrees of every node never differs by more than one. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2020/10/06/balance_1.jpg) + +``` +Input: root = [3,9,20,null,null,15,7] +Output: true +``` + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2020/10/06/balance_2.jpg) + +``` +Input: root = [1,2,2,3,3,null,null,4,4] +Output: false +``` + +### Example 3: + +``` +Input: root = [] +Output: true +``` + +## Constraints + +- The number of nodes in the tree is in the range `[0, 5000]`. +- `-10^4 <= Node.val <= 10^4` diff --git a/leetcode/balanced_binary_tree/__init__.py b/leetcode/balanced_binary_tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/balanced_binary_tree/helpers.py b/leetcode/balanced_binary_tree/helpers.py new file mode 100644 index 0000000..199f407 --- /dev/null +++ b/leetcode/balanced_binary_tree/helpers.py @@ -0,0 +1,12 @@ +from leetcode_py import TreeNode + + +def run_is_balanced(solution_class: type, root_list: list[int | None]): + implementation = solution_class() + root = TreeNode.from_list(root_list) + return implementation.is_balanced(root) + + +def assert_is_balanced(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/balanced_binary_tree/playground.ipynb b/leetcode/balanced_binary_tree/playground.ipynb new file mode 100644 index 0000000..e115cdb --- /dev/null +++ b/leetcode/balanced_binary_tree/playground.ipynb @@ -0,0 +1,183 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_is_balanced, run_is_balanced\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import TreeNode" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root_list: list[int | None] = [3, 9, 20, None, None, 15, 7]\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "run", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_is_balanced(Solution, root_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c01db53b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "0\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "2\n", + "\n", + "20\n", + "\n", + "\n", + "\n", + "0->2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "3\n", + "\n", + "15\n", + "\n", + "\n", + "\n", + "2->3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "4\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "2->4\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "TreeNode([3, 9, 20, None, None, 15, 7])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "root = TreeNode.from_list(root_list)\n", + "root" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "assert", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_is_balanced(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/balanced_binary_tree/solution.py b/leetcode/balanced_binary_tree/solution.py new file mode 100644 index 0000000..e7740f4 --- /dev/null +++ b/leetcode/balanced_binary_tree/solution.py @@ -0,0 +1,20 @@ +from leetcode_py import TreeNode + + +class Solution: + # Time: O(n) + # Space: O(h) + def is_balanced(self, root: TreeNode[int] | None) -> bool: + def height(node: TreeNode[int] | None) -> int: + if not node: + return 0 + + left = height(node.left) + right = height(node.right) + + if left == -1 or right == -1 or abs(left - right) > 1: + return -1 + + return max(left, right) + 1 + + return height(root) != -1 diff --git a/leetcode/balanced_binary_tree/test_solution.py b/leetcode/balanced_binary_tree/test_solution.py new file mode 100644 index 0000000..08eaaa8 --- /dev/null +++ b/leetcode/balanced_binary_tree/test_solution.py @@ -0,0 +1,29 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_is_balanced, run_is_balanced +from .solution import Solution + + +class TestBalancedBinaryTree: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "root_list, expected", + [ + ([3, 9, 20, None, None, 15, 7], True), + ([1, 2, 2, 3, 3, None, None, 4, 4], False), + ([], True), + ([1], True), + ([1, 2], True), + ([1, None, 2], True), + ([1, 2, 3, 4], True), + ([1, 2, 2, 3, None, None, 3, 4, None, None, 4], False), + ], + ) + def test_is_balanced(self, root_list: list[int | None], expected: bool): + result = run_is_balanced(Solution, root_list) + assert_is_balanced(result, expected) diff --git a/leetcode/basic_calculator/README.md b/leetcode/basic_calculator/README.md new file mode 100644 index 0000000..885a6c8 --- /dev/null +++ b/leetcode/basic_calculator/README.md @@ -0,0 +1,46 @@ +# Basic Calculator + +**Difficulty:** Hard +**Topics:** Math, String, Stack, Recursion +**Tags:** grind-75 + +**LeetCode:** [Problem 224](https://leetcode.com/problems/basic-calculator/description/) + +## Problem Description + +Given a string `s` representing a valid expression, implement a basic calculator to evaluate it, and return the result of the evaluation. + +**Note:** You are **not** allowed to use any built-in function which evaluates strings as mathematical expressions, such as `eval()`. + +## Examples + +### Example 1: + +``` +Input: s = "1 + 1" +Output: 2 +``` + +### Example 2: + +``` +Input: s = " 2-1 + 2 " +Output: 3 +``` + +### Example 3: + +``` +Input: s = "(1+(4+5+2)-3)+(6+8)" +Output: 23 +``` + +## Constraints + +- `1 <= s.length <= 3 * 10^5` +- `s` consists of digits, `'+'`, `'-'`, `'('`, `')'`, and `' '`. +- `s` represents a valid expression. +- `'+'` is **not** used as a unary operation (i.e., `"+1"` and `"+(2 + 3)"` is invalid). +- `'-'` could be used as a unary operation (i.e., `"-1"` and `"-(2 + 3)"` is valid). +- There will be no two consecutive operators in the input. +- Every number and running calculation will fit in a signed 32-bit integer. diff --git a/leetcode/basic_calculator/__init__.py b/leetcode/basic_calculator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/basic_calculator/helpers.py b/leetcode/basic_calculator/helpers.py new file mode 100644 index 0000000..295f681 --- /dev/null +++ b/leetcode/basic_calculator/helpers.py @@ -0,0 +1,8 @@ +def run_calculate(solution_class: type, s: str): + implementation = solution_class() + return implementation.calculate(s) + + +def assert_calculate(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/basic_calculator/playground.ipynb b/leetcode/basic_calculator/playground.ipynb new file mode 100644 index 0000000..d7681b7 --- /dev/null +++ b/leetcode/basic_calculator/playground.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_calculate, run_calculate\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "s = \"(1+(4+5+2)-3)+(6+8)\"\n", + "expected = 23" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "run", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "23" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_calculate(Solution, s)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "assert", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_calculate(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/basic_calculator/solution.py b/leetcode/basic_calculator/solution.py new file mode 100644 index 0000000..e441460 --- /dev/null +++ b/leetcode/basic_calculator/solution.py @@ -0,0 +1,61 @@ +class Solution: + # Time: O(n) + # Space: O(n) + def calculate(self, s: str) -> int: + stack = [] + num = 0 + sign = 1 + result = 0 + + for char in s: + if char.isdigit(): + num = num * 10 + int(char) + elif char in "+-": + result += sign * num + num = 0 + sign = 1 if char == "+" else -1 + elif char == "(": + stack.append(result) + stack.append(sign) + result = 0 + sign = 1 + elif char == ")": + if len(stack) < 2: + raise ValueError("Mismatched parentheses") + result += sign * num + num = 0 + result *= stack.pop() + result += stack.pop() + elif char != " ": + raise ValueError(f"Invalid character: '{char}'") + + if stack: + raise ValueError("Mismatched parentheses") + + return result + sign * num + + +# Example walkthrough: "(1+(4+5+2)-3)+(6+8)" = 23 +# +# char | num | sign | result | stack | action +# -----|-----|------|--------|------------|------------------ +# '(' | 0 | 1 | 0 | [0, 1] | push result=0, sign=1 +# '1' | 1 | 1 | 0 | [0, 1] | build num=1 +# '+' | 0 | 1 | 1 | [0, 1] | result += 1*1 = 1 +# '(' | 0 | 1 | 0 | [0,1,1,1] | push result=1, sign=1 +# '4' | 4 | 1 | 0 | [0,1,1,1] | build num=4 +# '+' | 0 | 1 | 4 | [0,1,1,1] | result += 1*4 = 4 +# '5' | 5 | 1 | 4 | [0,1,1,1] | build num=5 +# '+' | 0 | 1 | 9 | [0,1,1,1] | result += 1*5 = 9 +# '2' | 2 | 1 | 9 | [0,1,1,1] | build num=2 +# ')' | 0 | 1 | 11 | [0, 1] | result=11*1+1 = 12 +# '-' | 0 | -1 | 12 | [0, 1] | sign = -1 +# '3' | 3 | -1 | 12 | [0, 1] | build num=3 +# ')' | 0 | 1 | 9 | [] | result=9*1+0 = 9 +# '+' | 0 | 1 | 9 | [] | sign = 1 +# '(' | 0 | 1 | 0 | [9, 1] | push result=9, sign=1 +# '6' | 6 | 1 | 0 | [9, 1] | build num=6 +# '+' | 0 | 1 | 6 | [9, 1] | result += 1*6 = 6 +# '8' | 8 | 1 | 6 | [9, 1] | build num=8 +# ')' | 0 | 1 | 14 | [] | result=14*1+9 = 23 +# end | 0 | 1 | 14 | [] | return 14+1*0 = 23 diff --git a/leetcode/basic_calculator/test_solution.py b/leetcode/basic_calculator/test_solution.py new file mode 100644 index 0000000..8a67479 --- /dev/null +++ b/leetcode/basic_calculator/test_solution.py @@ -0,0 +1,67 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_calculate, run_calculate +from .solution import Solution + + +class TestBasicCalculator: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, expected", + [ + ("1 + 1", 2), + (" 2-1 + 2 ", 3), + ("(1+(4+5+2)-3)+(6+8)", 23), + ("1", 1), + ("-1", -1), + ("-(1+2)", -3), + ("2147483647", 2147483647), + ("1-1+1", 1), + # Additional edge cases from old tests + ("0", 0), + ("-0", 0), + ("1+(2+3)", 6), + ("(1+2)+3", 6), + ("1-(2+3)", -4), + ("(1-2)+3", 2), + ("-(-1)", 1), + ("-(-(-1))", -1), + ("1000000-999999", 1), + ("10+20-30+40", 40), + ("((1+2)+(3+4))", 10), + ("1+(2-(3+4))", -4), + ("-(1+(2+3))", -6), + (" 1 + 2 ", 3), + ("123+456", 579), + ("-2147483648", -2147483648), + ], + ) + def test_calculate(self, s: str, expected: int): + result = run_calculate(Solution, s) + assert_calculate(result, expected) + + @logged_test + @pytest.mark.parametrize( + "s, error_msg", + [ + ("(1+2", "Mismatched parentheses"), + ("1+2)", "Mismatched parentheses"), + ("((1+2)", "Mismatched parentheses"), + ("1+2))", "Mismatched parentheses"), + ("1*2", r"Invalid character: '\*'"), + ("1/2", "Invalid character: '/'"), + ("1%2", "Invalid character: '%'"), + ("1^2", r"Invalid character: '\^'"), + ("1&2", "Invalid character: '&'"), + ("a+b", "Invalid character: 'a'"), + ("1+2.5", r"Invalid character: '\.'"), + ], + ) + def test_calculate_invalid_input(self, s: str, error_msg: str): + with pytest.raises(ValueError, match=error_msg): + self.solution.calculate(s) diff --git a/leetcode/best_time_to_buy_and_sell_stock/README.md b/leetcode/best_time_to_buy_and_sell_stock/README.md new file mode 100644 index 0000000..a18f45b --- /dev/null +++ b/leetcode/best_time_to_buy_and_sell_stock/README.md @@ -0,0 +1,41 @@ +# Best Time to Buy and Sell Stock + +**Difficulty:** Easy +**Topics:** Array, Dynamic Programming +**Tags:** grind-75 + +**LeetCode:** [Problem 121](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/) + +## Problem Description + +You are given an array `prices` where `prices[i]` is the price of a given stock on the ith day. + +You want to maximize your profit by choosing a **single day** to buy one stock and choosing a **different day in the future** to sell that stock. + +Return _the maximum profit you can achieve from this transaction_. If you cannot achieve any profit, return `0`. + +## Examples + +### Example 1: + +``` +Input: prices = [7,1,5,3,6,4] +Output: 5 +``` + +**Explanation:** Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5. +Note that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell. + +### Example 2: + +``` +Input: prices = [7,6,4,3,1] +Output: 0 +``` + +**Explanation:** In this case, no transactions are done and the max profit = 0. + +## Constraints + +- 1 <= prices.length <= 10^5 +- 0 <= prices[i] <= 10^4 diff --git a/leetcode/best_time_to_buy_and_sell_stock/__init__.py b/leetcode/best_time_to_buy_and_sell_stock/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/best_time_to_buy_and_sell_stock/helpers.py b/leetcode/best_time_to_buy_and_sell_stock/helpers.py new file mode 100644 index 0000000..90c2b1e --- /dev/null +++ b/leetcode/best_time_to_buy_and_sell_stock/helpers.py @@ -0,0 +1,8 @@ +def run_max_profit(solution_class: type, prices: list[int]): + implementation = solution_class() + return implementation.max_profit(prices) + + +def assert_max_profit(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/best_time_to_buy_and_sell_stock/playground.ipynb b/leetcode/best_time_to_buy_and_sell_stock/playground.ipynb new file mode 100644 index 0000000..81034bf --- /dev/null +++ b/leetcode/best_time_to_buy_and_sell_stock/playground.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_max_profit, run_max_profit\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "prices = [7, 1, 5, 3, 6, 4]\n", + "expected = 5" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "run", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_max_profit(Solution, prices)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "assert", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_max_profit(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/best_time_to_buy_and_sell_stock/solution.py b/leetcode/best_time_to_buy_and_sell_stock/solution.py new file mode 100644 index 0000000..4f877ec --- /dev/null +++ b/leetcode/best_time_to_buy_and_sell_stock/solution.py @@ -0,0 +1,12 @@ +class Solution: + # Time: O(n) + # Space: O(1) + def max_profit(self, prices: list[int]) -> int: + min_price = prices[0] + max_profit = 0 + + for price in prices[1:]: + max_profit = max(max_profit, price - min_price) + min_price = min(min_price, price) + + return max_profit diff --git a/leetcode/best_time_to_buy_and_sell_stock/test_solution.py b/leetcode/best_time_to_buy_and_sell_stock/test_solution.py new file mode 100644 index 0000000..d889ade --- /dev/null +++ b/leetcode/best_time_to_buy_and_sell_stock/test_solution.py @@ -0,0 +1,29 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_max_profit, run_max_profit +from .solution import Solution + + +class TestBestTimeToBuyAndSellStock: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "prices, expected", + [ + ([7, 1, 5, 3, 6, 4], 5), + ([7, 6, 4, 3, 1], 0), + ([1, 2, 3, 4, 5], 4), + ([5, 4, 3, 2, 1], 0), + ([1], 0), + ([2, 1], 0), + ([1, 2], 1), + ([3, 2, 6, 5, 0, 3], 4), + ], + ) + def test_max_profit(self, prices: list[int], expected: int): + result = run_max_profit(Solution, prices) + assert_max_profit(result, expected) diff --git a/leetcode/binary_search/README.md b/leetcode/binary_search/README.md new file mode 100644 index 0000000..16d0461 --- /dev/null +++ b/leetcode/binary_search/README.md @@ -0,0 +1,40 @@ +# Binary Search + +**Difficulty:** Easy +**Topics:** Array, Binary Search +**Tags:** grind-75 + +**LeetCode:** [Problem 704](https://leetcode.com/problems/binary-search/description/) + +## Problem Description + +Given an array of integers `nums` which is sorted in ascending order, and an integer `target`, write a function to search `target` in `nums`. If `target` exists, then return its index. Otherwise, return `-1`. + +You must write an algorithm with `O(log n)` runtime complexity. + +## Examples + +### Example 1: + +``` +Input: nums = [-1,0,3,5,9,12], target = 9 +Output: 4 +``` + +**Explanation:** 9 exists in nums and its index is 4 + +### Example 2: + +``` +Input: nums = [-1,0,3,5,9,12], target = 2 +Output: -1 +``` + +**Explanation:** 2 does not exist in nums so return -1 + +## Constraints + +- `1 <= nums.length <= 10^4` +- `-10^4 < nums[i], target < 10^4` +- All the integers in `nums` are **unique**. +- `nums` is sorted in ascending order. diff --git a/leetcode/binary_search/__init__.py b/leetcode/binary_search/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/binary_search/helpers.py b/leetcode/binary_search/helpers.py new file mode 100644 index 0000000..70f8aee --- /dev/null +++ b/leetcode/binary_search/helpers.py @@ -0,0 +1,8 @@ +def run_search(solution_class: type, nums: list[int], target: int): + implementation = solution_class() + return implementation.search(nums, target) + + +def assert_search(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/binary_search/playground.ipynb b/leetcode/binary_search/playground.ipynb new file mode 100644 index 0000000..9c1a608 --- /dev/null +++ b/leetcode/binary_search/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_search, run_search\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [-1, 0, 3, 5, 9, 12]\n", + "target = 9\n", + "expected = 4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_search(Solution, nums, target)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_search(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/binary_search/solution.py b/leetcode/binary_search/solution.py new file mode 100644 index 0000000..7bc15e6 --- /dev/null +++ b/leetcode/binary_search/solution.py @@ -0,0 +1,17 @@ +class Solution: + # Time: O(log n) + # Space: O(1) + def search(self, nums: list[int], target: int) -> int: + left, right = 0, len(nums) - 1 + + while left <= right: + mid = (left + right) // 2 + + if nums[mid] == target: + return mid + elif nums[mid] < target: + left = mid + 1 + else: + right = mid - 1 + + return -1 diff --git a/leetcode/binary_search/test_solution.py b/leetcode/binary_search/test_solution.py new file mode 100644 index 0000000..5ed0cee --- /dev/null +++ b/leetcode/binary_search/test_solution.py @@ -0,0 +1,34 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_search, run_search +from .solution import Solution + + +class TestBinarySearch: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, target, expected", + [ + ([-1, 0, 3, 5, 9, 12], 9, 4), + ([-1, 0, 3, 5, 9, 12], 2, -1), + ([5], 5, 0), + ([5], -5, -1), + ([1, 3, 5, 7, 9], 1, 0), + ([1, 3, 5, 7, 9], 9, 4), + ([1, 3, 5, 7, 9], 4, -1), + ([1, 3], 1, 0), + ([1, 3], 3, 1), + ([1, 3], 2, -1), + ([-5, -2, 0, 3, 7], -2, 1), + ([-5, -2, 0, 3, 7], 0, 2), + ([-5, -2, 0, 3, 7], -1, -1), + ], + ) + def test_search(self, nums: list[int], target: int, expected: int): + result = run_search(Solution, nums, target) + assert_search(result, expected) diff --git a/leetcode/binary_tree_level_order_traversal/README.md b/leetcode/binary_tree_level_order_traversal/README.md new file mode 100644 index 0000000..346f623 --- /dev/null +++ b/leetcode/binary_tree_level_order_traversal/README.md @@ -0,0 +1,41 @@ +# Binary Tree Level Order Traversal + +**Difficulty:** Medium +**Topics:** Tree, Breadth-First Search, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 102](https://leetcode.com/problems/binary-tree-level-order-traversal/description/) + +## Problem Description + +Given the `root` of a binary tree, return the level order traversal of its nodes' values. (i.e., from left to right, level by level). + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2021/02/19/tree1.jpg) + +``` +Input: root = [3,9,20,null,null,15,7] +Output: [[3],[9,20],[15,7]] +``` + +### Example 2: + +``` +Input: root = [1] +Output: [[1]] +``` + +### Example 3: + +``` +Input: root = [] +Output: [] +``` + +## Constraints + +- The number of nodes in the tree is in the range [0, 2000] +- -1000 <= Node.val <= 1000 diff --git a/leetcode/binary_tree_level_order_traversal/__init__.py b/leetcode/binary_tree_level_order_traversal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/binary_tree_level_order_traversal/helpers.py b/leetcode/binary_tree_level_order_traversal/helpers.py new file mode 100644 index 0000000..8da1fe8 --- /dev/null +++ b/leetcode/binary_tree_level_order_traversal/helpers.py @@ -0,0 +1,12 @@ +from leetcode_py import TreeNode + + +def run_level_order(solution_class: type, root_list: list[int | None]): + implementation = solution_class() + root = TreeNode.from_list(root_list) if root_list else None + return implementation.level_order(root) + + +def assert_level_order(result: list[list[int]], expected: list[list[int]]) -> bool: + assert result == expected + return True diff --git a/leetcode/binary_tree_level_order_traversal/playground.ipynb b/leetcode/binary_tree_level_order_traversal/playground.ipynb new file mode 100644 index 0000000..9c98b39 --- /dev/null +++ b/leetcode/binary_tree_level_order_traversal/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_level_order, run_level_order\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import TreeNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root_list: list[int | None] = [3, 9, 20, None, None, 15, 7]\n", + "expected = [[3], [9, 20], [15, 7]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_level_order(Solution, root_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_level_order(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/binary_tree_level_order_traversal/solution.py b/leetcode/binary_tree_level_order_traversal/solution.py new file mode 100644 index 0000000..0486b29 --- /dev/null +++ b/leetcode/binary_tree_level_order_traversal/solution.py @@ -0,0 +1,31 @@ +from collections import deque + +from leetcode_py import TreeNode + + +class Solution: + # Time: O(n) + # Space: O(w) where w is max width of tree + def level_order(self, root: TreeNode[int] | None) -> list[list[int]]: + if not root: + return [] + + result = [] + queue = deque([root]) + + while queue: + level_size = len(queue) + level = [] + + for _ in range(level_size): + node = queue.popleft() + level.append(node.val) + + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + + result.append(level) + + return result diff --git a/leetcode/binary_tree_level_order_traversal/test_solution.py b/leetcode/binary_tree_level_order_traversal/test_solution.py new file mode 100644 index 0000000..ccb041a --- /dev/null +++ b/leetcode/binary_tree_level_order_traversal/test_solution.py @@ -0,0 +1,37 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_level_order, run_level_order +from .solution import Solution + + +class TestBinaryTreeLevelOrderTraversal: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "root_list, expected", + [ + ([3, 9, 20, None, None, 15, 7], [[3], [9, 20], [15, 7]]), + ([1], [[1]]), + ([], []), + ([1, 2, 3, 4, 5, 6, 7], [[1], [2, 3], [4, 5, 6, 7]]), + ([1, 2, None, 3, None, 4, None, 5], [[1], [2], [3], [4], [5]]), + ([1, None, 2, None, 3], [[1], [2], [3]]), + ([1, 2, None, 3, None], [[1], [2], [3]]), + ([0], [[0]]), + ([-1, -2, -3], [[-1], [-2, -3]]), + ([1, 2, 3, None, None, None, 4], [[1], [2, 3], [4]]), + ( + [5, 4, 8, 11, None, 13, 4, 7, 2, None, None, None, 1], + [[5], [4, 8], [11, 13, 4], [7, 2, 1]], + ), + ([1, 2, 2, 3, 3, 3, 3], [[1], [2, 2], [3, 3, 3, 3]]), + ([1, None, None], [[1]]), + ], + ) + def test_level_order(self, root_list: list[int | None], expected: list[list[int]]): + result = run_level_order(Solution, root_list) + assert_level_order(result, expected) diff --git a/leetcode/binary_tree_right_side_view/README.md b/leetcode/binary_tree_right_side_view/README.md new file mode 100644 index 0000000..b44249a --- /dev/null +++ b/leetcode/binary_tree_right_side_view/README.md @@ -0,0 +1,50 @@ +# Binary Tree Right Side View + +**Difficulty:** Medium +**Topics:** Tree, Depth-First Search, Breadth-First Search, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 199](https://leetcode.com/problems/binary-tree-right-side-view/description/) + +## Problem Description + +Given the `root` of a binary tree, imagine yourself standing on the **right side** of it, return _the values of the nodes you can see ordered from top to bottom_. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2024/11/24/tmpd5jn43fs-1.png) + +``` +Input: root = [1,2,3,null,5,null,4] +Output: [1,3,4] +``` + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2024/11/24/tmpkpe40xeh-1.png) + +``` +Input: root = [1,2,3,4,null,null,null,5] +Output: [1,3,4,5] +``` + +### Example 3: + +``` +Input: root = [1,null,3] +Output: [1,3] +``` + +### Example 4: + +``` +Input: root = [] +Output: [] +``` + +## Constraints + +- The number of nodes in the tree is in the range `[0, 100]`. +- `-100 <= Node.val <= 100` diff --git a/leetcode/binary_tree_right_side_view/__init__.py b/leetcode/binary_tree_right_side_view/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/binary_tree_right_side_view/helpers.py b/leetcode/binary_tree_right_side_view/helpers.py new file mode 100644 index 0000000..6b34ffc --- /dev/null +++ b/leetcode/binary_tree_right_side_view/helpers.py @@ -0,0 +1,12 @@ +from leetcode_py import TreeNode + + +def run_right_side_view(solution_class: type, root_list: list[int | None]): + implementation = solution_class() + root = TreeNode.from_list(root_list) if root_list else None + return implementation.right_side_view(root) + + +def assert_right_side_view(result: list[int], expected: list[int]) -> bool: + assert result == expected + return True diff --git a/leetcode/binary_tree_right_side_view/playground.ipynb b/leetcode/binary_tree_right_side_view/playground.ipynb new file mode 100644 index 0000000..77ae9a4 --- /dev/null +++ b/leetcode/binary_tree_right_side_view/playground.ipynb @@ -0,0 +1,93 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_right_side_view, run_right_side_view\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import TreeNode" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root_list: list[int | None] = [1, 2, 3, None, 5, None, 4]\n", + "expected = [1, 3, 4]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "run", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 3, 4]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_right_side_view(Solution, root_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "assert", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_right_side_view(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/binary_tree_right_side_view/solution.py b/leetcode/binary_tree_right_side_view/solution.py new file mode 100644 index 0000000..841c2c8 --- /dev/null +++ b/leetcode/binary_tree_right_side_view/solution.py @@ -0,0 +1,67 @@ +from collections import deque + +from leetcode_py import TreeNode + + +class Solution: + # Time: O(n) + # Space: O(h) + def right_side_view(self, root: TreeNode[int] | None) -> list[int]: + result: list[int] = [] + + def dfs(node: TreeNode[int] | None, level: int) -> None: + if not node: + return + if level == len(result): + result.append(node.val) + dfs(node.right, level + 1) + dfs(node.left, level + 1) + + dfs(root, 0) + return result + + +class SolutionDFS: + # Time: O(n) + # Space: O(h) + def right_side_view(self, root: TreeNode[int] | None) -> list[int]: + if not root: + return [] + + result: list[int] = [] + stack = [(root, 0)] + + while stack: + node, level = stack.pop() + if level == len(result): + result.append(node.val) + if node.left: + stack.append((node.left, level + 1)) + if node.right: + stack.append((node.right, level + 1)) + + return result + + +class SolutionBFS: + # Time: O(n) + # Space: O(w) + def right_side_view(self, root: TreeNode[int] | None) -> list[int]: + if not root: + return [] + + result: list[int] = [] + queue = deque([root]) + + while queue: + level_size = len(queue) + for i in range(level_size): + node = queue.popleft() + if i == level_size - 1: # rightmost node + result.append(node.val) + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + + return result diff --git a/leetcode/binary_tree_right_side_view/test_solution.py b/leetcode/binary_tree_right_side_view/test_solution.py new file mode 100644 index 0000000..11aab2f --- /dev/null +++ b/leetcode/binary_tree_right_side_view/test_solution.py @@ -0,0 +1,28 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_right_side_view, run_right_side_view +from .solution import Solution + + +class TestBinaryTreeRightSideView: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "root_list, expected", + [ + ([1, 2, 3, None, 5, None, 4], [1, 3, 4]), + ([1, 2, 3, 4, None, None, None, 5], [1, 3, 4, 5]), + ([1, None, 3], [1, 3]), + ([], []), + ([1], [1]), + ([1, 2], [1, 2]), + ([1, None, 2], [1, 2]), + ], + ) + def test_right_side_view(self, root_list: list[int | None], expected: list[int]): + result = run_right_side_view(Solution, root_list) + assert_right_side_view(result, expected) From 094e2bf3e7a66cd2e6141e848f6ee764a519ad50 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 15:45:57 +0700 Subject: [PATCH 15/39] feat: add find_median_from_data_stream --- .templates/leetcode/json/climbing_stairs.json | 64 +++++++++ .templates/leetcode/json/clone_graph.json | 67 +++++++++ .templates/leetcode/json/coin_change.json | 63 +++++++++ .templates/leetcode/json/combination_sum.json | 65 +++++++++ .../json/container_with_most_water.json | 62 +++++++++ .../leetcode/json/contains_duplicate.json | 65 +++++++++ .templates/leetcode/json/course_schedule.json | 64 +++++++++ .../json/diameter_of_binary_tree.json | 62 +++++++++ .../evaluate_reverse_polish_notation.json | 67 +++++++++ .../json/find_median_from_data_stream.json | 69 ++++++++++ .../leetcode/json/first_bad_version.json | 67 +++++++++ Makefile | 3 +- .../playground.ipynb | 96 ++++++++++++- .../test_solution.py | 9 +- leetcode/climbing_stairs/README.md | 44 ++++++ leetcode/climbing_stairs/__init__.py | 0 leetcode/climbing_stairs/helpers.py | 8 ++ leetcode/climbing_stairs/playground.ipynb | 68 +++++++++ leetcode/climbing_stairs/solution.py | 17 +++ leetcode/climbing_stairs/test_solution.py | 20 +++ leetcode/clone_graph/README.md | 75 ++++++++++ leetcode/clone_graph/__init__.py | 0 leetcode/clone_graph/helpers.py | 16 +++ leetcode/clone_graph/playground.ipynb | 69 ++++++++++ leetcode/clone_graph/solution.py | 73 ++++++++++ leetcode/clone_graph/test_solution.py | 18 +++ leetcode/coin_change/README.md | 46 +++++++ leetcode/coin_change/__init__.py | 0 leetcode/coin_change/helpers.py | 8 ++ leetcode/coin_change/playground.ipynb | 69 ++++++++++ leetcode/coin_change/solution.py | 21 +++ leetcode/coin_change/test_solution.py | 30 ++++ leetcode/combination_sum/README.md | 47 +++++++ leetcode/combination_sum/__init__.py | 0 leetcode/combination_sum/helpers.py | 13 ++ leetcode/combination_sum/playground.ipynb | 69 ++++++++++ leetcode/combination_sum/solution.py | 19 +++ leetcode/combination_sum/test_solution.py | 24 ++++ leetcode/container_with_most_water/README.md | 43 ++++++ .../container_with_most_water/__init__.py | 0 leetcode/container_with_most_water/helpers.py | 8 ++ .../playground.ipynb | 68 +++++++++ .../container_with_most_water/solution.py | 17 +++ .../test_solution.py | 19 +++ leetcode/contains_duplicate/README.md | 43 ++++++ leetcode/contains_duplicate/__init__.py | 0 leetcode/contains_duplicate/helpers.py | 8 ++ leetcode/contains_duplicate/playground.ipynb | 91 ++++++++++++ leetcode/contains_duplicate/solution.py | 10 ++ leetcode/contains_duplicate/test_solution.py | 20 +++ leetcode/course_schedule/README.md | 43 ++++++ leetcode/course_schedule/__init__.py | 0 leetcode/course_schedule/helpers.py | 8 ++ leetcode/course_schedule/playground.ipynb | 69 ++++++++++ leetcode/course_schedule/solution.py | 30 ++++ leetcode/course_schedule/test_solution.py | 26 ++++ leetcode/diameter_of_binary_tree/README.md | 40 ++++++ leetcode/diameter_of_binary_tree/__init__.py | 0 leetcode/diameter_of_binary_tree/helpers.py | 12 ++ .../diameter_of_binary_tree/playground.ipynb | 70 ++++++++++ leetcode/diameter_of_binary_tree/solution.py | 23 ++++ .../diameter_of_binary_tree/test_solution.py | 20 +++ .../README.md | 56 ++++++++ .../__init__.py | 0 .../helpers.py | 8 ++ .../playground.ipynb | 68 +++++++++ .../solution.py | 21 +++ .../test_solution.py | 24 ++++ .../find_median_from_data_stream/README.md | 54 ++++++++ .../find_median_from_data_stream/__init__.py | 0 .../find_median_from_data_stream/helpers.py | 18 +++ .../playground.ipynb | 70 ++++++++++ .../find_median_from_data_stream/solution.py | 115 ++++++++++++++++ .../test_solution.py | 31 +++++ leetcode/first_bad_version/README.md | 47 +++++++ leetcode/first_bad_version/__init__.py | 0 leetcode/first_bad_version/helpers.py | 8 ++ leetcode/first_bad_version/playground.ipynb | 69 ++++++++++ leetcode/first_bad_version/solution.py | 47 +++++++ leetcode/first_bad_version/test_solution.py | 31 +++++ leetcode/invert_binary_tree/playground.ipynb | 129 +++++++++++++++++- 81 files changed, 3027 insertions(+), 14 deletions(-) create mode 100644 .templates/leetcode/json/climbing_stairs.json create mode 100644 .templates/leetcode/json/clone_graph.json create mode 100644 .templates/leetcode/json/coin_change.json create mode 100644 .templates/leetcode/json/combination_sum.json create mode 100644 .templates/leetcode/json/container_with_most_water.json create mode 100644 .templates/leetcode/json/contains_duplicate.json create mode 100644 .templates/leetcode/json/course_schedule.json create mode 100644 .templates/leetcode/json/diameter_of_binary_tree.json create mode 100644 .templates/leetcode/json/evaluate_reverse_polish_notation.json create mode 100644 .templates/leetcode/json/find_median_from_data_stream.json create mode 100644 .templates/leetcode/json/first_bad_version.json create mode 100644 leetcode/climbing_stairs/README.md create mode 100644 leetcode/climbing_stairs/__init__.py create mode 100644 leetcode/climbing_stairs/helpers.py create mode 100644 leetcode/climbing_stairs/playground.ipynb create mode 100644 leetcode/climbing_stairs/solution.py create mode 100644 leetcode/climbing_stairs/test_solution.py create mode 100644 leetcode/clone_graph/README.md create mode 100644 leetcode/clone_graph/__init__.py create mode 100644 leetcode/clone_graph/helpers.py create mode 100644 leetcode/clone_graph/playground.ipynb create mode 100644 leetcode/clone_graph/solution.py create mode 100644 leetcode/clone_graph/test_solution.py create mode 100644 leetcode/coin_change/README.md create mode 100644 leetcode/coin_change/__init__.py create mode 100644 leetcode/coin_change/helpers.py create mode 100644 leetcode/coin_change/playground.ipynb create mode 100644 leetcode/coin_change/solution.py create mode 100644 leetcode/coin_change/test_solution.py create mode 100644 leetcode/combination_sum/README.md create mode 100644 leetcode/combination_sum/__init__.py create mode 100644 leetcode/combination_sum/helpers.py create mode 100644 leetcode/combination_sum/playground.ipynb create mode 100644 leetcode/combination_sum/solution.py create mode 100644 leetcode/combination_sum/test_solution.py create mode 100644 leetcode/container_with_most_water/README.md create mode 100644 leetcode/container_with_most_water/__init__.py create mode 100644 leetcode/container_with_most_water/helpers.py create mode 100644 leetcode/container_with_most_water/playground.ipynb create mode 100644 leetcode/container_with_most_water/solution.py create mode 100644 leetcode/container_with_most_water/test_solution.py create mode 100644 leetcode/contains_duplicate/README.md create mode 100644 leetcode/contains_duplicate/__init__.py create mode 100644 leetcode/contains_duplicate/helpers.py create mode 100644 leetcode/contains_duplicate/playground.ipynb create mode 100644 leetcode/contains_duplicate/solution.py create mode 100644 leetcode/contains_duplicate/test_solution.py create mode 100644 leetcode/course_schedule/README.md create mode 100644 leetcode/course_schedule/__init__.py create mode 100644 leetcode/course_schedule/helpers.py create mode 100644 leetcode/course_schedule/playground.ipynb create mode 100644 leetcode/course_schedule/solution.py create mode 100644 leetcode/course_schedule/test_solution.py create mode 100644 leetcode/diameter_of_binary_tree/README.md create mode 100644 leetcode/diameter_of_binary_tree/__init__.py create mode 100644 leetcode/diameter_of_binary_tree/helpers.py create mode 100644 leetcode/diameter_of_binary_tree/playground.ipynb create mode 100644 leetcode/diameter_of_binary_tree/solution.py create mode 100644 leetcode/diameter_of_binary_tree/test_solution.py create mode 100644 leetcode/evaluate_reverse_polish_notation/README.md create mode 100644 leetcode/evaluate_reverse_polish_notation/__init__.py create mode 100644 leetcode/evaluate_reverse_polish_notation/helpers.py create mode 100644 leetcode/evaluate_reverse_polish_notation/playground.ipynb create mode 100644 leetcode/evaluate_reverse_polish_notation/solution.py create mode 100644 leetcode/evaluate_reverse_polish_notation/test_solution.py create mode 100644 leetcode/find_median_from_data_stream/README.md create mode 100644 leetcode/find_median_from_data_stream/__init__.py create mode 100644 leetcode/find_median_from_data_stream/helpers.py create mode 100644 leetcode/find_median_from_data_stream/playground.ipynb create mode 100644 leetcode/find_median_from_data_stream/solution.py create mode 100644 leetcode/find_median_from_data_stream/test_solution.py create mode 100644 leetcode/first_bad_version/README.md create mode 100644 leetcode/first_bad_version/__init__.py create mode 100644 leetcode/first_bad_version/helpers.py create mode 100644 leetcode/first_bad_version/playground.ipynb create mode 100644 leetcode/first_bad_version/solution.py create mode 100644 leetcode/first_bad_version/test_solution.py diff --git a/.templates/leetcode/json/climbing_stairs.json b/.templates/leetcode/json/climbing_stairs.json new file mode 100644 index 0000000..8dcb90b --- /dev/null +++ b/.templates/leetcode/json/climbing_stairs.json @@ -0,0 +1,64 @@ +{ + "problem_name": "climbing_stairs", + "solution_class_name": "Solution", + "problem_number": "70", + "problem_title": "Climbing Stairs", + "difficulty": "Easy", + "topics": "Math, Dynamic Programming, Memoization", + "_tags": { "list": ["grind-75"] }, + "readme_description": "You are climbing a staircase. It takes `n` steps to reach the top.\n\nEach time you can either climb `1` or `2` steps. In how many distinct ways can you climb to the top?", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: n = 2\nOutput: 2\n```\n**Explanation:** There are two ways to climb to the top.\n1. 1 step + 1 step\n2. 2 steps" + }, + { + "content": "```\nInput: n = 3\nOutput: 3\n```\n**Explanation:** There are three ways to climb to the top.\n1. 1 step + 1 step + 1 step\n2. 1 step + 2 steps\n3. 2 steps + 1 step" + } + ] + }, + "readme_constraints": "- 1 <= n <= 45", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "climb_stairs", + "helpers_run_signature": "(solution_class: type, n: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.climb_stairs(n)", + "helpers_assert_name": "climb_stairs", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_climb_stairs, run_climb_stairs\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ClimbingStairs", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "climb_stairs", + "signature": "(self, n: int) -> int", + "body": " # TODO: Implement climb_stairs\n return 1" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_climb_stairs", + "signature": "(self, n: int, expected: int)", + "parametrize": "n, expected", + "test_cases": "[(1, 1), (2, 2), (3, 3), (4, 5), (5, 8), (6, 13), (10, 89), (20, 10946), (45, 1836311903)]", + "body": " result = run_climb_stairs(Solution, n)\n assert_climb_stairs(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_climb_stairs, assert_climb_stairs\nfrom solution import Solution", + "playground_setup": "# Example test case\nn = 3\nexpected = 3", + "playground_run": "result = run_climb_stairs(Solution, n)\nresult", + "playground_assert": "assert_climb_stairs(result, expected)" +} diff --git a/.templates/leetcode/json/clone_graph.json b/.templates/leetcode/json/clone_graph.json new file mode 100644 index 0000000..594a2fc --- /dev/null +++ b/.templates/leetcode/json/clone_graph.json @@ -0,0 +1,67 @@ +{ + "problem_name": "clone_graph", + "solution_class_name": "Solution", + "problem_number": "133", + "problem_title": "Clone Graph", + "difficulty": "Medium", + "topics": "Hash Table, Depth-First Search, Breadth-First Search, Graph", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given a reference of a node in a **connected** undirected graph.\n\nReturn a **deep copy** (clone) of the graph.\n\nEach node in the graph contains a value (`int`) and a list (`List[Node]`) of its neighbors.\n\n```\nclass Node {\n public int val;\n public List neighbors;\n}\n```\n\n**Test case format:**\n\nFor simplicity, each node's value is the same as the node's index (1-indexed). For example, the first node with `val == 1`, the second node with `val == 2`, and so on. The graph is represented in the test case using an adjacency list.\n\n**An adjacency list** is a collection of unordered **lists** used to represent a finite graph. Each list describes the set of neighbors of a node in the graph.\n\nThe given node will always be the first node with `val = 1`. You must return the **copy of the given node** as a reference to the cloned graph.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2019/11/04/133_clone_graph_question.png)\n\n```\nInput: adjList = [[2,4],[1,3],[2,4],[1,3]]\nOutput: [[2,4],[1,3],[2,4],[1,3]]\n```\n**Explanation:** There are 4 nodes in the graph.\n1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).\n2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).\n3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).\n4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3)." + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2020/01/07/graph.png)\n\n```\nInput: adjList = [[]]\nOutput: [[]]\n```\n**Explanation:** Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors." + }, + { + "content": "```\nInput: adjList = []\nOutput: []\n```\n**Explanation:** This an empty graph, it does not have any nodes." + } + ] + }, + "readme_constraints": "- The number of nodes in the graph is in the range `[0, 100]`.\n- `1 <= Node.val <= 100`\n- `Node.val` is unique for each node.\n- There are no repeated edges and no self-loops in the graph.\n- The Graph is connected and all nodes can be visited starting from the given node.", + "readme_additional": "", + "helpers_imports": "from leetcode_py import GraphNode", + "helpers_content": "", + "helpers_run_name": "clone_graph", + "helpers_run_signature": "(solution_class: type, adj_list: list[list[int]])", + "helpers_run_body": " node = GraphNode.from_adjacency_list(adj_list)\n implementation = solution_class()\n return implementation.clone_graph(node)", + "helpers_assert_name": "clone_graph", + "helpers_assert_signature": "(result: GraphNode | None, adj_list: list[list[int]]) -> bool", + "helpers_assert_body": " original = GraphNode.from_adjacency_list(adj_list)\n if result is None:\n assert original is None\n else:\n assert result.is_clone(original)\n return True", + "solution_imports": "from leetcode_py import GraphNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_clone_graph, run_clone_graph\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "CloneGraph", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "clone_graph", + "signature": "(self, node: GraphNode | None) -> GraphNode | None", + "body": " # TODO: Implement clone_graph\n return None" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_clone_graph", + "signature": "(self, adj_list: list[list[int]])", + "parametrize": "adj_list", + "test_cases": "[[[2, 4], [1, 3], [2, 4], [1, 3]], [[]], []]", + "body": " result = run_clone_graph(Solution, adj_list)\n assert_clone_graph(result, adj_list)" + } + ] + }, + "playground_imports": "from helpers import run_clone_graph, assert_clone_graph\nfrom solution import Solution\nfrom leetcode_py import GraphNode", + "playground_setup": "# Example test case\nadj_list = [[2,4],[1,3],[2,4],[1,3]]", + "playground_run": "result = run_clone_graph(Solution, adj_list)\nresult", + "playground_assert": "assert_clone_graph(result, adj_list)" +} diff --git a/.templates/leetcode/json/coin_change.json b/.templates/leetcode/json/coin_change.json new file mode 100644 index 0000000..7702878 --- /dev/null +++ b/.templates/leetcode/json/coin_change.json @@ -0,0 +1,63 @@ +{ + "problem_name": "coin_change", + "solution_class_name": "Solution", + "problem_number": "322", + "problem_title": "Coin Change", + "difficulty": "Medium", + "topics": "Array, Dynamic Programming, Breadth-First Search", + "_tags": { "list": ["grind-75"] }, + "readme_description": "You are given an integer array `coins` representing coins of different denominations and an integer `amount` representing a total amount of money.\n\nReturn the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return `-1`.\n\nYou may assume that you have an infinite number of each kind of coin.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: coins = [1,2,5], amount = 11\nOutput: 3\n```\n**Explanation:** 11 = 5 + 5 + 1" + }, + { "content": "```\nInput: coins = [2], amount = 3\nOutput: -1\n```" }, + { "content": "```\nInput: coins = [1], amount = 0\nOutput: 0\n```" } + ] + }, + "readme_constraints": "- `1 <= coins.length <= 12`\n- `1 <= coins[i] <= 2^31 - 1`\n- `0 <= amount <= 10^4`", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "coin_change", + "helpers_run_signature": "(solution_class: type, coins: list[int], amount: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.coin_change(coins, amount)", + "helpers_assert_name": "coin_change", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_coin_change, run_coin_change\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "CoinChange", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "coin_change", + "signature": "(self, coins: list[int], amount: int) -> int", + "body": " # TODO: Implement coin_change\n return -1" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_coin_change", + "signature": "(self, coins: list[int], amount: int, expected: int)", + "parametrize": "coins, amount, expected", + "test_cases": "[([1, 2, 5], 11, 3), ([2], 3, -1), ([1], 0, 0), ([1, 3, 4], 6, 2), ([2, 5, 10, 1], 27, 4), ([5], 3, -1), ([1], 1, 1), ([1, 2], 2, 1), ([186, 419, 83, 408], 6249, 20)]", + "body": " result = run_coin_change(Solution, coins, amount)\n assert_coin_change(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_coin_change, assert_coin_change\nfrom solution import Solution", + "playground_setup": "# Example test case\ncoins = [1, 2, 5]\namount = 11\nexpected = 3", + "playground_run": "result = run_coin_change(Solution, coins, amount)\nresult", + "playground_assert": "assert_coin_change(result, expected)" +} diff --git a/.templates/leetcode/json/combination_sum.json b/.templates/leetcode/json/combination_sum.json new file mode 100644 index 0000000..d7f7e64 --- /dev/null +++ b/.templates/leetcode/json/combination_sum.json @@ -0,0 +1,65 @@ +{ + "problem_name": "combination_sum", + "solution_class_name": "Solution", + "problem_number": "39", + "problem_title": "Combination Sum", + "difficulty": "Medium", + "topics": "Array, Backtracking", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an array of **distinct** integers `candidates` and a target integer `target`, return *a list of all **unique combinations** of* `candidates` *where the chosen numbers sum to* `target`. You may return the combinations in **any order**.\n\nThe **same** number may be chosen from `candidates` an **unlimited number of times**. Two combinations are unique if the frequency of at least one of the chosen numbers is different.\n\nThe test cases are generated such that the number of unique combinations that sum up to `target` is less than `150` combinations for the given input.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: candidates = [2,3,6,7], target = 7\nOutput: [[2,2,3],[7]]\n```\n**Explanation:** 2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times. 7 is a candidate, and 7 = 7. These are the only two combinations." + }, + { + "content": "```\nInput: candidates = [2,3,5], target = 8\nOutput: [[2,2,2,2],[2,3,3],[3,5]]\n```" + }, + { "content": "```\nInput: candidates = [2], target = 1\nOutput: []\n```" } + ] + }, + "readme_constraints": "- 1 <= candidates.length <= 30\n- 2 <= candidates[i] <= 40\n- All elements of candidates are distinct.\n- 1 <= target <= 40", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "combination_sum", + "helpers_run_signature": "(solution_class: type, candidates: list[int], target: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.combination_sum(candidates, target)", + "helpers_assert_name": "combination_sum", + "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool", + "helpers_assert_body": " # Sort both result and expected for comparison\n result_sorted = [sorted(combo) for combo in result]\n expected_sorted = [sorted(combo) for combo in expected]\n result_sorted.sort()\n expected_sorted.sort()\n assert result_sorted == expected_sorted\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_combination_sum, run_combination_sum\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "CombinationSum", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "combination_sum", + "signature": "(self, candidates: list[int], target: int) -> list[list[int]]", + "body": " # TODO: Implement combination_sum\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_combination_sum", + "signature": "(self, candidates: list[int], target: int, expected: list[list[int]])", + "parametrize": "candidates, target, expected", + "test_cases": "[([2, 3, 6, 7], 7, [[2, 2, 3], [7]]), ([2, 3, 5], 8, [[2, 2, 2, 2], [2, 3, 3], [3, 5]]), ([2], 1, [])]", + "body": " result = run_combination_sum(Solution, candidates, target)\n assert_combination_sum(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_combination_sum, assert_combination_sum\nfrom solution import Solution", + "playground_setup": "# Example test case\ncandidates = [2, 3, 6, 7]\ntarget = 7\nexpected = [[2, 2, 3], [7]]", + "playground_run": "result = run_combination_sum(Solution, candidates, target)\nresult", + "playground_assert": "assert_combination_sum(result, expected)" +} diff --git a/.templates/leetcode/json/container_with_most_water.json b/.templates/leetcode/json/container_with_most_water.json new file mode 100644 index 0000000..604d452 --- /dev/null +++ b/.templates/leetcode/json/container_with_most_water.json @@ -0,0 +1,62 @@ +{ + "problem_name": "container_with_most_water", + "solution_class_name": "Solution", + "problem_number": "11", + "problem_title": "Container With Most Water", + "difficulty": "Medium", + "topics": "Array, Two Pointers, Greedy", + "_tags": { "list": ["grind-75"] }, + "readme_description": "You are given an integer array `height` of length `n`. There are `n` vertical lines drawn such that the two endpoints of the `i`th line are `(i, 0)` and `(i, height[i])`.\n\nFind two lines that together with the x-axis form a container, such that the container contains the most water.\n\nReturn the maximum amount of water a container can store.\n\nNotice that you may not slant the container.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/07/17/question_11.jpg)\n\n```\nInput: height = [1,8,6,2,5,4,8,3,7]\nOutput: 49\n```\n**Explanation:** The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49." + }, + { "content": "```\nInput: height = [1,1]\nOutput: 1\n```" } + ] + }, + "readme_constraints": "- n == height.length\n- 2 <= n <= 10^5\n- 0 <= height[i] <= 10^4", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "max_area", + "helpers_run_signature": "(solution_class: type, height: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.max_area(height)", + "helpers_assert_name": "max_area", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_max_area, run_max_area\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ContainerWithMostWater", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "max_area", + "signature": "(self, height: list[int]) -> int", + "body": " # TODO: Implement max_area\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_max_area", + "signature": "(self, height: list[int], expected: int)", + "parametrize": "height, expected", + "test_cases": "[([1,8,6,2,5,4,8,3,7], 49), ([1,1], 1), ([1,2,1], 2)]", + "body": " result = run_max_area(Solution, height)\n assert_max_area(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_max_area, assert_max_area\nfrom solution import Solution", + "playground_setup": "# Example test case\nheight = [1,8,6,2,5,4,8,3,7]\nexpected = 49", + "playground_run": "result = run_max_area(Solution, height)\nresult", + "playground_assert": "assert_max_area(result, expected)" +} diff --git a/.templates/leetcode/json/contains_duplicate.json b/.templates/leetcode/json/contains_duplicate.json new file mode 100644 index 0000000..8b540d0 --- /dev/null +++ b/.templates/leetcode/json/contains_duplicate.json @@ -0,0 +1,65 @@ +{ + "problem_name": "contains_duplicate", + "solution_class_name": "Solution", + "problem_number": "217", + "problem_title": "Contains Duplicate", + "difficulty": "Easy", + "topics": "Array, Hash Table, Sorting", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an integer array `nums`, return `true` if any value appears **at least twice** in the array, and return `false` if every element is distinct.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums = [1,2,3,1]\nOutput: true\n```\n**Explanation:** The element 1 occurs at the indices 0 and 3." + }, + { + "content": "```\nInput: nums = [1,2,3,4]\nOutput: false\n```\n**Explanation:** All elements are distinct." + }, + { "content": "```\nInput: nums = [1,1,1,3,3,4,3,2,4,2]\nOutput: true\n```" } + ] + }, + "readme_constraints": "- 1 <= nums.length <= 10^5\n- -10^9 <= nums[i] <= 10^9", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "contains_duplicate", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.contains_duplicate(nums)", + "helpers_assert_name": "contains_duplicate", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_contains_duplicate, run_contains_duplicate\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ContainsDuplicate", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "contains_duplicate", + "signature": "(self, nums: list[int]) -> bool", + "body": " # TODO: Implement contains_duplicate\n return False" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_contains_duplicate", + "signature": "(self, nums: list[int], expected: bool)", + "parametrize": "nums, expected", + "test_cases": "[([1, 2, 3, 1], True), ([1, 2, 3, 4], False), ([1, 1, 1, 3, 3, 4, 3, 2, 4, 2], True)]", + "body": " result = run_contains_duplicate(Solution, nums)\n assert_contains_duplicate(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_contains_duplicate, assert_contains_duplicate\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [1, 2, 3, 1]\nexpected = True", + "playground_run": "result = run_contains_duplicate(Solution, nums)\nresult", + "playground_assert": "assert_contains_duplicate(result, expected)" +} diff --git a/.templates/leetcode/json/course_schedule.json b/.templates/leetcode/json/course_schedule.json new file mode 100644 index 0000000..f04589d --- /dev/null +++ b/.templates/leetcode/json/course_schedule.json @@ -0,0 +1,64 @@ +{ + "problem_name": "course_schedule", + "solution_class_name": "Solution", + "problem_number": "207", + "problem_title": "Course Schedule", + "difficulty": "Medium", + "topics": "Depth-First Search, Breadth-First Search, Graph, Topological Sort", + "_tags": { "list": ["grind-75"] }, + "readme_description": "There are a total of `numCourses` courses you have to take, labeled from `0` to `numCourses - 1`. You are given an array `prerequisites` where `prerequisites[i] = [ai, bi]` indicates that you **must** take course `bi` first if you want to take course `ai`.\n\n- For example, the pair `[0, 1]`, indicates that to take course `0` you have to first take course `1`.\n\nReturn `true` if you can finish all courses. Otherwise, return `false`.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: numCourses = 2, prerequisites = [[1,0]]\nOutput: true\n```\n**Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible." + }, + { + "content": "```\nInput: numCourses = 2, prerequisites = [[1,0],[0,1]]\nOutput: false\n```\n**Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible." + } + ] + }, + "readme_constraints": "- `1 <= numCourses <= 2000`\n- `0 <= prerequisites.length <= 5000`\n- `prerequisites[i].length == 2`\n- `0 <= ai, bi < numCourses`\n- All the pairs prerequisites[i] are **unique**.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "can_finish", + "helpers_run_signature": "(solution_class: type, num_courses: int, prerequisites: list[list[int]])", + "helpers_run_body": " implementation = solution_class()\n return implementation.can_finish(num_courses, prerequisites)", + "helpers_assert_name": "can_finish", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_can_finish, run_can_finish\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "CourseSchedule", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "can_finish", + "signature": "(self, num_courses: int, prerequisites: list[list[int]]) -> bool", + "body": " # TODO: Implement can_finish\n return False" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_can_finish", + "signature": "(self, num_courses: int, prerequisites: list[list[int]], expected: bool)", + "parametrize": "num_courses, prerequisites, expected", + "test_cases": "[(2, [[1, 0]], True), (2, [[1, 0], [0, 1]], False), (1, [], True), (3, [[1, 0], [2, 1]], True), (4, [[1, 0], [2, 1], [3, 2], [1, 3]], False)]", + "body": " result = run_can_finish(Solution, num_courses, prerequisites)\n assert_can_finish(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_can_finish, assert_can_finish\nfrom solution import Solution", + "playground_setup": "# Example test case\nnum_courses = 2\nprerequisites = [[1, 0]]\nexpected = True", + "playground_run": "result = run_can_finish(Solution, num_courses, prerequisites)\nresult", + "playground_assert": "assert_can_finish(result, expected)" +} diff --git a/.templates/leetcode/json/diameter_of_binary_tree.json b/.templates/leetcode/json/diameter_of_binary_tree.json new file mode 100644 index 0000000..37ca4c0 --- /dev/null +++ b/.templates/leetcode/json/diameter_of_binary_tree.json @@ -0,0 +1,62 @@ +{ + "problem_name": "diameter_of_binary_tree", + "solution_class_name": "Solution", + "problem_number": "543", + "problem_title": "Diameter of Binary Tree", + "difficulty": "Easy", + "topics": "Tree, Depth-First Search, Binary Tree", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given the `root` of a binary tree, return the length of the **diameter** of the tree.\n\nThe **diameter** of a binary tree is the **length** of the longest path between any two nodes in a tree. This path may or may not pass through the `root`.\n\nThe **length** of a path between two nodes is represented by the number of edges between them.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2021/03/06/diamtree.jpg)\n\n```\nInput: root = [1,2,3,4,5]\nOutput: 3\n```\n**Explanation:** 3 is the length of the path [4,2,1,3] or [5,2,1,3]." + }, + { "content": "```\nInput: root = [1,2]\nOutput: 1\n```" } + ] + }, + "readme_constraints": "- The number of nodes in the tree is in the range [1, 10^4].\n- -100 <= Node.val <= 100", + "readme_additional": "", + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "", + "helpers_run_name": "diameter_of_binary_tree", + "helpers_run_signature": "(solution_class: type, root_list: list[int | None])", + "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n implementation = solution_class()\n return implementation.diameter_of_binary_tree(root)", + "helpers_assert_name": "diameter_of_binary_tree", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "from leetcode_py import TreeNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_diameter_of_binary_tree, run_diameter_of_binary_tree\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "DiameterOfBinaryTree", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "diameter_of_binary_tree", + "signature": "(self, root: TreeNode[int] | None) -> int", + "body": " # TODO: Implement diameter_of_binary_tree\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_diameter_of_binary_tree", + "signature": "(self, root_list: list[int | None], expected: int)", + "parametrize": "root_list, expected", + "test_cases": "[([1, 2, 3, 4, 5], 3), ([1, 2], 1), ([], 0), ([1], 0), ([1, 2, 3], 2), ([1, None, 2], 1)]", + "body": " result = run_diameter_of_binary_tree(Solution, root_list)\n assert_diameter_of_binary_tree(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_diameter_of_binary_tree, assert_diameter_of_binary_tree\nfrom solution import Solution\nfrom leetcode_py import TreeNode", + "playground_setup": "# Example test case\nroot_list: list[int | None] = [1, 2, 3, 4, 5]\nexpected = 3", + "playground_run": "result = run_diameter_of_binary_tree(Solution, root_list)\nresult", + "playground_assert": "assert_diameter_of_binary_tree(result, expected)" +} diff --git a/.templates/leetcode/json/evaluate_reverse_polish_notation.json b/.templates/leetcode/json/evaluate_reverse_polish_notation.json new file mode 100644 index 0000000..781c301 --- /dev/null +++ b/.templates/leetcode/json/evaluate_reverse_polish_notation.json @@ -0,0 +1,67 @@ +{ + "problem_name": "evaluate_reverse_polish_notation", + "solution_class_name": "Solution", + "problem_number": "150", + "problem_title": "Evaluate Reverse Polish Notation", + "difficulty": "Medium", + "topics": "Array, Math, Stack", + "_tags": { "list": ["grind-75"] }, + "readme_description": "You are given an array of strings `tokens` that represents an arithmetic expression in a **Reverse Polish Notation**.\n\nEvaluate the expression. Return *an integer that represents the value of the expression*.\n\n**Note that:**\n\n- The valid operators are `'+'`, `'-'`, `'*'`, and `'/'`.\n- Each operand may be an integer or another expression.\n- The division between two integers always **truncates toward zero**.\n- There will not be any division by zero.\n- The input represents a valid arithmetic expression in a reverse polish notation.\n- The answer and all the intermediate calculations can be represented in a **32-bit** integer.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: tokens = [\"2\",\"1\",\"+\",\"3\",\"*\"]\nOutput: 9\n```\n**Explanation:** ((2 + 1) * 3) = 9" + }, + { + "content": "```\nInput: tokens = [\"4\",\"13\",\"5\",\"/\",\"+\"]\nOutput: 6\n```\n**Explanation:** (4 + (13 / 5)) = 6" + }, + { + "content": "```\nInput: tokens = [\"10\",\"6\",\"9\",\"3\",\"+\",\"-11\",\"*\",\"/\",\"*\",\"17\",\"+\",\"5\",\"+\"]\nOutput: 22\n```\n**Explanation:** ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = 22" + } + ] + }, + "readme_constraints": "- `1 <= tokens.length <= 10^4`\n- `tokens[i]` is either an operator: `\"+\"`, `\"-\"`, `\"*\"`, or `\"/\"`, or an integer in the range `[-200, 200]`.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "eval_rpn", + "helpers_run_signature": "(solution_class: type, tokens: list[str])", + "helpers_run_body": " implementation = solution_class()\n return implementation.eval_rpn(tokens)", + "helpers_assert_name": "eval_rpn", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_eval_rpn, run_eval_rpn\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "EvaluateReversePolishNotation", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "eval_rpn", + "signature": "(self, tokens: list[str]) -> int", + "body": " # TODO: Implement eval_rpn\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_eval_rpn", + "signature": "(self, tokens: list[str], expected: int)", + "parametrize": "tokens, expected", + "test_cases": "[(['2', '1', '+', '3', '*'], 9), (['4', '13', '5', '/', '+'], 6), (['10', '6', '9', '3', '+', '-11', '*', '/', '*', '17', '+', '5', '+'], 22)]", + "body": " result = run_eval_rpn(Solution, tokens)\n assert_eval_rpn(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_eval_rpn, assert_eval_rpn\nfrom solution import Solution", + "playground_setup": "# Example test case\ntokens = ['2', '1', '+', '3', '*']\nexpected = 9", + "playground_run": "result = run_eval_rpn(Solution, tokens)\nresult", + "playground_assert": "assert_eval_rpn(result, expected)" +} diff --git a/.templates/leetcode/json/find_median_from_data_stream.json b/.templates/leetcode/json/find_median_from_data_stream.json new file mode 100644 index 0000000..200846e --- /dev/null +++ b/.templates/leetcode/json/find_median_from_data_stream.json @@ -0,0 +1,69 @@ +{ + "problem_name": "find_median_from_data_stream", + "solution_class_name": "MedianFinder", + "problem_number": "295", + "problem_title": "Find Median from Data Stream", + "difficulty": "Hard", + "topics": "Two Pointers, Design, Sorting, Heap (Priority Queue), Data Stream", + "_tags": { "list": ["grind-75"] }, + "readme_description": "The **median** is the middle value in an ordered integer list. If the size of the list is even, there is no middle value, and the median is the mean of the two middle values.\n\n- For example, for `arr = [2,3,4]`, the median is `3`.\n- For example, for `arr = [2,3]`, the median is `(2 + 3) / 2 = 2.5`.\n\nImplement the MedianFinder class:\n\n- `MedianFinder()` initializes the `MedianFinder` object.\n- `void addNum(int num)` adds the integer `num` from the data stream to the data structure.\n- `double findMedian()` returns the median of all elements so far. Answers within `10^-5` of the actual answer will be accepted.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput\n[\"MedianFinder\", \"addNum\", \"addNum\", \"findMedian\", \"addNum\", \"findMedian\"]\n[[], [1], [2], [], [3], []]\nOutput\n[null, null, null, 1.5, null, 2.0]\n```\n\n**Explanation:**\n```\nMedianFinder medianFinder = new MedianFinder();\nmedianFinder.addNum(1); // arr = [1]\nmedianFinder.addNum(2); // arr = [1, 2]\nmedianFinder.findMedian(); // return 1.5 (i.e., (1 + 2) / 2)\nmedianFinder.addNum(3); // arr = [1, 2, 3]\nmedianFinder.findMedian(); // return 2.0\n```" + } + ] + }, + "readme_constraints": "- `-10^5 <= num <= 10^5`\n- There will be at least one element in the data structure before calling `findMedian`.\n- At most `5 * 10^4` calls will be made to `addNum` and `findMedian`.", + "readme_additional": "**Follow up:**\n\n- If all integer numbers from the stream are in the range `[0, 100]`, how would you optimize your solution?\n- If `99%` of all integer numbers from the stream are in the range `[0, 100]`, how would you optimize your solution?", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "median_finder", + "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[int]])", + "helpers_run_body": " mf = None\n results: list[float | None] = []\n for i, op in enumerate(operations):\n if op == 'MedianFinder':\n mf = solution_class()\n results.append(None)\n elif op == 'addNum' and mf is not None:\n mf.add_num(inputs[i][0])\n results.append(None)\n elif op == 'findMedian' and mf is not None:\n results.append(mf.find_median())\n return results, mf", + "helpers_assert_name": "median_finder", + "helpers_assert_signature": "(result: list[float | None], expected: list[float | None]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_median_finder, run_median_finder\nfrom .solution import MedianFinder", + "test_content": "", + "test_class_name": "FindMedianFromDataStream", + "test_class_content": "", + "_solution_methods": { + "list": [ + { + "name": "__init__", + "signature": "(self) -> None", + "body": " # TODO: Initialize\n pass" + }, + { + "name": "add_num", + "signature": "(self, num: int) -> None", + "body": " # TODO: Implement add_num\n pass" + }, + { + "name": "find_median", + "signature": "(self) -> float", + "body": " # TODO: Implement find_median\n return 0.0" + } + ] + }, + "_test_helper_methods": { "list": [] }, + "_test_methods": { + "list": [ + { + "name": "test_median_finder", + "signature": "(self, operations: list[str], inputs: list[list[int]], expected: list[float | None])", + "parametrize": "operations, inputs, expected", + "test_cases": "[(['MedianFinder', 'addNum', 'addNum', 'findMedian', 'addNum', 'findMedian'], [[], [1], [2], [], [3], []], [None, None, None, 1.5, None, 2.0])]", + "body": " result, _ = run_median_finder(MedianFinder, operations, inputs)\n assert_median_finder(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_median_finder, assert_median_finder\nfrom solution import MedianFinder", + "playground_setup": "# Example test case\noperations = ['MedianFinder', 'addNum', 'addNum', 'findMedian', 'addNum', 'findMedian']\ninputs = [[], [1], [2], [], [3], []]\nexpected = [None, None, None, 1.5, None, 2.0]", + "playground_run": "result, mf = run_median_finder(MedianFinder, operations, inputs)\nprint(result)\nmf", + "playground_assert": "assert_median_finder(result, expected)" +} diff --git a/.templates/leetcode/json/first_bad_version.json b/.templates/leetcode/json/first_bad_version.json new file mode 100644 index 0000000..20b50af --- /dev/null +++ b/.templates/leetcode/json/first_bad_version.json @@ -0,0 +1,67 @@ +{ + "problem_name": "first_bad_version", + "solution_class_name": "Solution", + "problem_number": "278", + "problem_title": "First Bad Version", + "difficulty": "Easy", + "topics": "Binary Search, Interactive", + "_tags": { "list": ["grind-75"] }, + "readme_description": "You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad.\n\nSuppose you have `n` versions `[1, 2, ..., n]` and you want to find out the first bad one, which causes all the following ones to be bad.\n\nYou are given an API `bool isBadVersion(version)` which returns whether `version` is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: n = 5, bad = 4\nOutput: 4\n```\n**Explanation:**\n```\ncall isBadVersion(3) -> false\ncall isBadVersion(5) -> true\ncall isBadVersion(4) -> true\n```\nThen 4 is the first bad version." + }, + { "content": "```\nInput: n = 1, bad = 1\nOutput: 1\n```" } + ] + }, + "readme_constraints": "- 1 <= bad <= n <= 2^31 - 1", + "readme_additional": "**Note:** The `isBadVersion` API is already defined for you.", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "first_bad_version", + "helpers_run_signature": "(solution_class: type, n: int, bad: int)", + "helpers_run_body": " solution = solution_class(bad)\n return solution.first_bad_version(n)", + "helpers_assert_name": "first_bad_version", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_first_bad_version, run_first_bad_version\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "FirstBadVersion", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "__init__", + "signature": "(self, first_bad: int = 1) -> None", + "body": " self.is_bad_version = lambda version: version >= first_bad" + }, + { + "name": "first_bad_version", + "signature": "(self, n: int) -> int", + "body": " # TODO: Implement first_bad_version\n return 1" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_first_bad_version", + "signature": "(self, n: int, bad: int, expected: int)", + "parametrize": "n, bad, expected", + "test_cases": "[(5, 4, 4), (1, 1, 1), (3, 1, 1), (10, 7, 7), (100, 50, 50), (2, 1, 1), (2, 2, 2), (1000, 1, 1), (1000, 999, 999), (1000, 500, 500)]", + "body": " result = run_first_bad_version(Solution, n, bad)\n assert_first_bad_version(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_first_bad_version, assert_first_bad_version\nfrom solution import Solution", + "playground_setup": "# Example test case\nn = 5\nbad = 4\nexpected = 4", + "playground_run": "result = run_first_bad_version(Solution, n, bad)\nresult", + "playground_assert": "assert_first_bad_version(result, expected)" +} diff --git a/Makefile b/Makefile index 7c12c81..67018b2 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,8 @@ test: --cov-report=term-missing \ --cov-report=xml \ --ignore=.templates \ - --ignore=leetcode/__pycache__,leetcode_ideal/__pycache__ + --ignore=leetcode/__pycache__ \ + $(shell if [ -d leetcode_ideal ]; then for dir in leetcode_ideal/*/; do problem=$$(basename "$$dir"); if [ -d "leetcode/$$problem" ]; then echo "--ignore=leetcode_ideal/$$problem"; fi; done; fi) p-test: @echo "Testing problem: $(PROBLEM)" diff --git a/leetcode/binary_tree_right_side_view/playground.ipynb b/leetcode/binary_tree_right_side_view/playground.ipynb index 77ae9a4..9187c4f 100644 --- a/leetcode/binary_tree_right_side_view/playground.ipynb +++ b/leetcode/binary_tree_right_side_view/playground.ipynb @@ -28,6 +28,96 @@ { "cell_type": "code", "execution_count": 3, + "id": "6ba11550", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "0\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "3\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "0->3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "2\n", + "\n", + "5\n", + "\n", + "\n", + "\n", + "1->2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "4\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "3->4\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "TreeNode([1, 2, 3, None, 5, None, 4])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "root = TreeNode.from_list(root_list)\n", + "root" + ] + }, + { + "cell_type": "code", + "execution_count": 4, "id": "run", "metadata": {}, "outputs": [ @@ -37,7 +127,7 @@ "[1, 3, 4]" ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -49,7 +139,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "assert", "metadata": {}, "outputs": [ @@ -59,7 +149,7 @@ "True" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } diff --git a/leetcode/binary_tree_right_side_view/test_solution.py b/leetcode/binary_tree_right_side_view/test_solution.py index 11aab2f..1da8582 100644 --- a/leetcode/binary_tree_right_side_view/test_solution.py +++ b/leetcode/binary_tree_right_side_view/test_solution.py @@ -3,7 +3,7 @@ from leetcode_py.test_utils import logged_test from .helpers import assert_right_side_view, run_right_side_view -from .solution import Solution +from .solution import Solution, SolutionBFS, SolutionDFS class TestBinaryTreeRightSideView: @@ -11,6 +11,7 @@ def setup_method(self): self.solution = Solution() @logged_test + @pytest.mark.parametrize("solution_class", [Solution, SolutionBFS, SolutionDFS]) @pytest.mark.parametrize( "root_list, expected", [ @@ -23,6 +24,8 @@ def setup_method(self): ([1, None, 2], [1, 2]), ], ) - def test_right_side_view(self, root_list: list[int | None], expected: list[int]): - result = run_right_side_view(Solution, root_list) + def test_right_side_view( + self, solution_class: type, root_list: list[int | None], expected: list[int] + ): + result = run_right_side_view(solution_class, root_list) assert_right_side_view(result, expected) diff --git a/leetcode/climbing_stairs/README.md b/leetcode/climbing_stairs/README.md new file mode 100644 index 0000000..18ea888 --- /dev/null +++ b/leetcode/climbing_stairs/README.md @@ -0,0 +1,44 @@ +# Climbing Stairs + +**Difficulty:** Easy +**Topics:** Math, Dynamic Programming, Memoization +**Tags:** grind-75 + +**LeetCode:** [Problem 70](https://leetcode.com/problems/climbing-stairs/description/) + +## Problem Description + +You are climbing a staircase. It takes `n` steps to reach the top. + +Each time you can either climb `1` or `2` steps. In how many distinct ways can you climb to the top? + +## Examples + +### Example 1: + +``` +Input: n = 2 +Output: 2 +``` + +**Explanation:** There are two ways to climb to the top. + +1. 1 step + 1 step +2. 2 steps + +### Example 2: + +``` +Input: n = 3 +Output: 3 +``` + +**Explanation:** There are three ways to climb to the top. + +1. 1 step + 1 step + 1 step +2. 1 step + 2 steps +3. 2 steps + 1 step + +## Constraints + +- 1 <= n <= 45 diff --git a/leetcode/climbing_stairs/__init__.py b/leetcode/climbing_stairs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/climbing_stairs/helpers.py b/leetcode/climbing_stairs/helpers.py new file mode 100644 index 0000000..8730d97 --- /dev/null +++ b/leetcode/climbing_stairs/helpers.py @@ -0,0 +1,8 @@ +def run_climb_stairs(solution_class: type, n: int): + implementation = solution_class() + return implementation.climb_stairs(n) + + +def assert_climb_stairs(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/climbing_stairs/playground.ipynb b/leetcode/climbing_stairs/playground.ipynb new file mode 100644 index 0000000..6fd5d25 --- /dev/null +++ b/leetcode/climbing_stairs/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_climb_stairs, run_climb_stairs\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "n = 3\n", + "expected = 3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_climb_stairs(Solution, n)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_climb_stairs(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/climbing_stairs/solution.py b/leetcode/climbing_stairs/solution.py new file mode 100644 index 0000000..7b7f6a7 --- /dev/null +++ b/leetcode/climbing_stairs/solution.py @@ -0,0 +1,17 @@ +class Solution: + + def climb_stairs(self, n: int) -> int: + # Time: O(n) + # Space: O(1) + + # This follows Fibonacci pattern + # Standard Fib: F(0)=0, F(1)=1, F(2)=1, F(3)=2, F(4)=3, F(5)=5... + if n <= 2: + return n + + prev2, prev1 = 1, 2 + for _ in range(3, n + 1): + current = prev1 + prev2 + prev2, prev1 = prev1, current + + return prev1 diff --git a/leetcode/climbing_stairs/test_solution.py b/leetcode/climbing_stairs/test_solution.py new file mode 100644 index 0000000..7e80934 --- /dev/null +++ b/leetcode/climbing_stairs/test_solution.py @@ -0,0 +1,20 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_climb_stairs, run_climb_stairs +from .solution import Solution + + +class TestClimbingStairs: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "n, expected", + [(1, 1), (2, 2), (3, 3), (4, 5), (5, 8), (6, 13), (10, 89), (20, 10946), (45, 1836311903)], + ) + def test_climb_stairs(self, n: int, expected: int): + result = run_climb_stairs(Solution, n) + assert_climb_stairs(result, expected) diff --git a/leetcode/clone_graph/README.md b/leetcode/clone_graph/README.md new file mode 100644 index 0000000..2e20837 --- /dev/null +++ b/leetcode/clone_graph/README.md @@ -0,0 +1,75 @@ +# Clone Graph + +**Difficulty:** Medium +**Topics:** Hash Table, Depth-First Search, Breadth-First Search, Graph +**Tags:** grind-75 + +**LeetCode:** [Problem 133](https://leetcode.com/problems/clone-graph/description/) + +## Problem Description + +Given a reference of a node in a **connected** undirected graph. + +Return a **deep copy** (clone) of the graph. + +Each node in the graph contains a value (`int`) and a list (`List[Node]`) of its neighbors. + +``` +class Node { + public int val; + public List neighbors; +} +``` + +**Test case format:** + +For simplicity, each node's value is the same as the node's index (1-indexed). For example, the first node with `val == 1`, the second node with `val == 2`, and so on. The graph is represented in the test case using an adjacency list. + +**An adjacency list** is a collection of unordered **lists** used to represent a finite graph. Each list describes the set of neighbors of a node in the graph. + +The given node will always be the first node with `val = 1`. You must return the **copy of the given node** as a reference to the cloned graph. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2019/11/04/133_clone_graph_question.png) + +``` +Input: adjList = [[2,4],[1,3],[2,4],[1,3]] +Output: [[2,4],[1,3],[2,4],[1,3]] +``` + +**Explanation:** There are 4 nodes in the graph. +1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4). +2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3). +3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4). +4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3). + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2020/01/07/graph.png) + +``` +Input: adjList = [[]] +Output: [[]] +``` + +**Explanation:** Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors. + +### Example 3: + +``` +Input: adjList = [] +Output: [] +``` + +**Explanation:** This an empty graph, it does not have any nodes. + +## Constraints + +- The number of nodes in the graph is in the range `[0, 100]`. +- `1 <= Node.val <= 100` +- `Node.val` is unique for each node. +- There are no repeated edges and no self-loops in the graph. +- The Graph is connected and all nodes can be visited starting from the given node. diff --git a/leetcode/clone_graph/__init__.py b/leetcode/clone_graph/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/clone_graph/helpers.py b/leetcode/clone_graph/helpers.py new file mode 100644 index 0000000..5b89b44 --- /dev/null +++ b/leetcode/clone_graph/helpers.py @@ -0,0 +1,16 @@ +from leetcode_py import GraphNode + + +def run_clone_graph(solution_class: type, adj_list: list[list[int]]): + node = GraphNode.from_adjacency_list(adj_list) + implementation = solution_class() + return implementation.clone_graph(node) + + +def assert_clone_graph(result: GraphNode | None, adj_list: list[list[int]]) -> bool: + original = GraphNode.from_adjacency_list(adj_list) + if result is None: + assert original is None + else: + assert result.is_clone(original) + return True diff --git a/leetcode/clone_graph/playground.ipynb b/leetcode/clone_graph/playground.ipynb new file mode 100644 index 0000000..bb2a422 --- /dev/null +++ b/leetcode/clone_graph/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_clone_graph, run_clone_graph\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import GraphNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "adj_list = [[2, 4], [1, 3], [2, 4], [1, 3]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_clone_graph(Solution, adj_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_clone_graph(result, adj_list)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/clone_graph/solution.py b/leetcode/clone_graph/solution.py new file mode 100644 index 0000000..bd53c0b --- /dev/null +++ b/leetcode/clone_graph/solution.py @@ -0,0 +1,73 @@ +from collections import deque + +from leetcode_py import GraphNode + + +class Solution: + # Time: O(V + E) + # Space: O(V) + def clone_graph(self, node: GraphNode | None) -> GraphNode | None: + if node is None: + return None + + def dfs(node: GraphNode, visited: dict[int, GraphNode]): + if node.val in visited: + return visited[node.val] + + clone = GraphNode(node.val) + visited[node.val] = clone + + for neighbor in node.neighbors: + clone.neighbors.append(dfs(neighbor, visited)) + + return clone + + return dfs(node, visited={}) + + +class SolutionDFS: + # DFS Iterative + # Time: O(V + E) + # Space: O(V) + def clone_graph(self, node: GraphNode | None) -> GraphNode | None: + if node is None: + return None + + stack = [node] + visited = {node.val: GraphNode(node.val)} + + while stack: + current = stack.pop() + clone = visited[current.val] + + for neighbor in current.neighbors: + if neighbor.val not in visited: + visited[neighbor.val] = GraphNode(neighbor.val) + stack.append(neighbor) + clone.neighbors.append(visited[neighbor.val]) + + return visited[node.val] + + +class SolutionBFS: + # BFS + # Time: O(V + E) + # Space: O(V) + def clone_graph(self, node: GraphNode | None) -> GraphNode | None: + if node is None: + return None + + queue = deque([node]) + visited = {node.val: GraphNode(node.val)} + + while queue: + current = queue.popleft() + clone = visited[current.val] + + for neighbor in current.neighbors: + if neighbor.val not in visited: + visited[neighbor.val] = GraphNode(neighbor.val) + queue.append(neighbor) + clone.neighbors.append(visited[neighbor.val]) + + return visited[node.val] diff --git a/leetcode/clone_graph/test_solution.py b/leetcode/clone_graph/test_solution.py new file mode 100644 index 0000000..f4825c9 --- /dev/null +++ b/leetcode/clone_graph/test_solution.py @@ -0,0 +1,18 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_clone_graph, run_clone_graph +from .solution import Solution, SolutionBFS, SolutionDFS + + +class TestCloneGraph: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize("solution_class", [Solution, SolutionBFS, SolutionDFS]) + @pytest.mark.parametrize("adj_list", [[[2, 4], [1, 3], [2, 4], [1, 3]], [[]], []]) + def test_clone_graph(self, solution_class: type, adj_list: list[list[int]]): + result = run_clone_graph(solution_class, adj_list) + assert_clone_graph(result, adj_list) diff --git a/leetcode/coin_change/README.md b/leetcode/coin_change/README.md new file mode 100644 index 0000000..54b005e --- /dev/null +++ b/leetcode/coin_change/README.md @@ -0,0 +1,46 @@ +# Coin Change + +**Difficulty:** Medium +**Topics:** Array, Dynamic Programming, Breadth-First Search +**Tags:** grind-75 + +**LeetCode:** [Problem 322](https://leetcode.com/problems/coin-change/description/) + +## Problem Description + +You are given an integer array `coins` representing coins of different denominations and an integer `amount` representing a total amount of money. + +Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return `-1`. + +You may assume that you have an infinite number of each kind of coin. + +## Examples + +### Example 1: + +``` +Input: coins = [1,2,5], amount = 11 +Output: 3 +``` + +**Explanation:** 11 = 5 + 5 + 1 + +### Example 2: + +``` +Input: coins = [2], amount = 3 +Output: -1 +``` + +### Example 3: + +``` +Input: coins = [1], amount = 0 +Output: 0 +``` + +## Constraints + +- `1 <= coins.length <= 12` +- `1 <= coins[i] <= 2^31 - 1` +- `0 <= amount <= 10^4` diff --git a/leetcode/coin_change/__init__.py b/leetcode/coin_change/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/coin_change/helpers.py b/leetcode/coin_change/helpers.py new file mode 100644 index 0000000..ce703f9 --- /dev/null +++ b/leetcode/coin_change/helpers.py @@ -0,0 +1,8 @@ +def run_coin_change(solution_class: type, coins: list[int], amount: int): + implementation = solution_class() + return implementation.coin_change(coins, amount) + + +def assert_coin_change(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/coin_change/playground.ipynb b/leetcode/coin_change/playground.ipynb new file mode 100644 index 0000000..580d39c --- /dev/null +++ b/leetcode/coin_change/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_coin_change, run_coin_change\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "coins = [1, 2, 5]\n", + "amount = 11\n", + "expected = 3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_coin_change(Solution, coins, amount)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_coin_change(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/coin_change/solution.py b/leetcode/coin_change/solution.py new file mode 100644 index 0000000..f808613 --- /dev/null +++ b/leetcode/coin_change/solution.py @@ -0,0 +1,21 @@ +class Solution: + # Time: O(amount * len(coins)) + # Space: O(amount) + def coin_change(self, coins: list[int], amount: int) -> int: + if amount == 0: + return 0 + + # Initialize dp array with amount + 1 (impossible value) + # Since max coins needed is amount (using all 1-cent coins) + # amount + 1 serves as "infinity" to indicate impossible cases + dp = [amount + 1] * (amount + 1) + + dp[0] = 0 + + for i in range(1, amount + 1): + for coin in coins: + if coin <= i: + dp[i] = min(dp[i], dp[i - coin] + 1) + + # Return result: -1 if impossible, otherwise minimum coins needed + return dp[amount] if dp[amount] <= amount else -1 diff --git a/leetcode/coin_change/test_solution.py b/leetcode/coin_change/test_solution.py new file mode 100644 index 0000000..a49a846 --- /dev/null +++ b/leetcode/coin_change/test_solution.py @@ -0,0 +1,30 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_coin_change, run_coin_change +from .solution import Solution + + +class TestCoinChange: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "coins, amount, expected", + [ + ([1, 2, 5], 11, 3), + ([2], 3, -1), + ([1], 0, 0), + ([1, 3, 4], 6, 2), + ([2, 5, 10, 1], 27, 4), + ([5], 3, -1), + ([1], 1, 1), + ([1, 2], 2, 1), + ([186, 419, 83, 408], 6249, 20), + ], + ) + def test_coin_change(self, coins: list[int], amount: int, expected: int): + result = run_coin_change(Solution, coins, amount) + assert_coin_change(result, expected) diff --git a/leetcode/combination_sum/README.md b/leetcode/combination_sum/README.md new file mode 100644 index 0000000..6d59d3e --- /dev/null +++ b/leetcode/combination_sum/README.md @@ -0,0 +1,47 @@ +# Combination Sum + +**Difficulty:** Medium +**Topics:** Array, Backtracking +**Tags:** grind-75 + +**LeetCode:** [Problem 39](https://leetcode.com/problems/combination-sum/description/) + +## Problem Description + +Given an array of **distinct** integers `candidates` and a target integer `target`, return _a list of all **unique combinations** of_ `candidates` _where the chosen numbers sum to_ `target`. You may return the combinations in **any order**. + +The **same** number may be chosen from `candidates` an **unlimited number of times**. Two combinations are unique if the frequency of at least one of the chosen numbers is different. + +The test cases are generated such that the number of unique combinations that sum up to `target` is less than `150` combinations for the given input. + +## Examples + +### Example 1: + +``` +Input: candidates = [2,3,6,7], target = 7 +Output: [[2,2,3],[7]] +``` + +**Explanation:** 2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times. 7 is a candidate, and 7 = 7. These are the only two combinations. + +### Example 2: + +``` +Input: candidates = [2,3,5], target = 8 +Output: [[2,2,2,2],[2,3,3],[3,5]] +``` + +### Example 3: + +``` +Input: candidates = [2], target = 1 +Output: [] +``` + +## Constraints + +- 1 <= candidates.length <= 30 +- 2 <= candidates[i] <= 40 +- All elements of candidates are distinct. +- 1 <= target <= 40 diff --git a/leetcode/combination_sum/__init__.py b/leetcode/combination_sum/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/combination_sum/helpers.py b/leetcode/combination_sum/helpers.py new file mode 100644 index 0000000..15af627 --- /dev/null +++ b/leetcode/combination_sum/helpers.py @@ -0,0 +1,13 @@ +def run_combination_sum(solution_class: type, candidates: list[int], target: int): + implementation = solution_class() + return implementation.combination_sum(candidates, target) + + +def assert_combination_sum(result: list[list[int]], expected: list[list[int]]) -> bool: + # Sort both result and expected for comparison + result_sorted = [sorted(combo) for combo in result] + expected_sorted = [sorted(combo) for combo in expected] + result_sorted.sort() + expected_sorted.sort() + assert result_sorted == expected_sorted + return True diff --git a/leetcode/combination_sum/playground.ipynb b/leetcode/combination_sum/playground.ipynb new file mode 100644 index 0000000..ac0711f --- /dev/null +++ b/leetcode/combination_sum/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_combination_sum, run_combination_sum\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "candidates = [2, 3, 6, 7]\n", + "target = 7\n", + "expected = [[2, 2, 3], [7]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_combination_sum(Solution, candidates, target)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_combination_sum(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/combination_sum/solution.py b/leetcode/combination_sum/solution.py new file mode 100644 index 0000000..e0508a3 --- /dev/null +++ b/leetcode/combination_sum/solution.py @@ -0,0 +1,19 @@ +class Solution: + # Time: O(N^(T/M)) where N=len(candidates), T=target, M=min(candidates) + # Space: O(T/M) recursion + O(K * T/M) output, where K = number of solutions + def combination_sum(self, candidates: list[int], target: int) -> list[list[int]]: + result = [] + + def backtrack(start: int, path: list[int], remaining: int) -> None: + if remaining == 0: + result.append(path[:]) + return + + for i in range(start, len(candidates)): + if candidates[i] <= remaining: + path.append(candidates[i]) + backtrack(i, path, remaining - candidates[i]) + path.pop() + + backtrack(0, [], target) + return result diff --git a/leetcode/combination_sum/test_solution.py b/leetcode/combination_sum/test_solution.py new file mode 100644 index 0000000..3bbe52b --- /dev/null +++ b/leetcode/combination_sum/test_solution.py @@ -0,0 +1,24 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_combination_sum, run_combination_sum +from .solution import Solution + + +class TestCombinationSum: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "candidates, target, expected", + [ + ([2, 3, 6, 7], 7, [[2, 2, 3], [7]]), + ([2, 3, 5], 8, [[2, 2, 2, 2], [2, 3, 3], [3, 5]]), + ([2], 1, []), + ], + ) + def test_combination_sum(self, candidates: list[int], target: int, expected: list[list[int]]): + result = run_combination_sum(Solution, candidates, target) + assert_combination_sum(result, expected) diff --git a/leetcode/container_with_most_water/README.md b/leetcode/container_with_most_water/README.md new file mode 100644 index 0000000..a9ee63f --- /dev/null +++ b/leetcode/container_with_most_water/README.md @@ -0,0 +1,43 @@ +# Container With Most Water + +**Difficulty:** Medium +**Topics:** Array, Two Pointers, Greedy +**Tags:** grind-75 + +**LeetCode:** [Problem 11](https://leetcode.com/problems/container-with-most-water/description/) + +## Problem Description + +You are given an integer array `height` of length `n`. There are `n` vertical lines drawn such that the two endpoints of the `i`th line are `(i, 0)` and `(i, height[i])`. + +Find two lines that together with the x-axis form a container, such that the container contains the most water. + +Return the maximum amount of water a container can store. + +Notice that you may not slant the container. + +## Examples + +### Example 1: + +![Example 1](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/07/17/question_11.jpg) + +``` +Input: height = [1,8,6,2,5,4,8,3,7] +Output: 49 +``` + +**Explanation:** The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49. + +### Example 2: + +``` +Input: height = [1,1] +Output: 1 +``` + +## Constraints + +- n == height.length +- 2 <= n <= 10^5 +- 0 <= height[i] <= 10^4 diff --git a/leetcode/container_with_most_water/__init__.py b/leetcode/container_with_most_water/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/container_with_most_water/helpers.py b/leetcode/container_with_most_water/helpers.py new file mode 100644 index 0000000..0ef5844 --- /dev/null +++ b/leetcode/container_with_most_water/helpers.py @@ -0,0 +1,8 @@ +def run_max_area(solution_class: type, height: list[int]): + implementation = solution_class() + return implementation.max_area(height) + + +def assert_max_area(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/container_with_most_water/playground.ipynb b/leetcode/container_with_most_water/playground.ipynb new file mode 100644 index 0000000..a95df0a --- /dev/null +++ b/leetcode/container_with_most_water/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_max_area, run_max_area\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "height = [1, 8, 6, 2, 5, 4, 8, 3, 7]\n", + "expected = 49" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_max_area(Solution, height)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_max_area(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/container_with_most_water/solution.py b/leetcode/container_with_most_water/solution.py new file mode 100644 index 0000000..34a1025 --- /dev/null +++ b/leetcode/container_with_most_water/solution.py @@ -0,0 +1,17 @@ +class Solution: + # Time: O(n) + # Space: O(1) + def max_area(self, height: list[int]) -> int: + left = 0 + right = len(height) - 1 + max_area_so_far = 0 + + while left < right: + area = min(height[left], height[right]) * (right - left) + max_area_so_far = max(area, max_area_so_far) + if height[right] > height[left]: + left += 1 + else: + right -= 1 + + return max_area_so_far diff --git a/leetcode/container_with_most_water/test_solution.py b/leetcode/container_with_most_water/test_solution.py new file mode 100644 index 0000000..4ac5842 --- /dev/null +++ b/leetcode/container_with_most_water/test_solution.py @@ -0,0 +1,19 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_max_area, run_max_area +from .solution import Solution + + +class TestContainerWithMostWater: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "height, expected", [([1, 8, 6, 2, 5, 4, 8, 3, 7], 49), ([1, 1], 1), ([1, 2, 1], 2)] + ) + def test_max_area(self, height: list[int], expected: int): + result = run_max_area(Solution, height) + assert_max_area(result, expected) diff --git a/leetcode/contains_duplicate/README.md b/leetcode/contains_duplicate/README.md new file mode 100644 index 0000000..c09c400 --- /dev/null +++ b/leetcode/contains_duplicate/README.md @@ -0,0 +1,43 @@ +# Contains Duplicate + +**Difficulty:** Easy +**Topics:** Array, Hash Table, Sorting +**Tags:** grind-75 + +**LeetCode:** [Problem 217](https://leetcode.com/problems/contains-duplicate/description/) + +## Problem Description + +Given an integer array `nums`, return `true` if any value appears **at least twice** in the array, and return `false` if every element is distinct. + +## Examples + +### Example 1: + +``` +Input: nums = [1,2,3,1] +Output: true +``` + +**Explanation:** The element 1 occurs at the indices 0 and 3. + +### Example 2: + +``` +Input: nums = [1,2,3,4] +Output: false +``` + +**Explanation:** All elements are distinct. + +### Example 3: + +``` +Input: nums = [1,1,1,3,3,4,3,2,4,2] +Output: true +``` + +## Constraints + +- 1 <= nums.length <= 10^5 +- -10^9 <= nums[i] <= 10^9 diff --git a/leetcode/contains_duplicate/__init__.py b/leetcode/contains_duplicate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/contains_duplicate/helpers.py b/leetcode/contains_duplicate/helpers.py new file mode 100644 index 0000000..c1e7c5e --- /dev/null +++ b/leetcode/contains_duplicate/helpers.py @@ -0,0 +1,8 @@ +def run_contains_duplicate(solution_class: type, nums: list[int]): + implementation = solution_class() + return implementation.contains_duplicate(nums) + + +def assert_contains_duplicate(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/contains_duplicate/playground.ipynb b/leetcode/contains_duplicate/playground.ipynb new file mode 100644 index 0000000..f43d0de --- /dev/null +++ b/leetcode/contains_duplicate/playground.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_contains_duplicate, run_contains_duplicate\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [1, 2, 3, 1]\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "run", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_contains_duplicate(Solution, nums)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "assert", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_contains_duplicate(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/contains_duplicate/solution.py b/leetcode/contains_duplicate/solution.py new file mode 100644 index 0000000..b9bd400 --- /dev/null +++ b/leetcode/contains_duplicate/solution.py @@ -0,0 +1,10 @@ +class Solution: + # Time: O(n) + # Space: O(n) + def contains_duplicate(self, nums: list[int]) -> bool: + seen = set() + for num in nums: + if num in seen: + return True + seen.add(num) + return False diff --git a/leetcode/contains_duplicate/test_solution.py b/leetcode/contains_duplicate/test_solution.py new file mode 100644 index 0000000..5eaa35a --- /dev/null +++ b/leetcode/contains_duplicate/test_solution.py @@ -0,0 +1,20 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_contains_duplicate, run_contains_duplicate +from .solution import Solution + + +class TestContainsDuplicate: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [([1, 2, 3, 1], True), ([1, 2, 3, 4], False), ([1, 1, 1, 3, 3, 4, 3, 2, 4, 2], True)], + ) + def test_contains_duplicate(self, nums: list[int], expected: bool): + result = run_contains_duplicate(Solution, nums) + assert_contains_duplicate(result, expected) diff --git a/leetcode/course_schedule/README.md b/leetcode/course_schedule/README.md new file mode 100644 index 0000000..bdc1cdd --- /dev/null +++ b/leetcode/course_schedule/README.md @@ -0,0 +1,43 @@ +# Course Schedule + +**Difficulty:** Medium +**Topics:** Depth-First Search, Breadth-First Search, Graph, Topological Sort +**Tags:** grind-75 + +**LeetCode:** [Problem 207](https://leetcode.com/problems/course-schedule/description/) + +## Problem Description + +There are a total of `numCourses` courses you have to take, labeled from `0` to `numCourses - 1`. You are given an array `prerequisites` where `prerequisites[i] = [ai, bi]` indicates that you **must** take course `bi` first if you want to take course `ai`. + +- For example, the pair `[0, 1]`, indicates that to take course `0` you have to first take course `1`. + +Return `true` if you can finish all courses. Otherwise, return `false`. + +## Examples + +### Example 1: + +``` +Input: numCourses = 2, prerequisites = [[1,0]] +Output: true +``` + +**Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible. + +### Example 2: + +``` +Input: numCourses = 2, prerequisites = [[1,0],[0,1]] +Output: false +``` + +**Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible. + +## Constraints + +- `1 <= numCourses <= 2000` +- `0 <= prerequisites.length <= 5000` +- `prerequisites[i].length == 2` +- `0 <= ai, bi < numCourses` +- All the pairs prerequisites[i] are **unique**. diff --git a/leetcode/course_schedule/__init__.py b/leetcode/course_schedule/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/course_schedule/helpers.py b/leetcode/course_schedule/helpers.py new file mode 100644 index 0000000..67b7f87 --- /dev/null +++ b/leetcode/course_schedule/helpers.py @@ -0,0 +1,8 @@ +def run_can_finish(solution_class: type, num_courses: int, prerequisites: list[list[int]]): + implementation = solution_class() + return implementation.can_finish(num_courses, prerequisites) + + +def assert_can_finish(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/course_schedule/playground.ipynb b/leetcode/course_schedule/playground.ipynb new file mode 100644 index 0000000..2dfeb00 --- /dev/null +++ b/leetcode/course_schedule/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_can_finish, run_can_finish\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "num_courses = 2\n", + "prerequisites = [[1, 0]]\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_can_finish(Solution, num_courses, prerequisites)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_can_finish(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/course_schedule/solution.py b/leetcode/course_schedule/solution.py new file mode 100644 index 0000000..925178c --- /dev/null +++ b/leetcode/course_schedule/solution.py @@ -0,0 +1,30 @@ +class Solution: + + # Time: O(V + E) where V = num_courses, E = prerequisites + # Space: O(V + E) for adjacency list and recursion stack + def can_finish(self, num_courses: int, prerequisites: list[list[int]]) -> bool: + UNVISITED, VISITING, VISITED = 0, 1, 2 + + graph: list[list[int]] = [[] for _ in range(num_courses)] + for course, prereq in prerequisites: + graph[course].append(prereq) + + state = [UNVISITED] * num_courses + + def has_cycle(course: int) -> bool: + if state[course] == VISITING: # Currently visiting - cycle detected + return True + if state[course] == VISITED: + return False + + state[course] = VISITING + for prereq in graph[course]: + if has_cycle(prereq): + return True + state[course] = VISITED + return False + + for course in range(num_courses): + if state[course] == UNVISITED and has_cycle(course): + return False + return True diff --git a/leetcode/course_schedule/test_solution.py b/leetcode/course_schedule/test_solution.py new file mode 100644 index 0000000..2fc24fc --- /dev/null +++ b/leetcode/course_schedule/test_solution.py @@ -0,0 +1,26 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_can_finish, run_can_finish +from .solution import Solution + + +class TestCourseSchedule: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "num_courses, prerequisites, expected", + [ + (2, [[1, 0]], True), + (2, [[1, 0], [0, 1]], False), + (1, [], True), + (3, [[1, 0], [2, 1]], True), + (4, [[1, 0], [2, 1], [3, 2], [1, 3]], False), + ], + ) + def test_can_finish(self, num_courses: int, prerequisites: list[list[int]], expected: bool): + result = run_can_finish(Solution, num_courses, prerequisites) + assert_can_finish(result, expected) diff --git a/leetcode/diameter_of_binary_tree/README.md b/leetcode/diameter_of_binary_tree/README.md new file mode 100644 index 0000000..4b1580b --- /dev/null +++ b/leetcode/diameter_of_binary_tree/README.md @@ -0,0 +1,40 @@ +# Diameter of Binary Tree + +**Difficulty:** Easy +**Topics:** Tree, Depth-First Search, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 543](https://leetcode.com/problems/diameter-of-binary-tree/description/) + +## Problem Description + +Given the `root` of a binary tree, return the length of the **diameter** of the tree. + +The **diameter** of a binary tree is the **length** of the longest path between any two nodes in a tree. This path may or may not pass through the `root`. + +The **length** of a path between two nodes is represented by the number of edges between them. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2021/03/06/diamtree.jpg) + +``` +Input: root = [1,2,3,4,5] +Output: 3 +``` + +**Explanation:** 3 is the length of the path [4,2,1,3] or [5,2,1,3]. + +### Example 2: + +``` +Input: root = [1,2] +Output: 1 +``` + +## Constraints + +- The number of nodes in the tree is in the range [1, 10^4]. +- -100 <= Node.val <= 100 diff --git a/leetcode/diameter_of_binary_tree/__init__.py b/leetcode/diameter_of_binary_tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/diameter_of_binary_tree/helpers.py b/leetcode/diameter_of_binary_tree/helpers.py new file mode 100644 index 0000000..7885029 --- /dev/null +++ b/leetcode/diameter_of_binary_tree/helpers.py @@ -0,0 +1,12 @@ +from leetcode_py import TreeNode + + +def run_diameter_of_binary_tree(solution_class: type, root_list: list[int | None]): + root = TreeNode[int].from_list(root_list) + implementation = solution_class() + return implementation.diameter_of_binary_tree(root) + + +def assert_diameter_of_binary_tree(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/diameter_of_binary_tree/playground.ipynb b/leetcode/diameter_of_binary_tree/playground.ipynb new file mode 100644 index 0000000..d401bcf --- /dev/null +++ b/leetcode/diameter_of_binary_tree/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_diameter_of_binary_tree, run_diameter_of_binary_tree\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import TreeNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root_list: list[int | None] = [1, 2, 3, 4, 5]\n", + "expected = 3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_diameter_of_binary_tree(Solution, root_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_diameter_of_binary_tree(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/diameter_of_binary_tree/solution.py b/leetcode/diameter_of_binary_tree/solution.py new file mode 100644 index 0000000..e54b5ed --- /dev/null +++ b/leetcode/diameter_of_binary_tree/solution.py @@ -0,0 +1,23 @@ +from leetcode_py import TreeNode + + +class Solution: + + # Time: O(n) + # Space: O(h) + def diameter_of_binary_tree(self, root: TreeNode[int] | None) -> int: + self.max_diameter = 0 + + def dfs(node: TreeNode[int] | None) -> int: + if not node: + return 0 + + left = dfs(node.left) + right = dfs(node.right) + + self.max_diameter = max(self.max_diameter, left + right) + + return max(left, right) + 1 + + dfs(root) + return self.max_diameter diff --git a/leetcode/diameter_of_binary_tree/test_solution.py b/leetcode/diameter_of_binary_tree/test_solution.py new file mode 100644 index 0000000..1abc301 --- /dev/null +++ b/leetcode/diameter_of_binary_tree/test_solution.py @@ -0,0 +1,20 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_diameter_of_binary_tree, run_diameter_of_binary_tree +from .solution import Solution + + +class TestDiameterOfBinaryTree: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "root_list, expected", + [([1, 2, 3, 4, 5], 3), ([1, 2], 1), ([], 0), ([1], 0), ([1, 2, 3], 2), ([1, None, 2], 1)], + ) + def test_diameter_of_binary_tree(self, root_list: list[int | None], expected: int): + result = run_diameter_of_binary_tree(Solution, root_list) + assert_diameter_of_binary_tree(result, expected) diff --git a/leetcode/evaluate_reverse_polish_notation/README.md b/leetcode/evaluate_reverse_polish_notation/README.md new file mode 100644 index 0000000..4015f66 --- /dev/null +++ b/leetcode/evaluate_reverse_polish_notation/README.md @@ -0,0 +1,56 @@ +# Evaluate Reverse Polish Notation + +**Difficulty:** Medium +**Topics:** Array, Math, Stack +**Tags:** grind-75 + +**LeetCode:** [Problem 150](https://leetcode.com/problems/evaluate-reverse-polish-notation/description/) + +## Problem Description + +You are given an array of strings `tokens` that represents an arithmetic expression in a **Reverse Polish Notation**. + +Evaluate the expression. Return _an integer that represents the value of the expression_. + +**Note that:** + +- The valid operators are `'+'`, `'-'`, `'*'`, and `'/'`. +- Each operand may be an integer or another expression. +- The division between two integers always **truncates toward zero**. +- There will not be any division by zero. +- The input represents a valid arithmetic expression in a reverse polish notation. +- The answer and all the intermediate calculations can be represented in a **32-bit** integer. + +## Examples + +### Example 1: + +``` +Input: tokens = ["2","1","+","3","*"] +Output: 9 +``` + +**Explanation:** ((2 + 1) \* 3) = 9 + +### Example 2: + +``` +Input: tokens = ["4","13","5","/","+"] +Output: 6 +``` + +**Explanation:** (4 + (13 / 5)) = 6 + +### Example 3: + +``` +Input: tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"] +Output: 22 +``` + +**Explanation:** ((10 _ (6 / ((9 + 3) _ -11))) + 17) + 5 = 22 + +## Constraints + +- `1 <= tokens.length <= 10^4` +- `tokens[i]` is either an operator: `"+"`, `"-"`, `"*"`, or `"/"`, or an integer in the range `[-200, 200]`. diff --git a/leetcode/evaluate_reverse_polish_notation/__init__.py b/leetcode/evaluate_reverse_polish_notation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/evaluate_reverse_polish_notation/helpers.py b/leetcode/evaluate_reverse_polish_notation/helpers.py new file mode 100644 index 0000000..540042e --- /dev/null +++ b/leetcode/evaluate_reverse_polish_notation/helpers.py @@ -0,0 +1,8 @@ +def run_eval_rpn(solution_class: type, tokens: list[str]): + implementation = solution_class() + return implementation.eval_rpn(tokens) + + +def assert_eval_rpn(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/evaluate_reverse_polish_notation/playground.ipynb b/leetcode/evaluate_reverse_polish_notation/playground.ipynb new file mode 100644 index 0000000..01fd8b8 --- /dev/null +++ b/leetcode/evaluate_reverse_polish_notation/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_eval_rpn, run_eval_rpn\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "tokens = [\"2\", \"1\", \"+\", \"3\", \"*\"]\n", + "expected = 9" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_eval_rpn(Solution, tokens)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_eval_rpn(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/evaluate_reverse_polish_notation/solution.py b/leetcode/evaluate_reverse_polish_notation/solution.py new file mode 100644 index 0000000..d6496e4 --- /dev/null +++ b/leetcode/evaluate_reverse_polish_notation/solution.py @@ -0,0 +1,21 @@ +class Solution: + + # Time: O(n) + # Space: O(n) + def eval_rpn(self, tokens: list[str]) -> int: + stack: list[int] = [] + ops = { + "+": lambda a, b: a + b, + "-": lambda a, b: a - b, + "*": lambda a, b: a * b, + "/": lambda a, b: int(a / b), + } + + for token in tokens: + if token in ops: + b, a = stack.pop(), stack.pop() + stack.append(ops[token](a, b)) + else: + stack.append(int(token)) + + return stack[0] diff --git a/leetcode/evaluate_reverse_polish_notation/test_solution.py b/leetcode/evaluate_reverse_polish_notation/test_solution.py new file mode 100644 index 0000000..e422248 --- /dev/null +++ b/leetcode/evaluate_reverse_polish_notation/test_solution.py @@ -0,0 +1,24 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_eval_rpn, run_eval_rpn +from .solution import Solution + + +class TestEvaluateReversePolishNotation: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "tokens, expected", + [ + (["2", "1", "+", "3", "*"], 9), + (["4", "13", "5", "/", "+"], 6), + (["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"], 22), + ], + ) + def test_eval_rpn(self, tokens: list[str], expected: int): + result = run_eval_rpn(Solution, tokens) + assert_eval_rpn(result, expected) diff --git a/leetcode/find_median_from_data_stream/README.md b/leetcode/find_median_from_data_stream/README.md new file mode 100644 index 0000000..90ace29 --- /dev/null +++ b/leetcode/find_median_from_data_stream/README.md @@ -0,0 +1,54 @@ +# Find Median from Data Stream + +**Difficulty:** Hard +**Topics:** Two Pointers, Design, Sorting, Heap (Priority Queue), Data Stream +**Tags:** grind-75 + +**LeetCode:** [Problem 295](https://leetcode.com/problems/find-median-from-data-stream/description/) + +## Problem Description + +The **median** is the middle value in an ordered integer list. If the size of the list is even, there is no middle value, and the median is the mean of the two middle values. + +- For example, for `arr = [2,3,4]`, the median is `3`. +- For example, for `arr = [2,3]`, the median is `(2 + 3) / 2 = 2.5`. + +Implement the MedianFinder class: + +- `MedianFinder()` initializes the `MedianFinder` object. +- `void addNum(int num)` adds the integer `num` from the data stream to the data structure. +- `double findMedian()` returns the median of all elements so far. Answers within `10^-5` of the actual answer will be accepted. + +## Examples + +### Example 1: + +``` +Input +["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"] +[[], [1], [2], [], [3], []] +Output +[null, null, null, 1.5, null, 2.0] +``` + +**Explanation:** + +``` +MedianFinder medianFinder = new MedianFinder(); +medianFinder.addNum(1); // arr = [1] +medianFinder.addNum(2); // arr = [1, 2] +medianFinder.findMedian(); // return 1.5 (i.e., (1 + 2) / 2) +medianFinder.addNum(3); // arr = [1, 2, 3] +medianFinder.findMedian(); // return 2.0 +``` + +## Constraints + +- `-10^5 <= num <= 10^5` +- There will be at least one element in the data structure before calling `findMedian`. +- At most `5 * 10^4` calls will be made to `addNum` and `findMedian`. + +**Follow up:** + +- If all integer numbers from the stream are in the range `[0, 100]`, how would you optimize your solution? +- If `99%` of all integer numbers from the stream are in the range `[0, 100]`, how would you optimize your solution? diff --git a/leetcode/find_median_from_data_stream/__init__.py b/leetcode/find_median_from_data_stream/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/find_median_from_data_stream/helpers.py b/leetcode/find_median_from_data_stream/helpers.py new file mode 100644 index 0000000..c16a010 --- /dev/null +++ b/leetcode/find_median_from_data_stream/helpers.py @@ -0,0 +1,18 @@ +def run_median_finder(solution_class: type, operations: list[str], inputs: list[list[int]]): + mf = None + results: list[float | None] = [] + for i, op in enumerate(operations): + if op == "MedianFinder": + mf = solution_class() + results.append(None) + elif op == "addNum" and mf is not None: + mf.add_num(inputs[i][0]) + results.append(None) + elif op == "findMedian" and mf is not None: + results.append(mf.find_median()) + return results, mf + + +def assert_median_finder(result: list[float | None], expected: list[float | None]) -> bool: + assert result == expected + return True diff --git a/leetcode/find_median_from_data_stream/playground.ipynb b/leetcode/find_median_from_data_stream/playground.ipynb new file mode 100644 index 0000000..3a72e85 --- /dev/null +++ b/leetcode/find_median_from_data_stream/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_median_finder, run_median_finder\n", + "from solution import MedianFinder" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "operations = [\"MedianFinder\", \"addNum\", \"addNum\", \"findMedian\", \"addNum\", \"findMedian\"]\n", + "inputs = [[], [1], [2], [], [3], []]\n", + "expected = [None, None, None, 1.5, None, 2.0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result, mf = run_median_finder(MedianFinder, operations, inputs)\n", + "print(result)\n", + "mf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_median_finder(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/find_median_from_data_stream/solution.py b/leetcode/find_median_from_data_stream/solution.py new file mode 100644 index 0000000..af1924e --- /dev/null +++ b/leetcode/find_median_from_data_stream/solution.py @@ -0,0 +1,115 @@ +import heapq + + +class MedianFinder: + # Two balanced heaps approach for general streaming median + # Time: O(1) init + # Space: O(n) + def __init__(self) -> None: + self.small: list[int] = [] # max heap (negated) + self.large: list[int] = [] # min heap + + # Time: O(log n) + # Space: O(1) + def add_num(self, num: int) -> None: + heapq.heappush(self.small, -num) + + if self.small and self.large and (-self.small[0] > self.large[0]): + heapq.heappush(self.large, -heapq.heappop(self.small)) + + if len(self.small) > len(self.large) + 1: + heapq.heappush(self.large, -heapq.heappop(self.small)) + if len(self.large) > len(self.small) + 1: + heapq.heappush(self.small, -heapq.heappop(self.large)) + + # Time: O(1) + # Space: O(1) + def find_median(self) -> float: + if len(self.small) > len(self.large): + return -self.small[0] + if len(self.large) > len(self.small): + return self.large[0] + return (-self.small[0] + self.large[0]) / 2.0 + + +class MedianFinderHybrid: + # Hybrid counting array + heaps for bounded ranges with outliers + # Time: O(1) init + # Space: O(R + k) where R = range_size, k = outliers + def __init__(self, min_val: int = 0, max_val: int = 100) -> None: + self.min_val = min_val + self.max_val = max_val + self.counts = [0] * (max_val - min_val + 1) + self.outliers_small: list[int] = [] # max heap for < min_val + self.outliers_large: list[int] = [] # min heap for > max_val + self.total = 0 + + # Time: O(1) for range, O(log k) for outliers + # Space: O(1) + def add_num(self, num: int) -> None: + if self.min_val <= num <= self.max_val: + self.counts[num - self.min_val] += 1 + elif num < self.min_val: + heapq.heappush(self.outliers_small, -num) + else: + heapq.heappush(self.outliers_large, num) + self.total += 1 + + # Time: O(R + k log k) worst case, O(R) typical, O(1) if R constant + # Space: O(k) for sorting outliers + def find_median(self) -> float: + target = self.total // 2 + count = 0 + + # Count outliers < 0 + outliers_small_count = len(self.outliers_small) + if count + outliers_small_count > target: + sorted_small = sorted([-x for x in self.outliers_small]) + if self.total % 2 == 1: + return sorted_small[target - count] + else: + if target - count == 0: + return (sorted_small[0] + self._get_next_value(0)) / 2.0 + return (sorted_small[target - count - 1] + sorted_small[target - count]) / 2.0 + count += outliers_small_count + + # Count [min_val, max_val] range + for i in range(len(self.counts)): + if count + self.counts[i] > target: + val = i + self.min_val + if self.total % 2 == 1: + return val + else: + if target == count: + return (self._get_prev_value(count - 1) + val) / 2.0 + return val + count += self.counts[i] + + # Must be in outliers > 100 + sorted_large = sorted(self.outliers_large) + idx = target - count + if self.total % 2 == 1: + return sorted_large[idx] + else: + if idx == 0: + return (self._get_prev_value(count - 1) + sorted_large[0]) / 2.0 + return (sorted_large[idx - 1] + sorted_large[idx]) / 2.0 + + def _get_prev_value(self, pos: int) -> int: + count = 0 + # Check outliers < 0 + if pos < len(self.outliers_small): + return sorted([-x for x in self.outliers_small])[pos] + count += len(self.outliers_small) + + # Check [min_val, max_val] range + for i in range(len(self.counts)): + if count + self.counts[i] > pos: + return i + self.min_val + count += self.counts[i] + + # Must be in outliers > 100 + return sorted(self.outliers_large)[pos - count] + + def _get_next_value(self, pos: int) -> int: + return self._get_prev_value(pos + 1) diff --git a/leetcode/find_median_from_data_stream/test_solution.py b/leetcode/find_median_from_data_stream/test_solution.py new file mode 100644 index 0000000..a4f759d --- /dev/null +++ b/leetcode/find_median_from_data_stream/test_solution.py @@ -0,0 +1,31 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_median_finder, run_median_finder +from .solution import MedianFinder, MedianFinderHybrid + + +class TestFindMedianFromDataStream: + + @logged_test + @pytest.mark.parametrize("solution_class", [MedianFinder, MedianFinderHybrid]) + @pytest.mark.parametrize( + "operations, inputs, expected", + [ + ( + ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"], + [[], [1], [2], [], [3], []], + [None, None, None, 1.5, None, 2.0], + ) + ], + ) + def test_median_finder( + self, + solution_class: type, + operations: list[str], + inputs: list[list[int]], + expected: list[float | None], + ): + result, _ = run_median_finder(solution_class, operations, inputs) + assert_median_finder(result, expected) diff --git a/leetcode/first_bad_version/README.md b/leetcode/first_bad_version/README.md new file mode 100644 index 0000000..a710eb0 --- /dev/null +++ b/leetcode/first_bad_version/README.md @@ -0,0 +1,47 @@ +# First Bad Version + +**Difficulty:** Easy +**Topics:** Binary Search, Interactive +**Tags:** grind-75 + +**LeetCode:** [Problem 278](https://leetcode.com/problems/first-bad-version/description/) + +## Problem Description + +You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad. + +Suppose you have `n` versions `[1, 2, ..., n]` and you want to find out the first bad one, which causes all the following ones to be bad. + +You are given an API `bool isBadVersion(version)` which returns whether `version` is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API. + +## Examples + +### Example 1: + +``` +Input: n = 5, bad = 4 +Output: 4 +``` + +**Explanation:** + +``` +call isBadVersion(3) -> false +call isBadVersion(5) -> true +call isBadVersion(4) -> true +``` + +Then 4 is the first bad version. + +### Example 2: + +``` +Input: n = 1, bad = 1 +Output: 1 +``` + +## Constraints + +- 1 <= bad <= n <= 2^31 - 1 + +**Note:** The `isBadVersion` API is already defined for you. diff --git a/leetcode/first_bad_version/__init__.py b/leetcode/first_bad_version/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/first_bad_version/helpers.py b/leetcode/first_bad_version/helpers.py new file mode 100644 index 0000000..a84be2a --- /dev/null +++ b/leetcode/first_bad_version/helpers.py @@ -0,0 +1,8 @@ +def run_first_bad_version(solution_class: type, n: int, bad: int): + solution = solution_class(bad) + return solution.first_bad_version(n) + + +def assert_first_bad_version(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/first_bad_version/playground.ipynb b/leetcode/first_bad_version/playground.ipynb new file mode 100644 index 0000000..eff1efe --- /dev/null +++ b/leetcode/first_bad_version/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_first_bad_version, run_first_bad_version\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "n = 5\n", + "bad = 4\n", + "expected = 4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_first_bad_version(Solution, n, bad)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_first_bad_version(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/first_bad_version/solution.py b/leetcode/first_bad_version/solution.py new file mode 100644 index 0000000..623e544 --- /dev/null +++ b/leetcode/first_bad_version/solution.py @@ -0,0 +1,47 @@ +class Solution: + + # Time: O(?) + # Space: O(?) + def __init__(self, first_bad: int = 1) -> None: + self.is_bad_version = lambda version: version >= first_bad + + # Time: O(log n) + # Space: O(1) + def first_bad_version(self, n: int) -> int: + left = 1 + right = n + + while left < right: + mid = (left + right) // 2 + if self.is_bad_version(mid): + right = mid + else: + left = mid + 1 + + return right + + +# BISECT PATTERNS - General Binary Search +# Given: arr = [10,20,30,30,30,40,50], target = 30 +# 0 1 2 3 4 5 6 +# +# bisect_left: Find FIRST occurrence (leftmost insertion point) +# while left < right: +# if arr[mid] >= target: # >= keeps moving left +# right = mid +# Returns: 2 (index of first 30, value=30) +# [10,20,30,30,30,40,50] +# 0 1 2 3 4 5 6 +# ↑ index 2 +# +# bisect_right: Find position AFTER last occurrence +# while left < right: +# if arr[mid] > target: # > allows equal values +# right = mid +# Returns: 5 (index after last 30, value=40) +# [10,20,30,30,30,40,50] +# 0 1 2 3 4 5 6 +# ↑ index 5 +# +# Key difference: >= vs > in the condition +# This problem uses bisect_left pattern to find first bad version diff --git a/leetcode/first_bad_version/test_solution.py b/leetcode/first_bad_version/test_solution.py new file mode 100644 index 0000000..12f25a5 --- /dev/null +++ b/leetcode/first_bad_version/test_solution.py @@ -0,0 +1,31 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_first_bad_version, run_first_bad_version +from .solution import Solution + + +class TestFirstBadVersion: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "n, bad, expected", + [ + (5, 4, 4), + (1, 1, 1), + (3, 1, 1), + (10, 7, 7), + (100, 50, 50), + (2, 1, 1), + (2, 2, 2), + (1000, 1, 1), + (1000, 999, 999), + (1000, 500, 500), + ], + ) + def test_first_bad_version(self, n: int, bad: int, expected: int): + result = run_first_bad_version(Solution, n, bad) + assert_first_bad_version(result, expected) diff --git a/leetcode/invert_binary_tree/playground.ipynb b/leetcode/invert_binary_tree/playground.ipynb index eb5d963..ba35a6c 100644 --- a/leetcode/invert_binary_tree/playground.ipynb +++ b/leetcode/invert_binary_tree/playground.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "imports", "metadata": {}, "outputs": [], @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "setup", "metadata": {}, "outputs": [], @@ -27,10 +27,113 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "run", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "0\n", + "\n", + "4\n", + "\n", + "\n", + "\n", + "1\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "4\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "0->4\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "2\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "1->2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "3\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "1->3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "5\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "4->5\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "6\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "4->6\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "TreeNode([4, 7, 2, 9, 6, 3, 1])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "result = run_invert_tree(Solution, root_list)\n", "result" @@ -38,10 +141,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "assert", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "assert_invert_tree(result, expected_list)" ] @@ -61,7 +175,8 @@ "file_extension": ".py", "mimetype": "text/x-python", "name": "python", - "nbconvert_exporter": "python3", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", "version": "3.13.7" } }, From 740592b3dc54afe9fa2442a886803c16ca676e56 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 16:42:31 +0700 Subject: [PATCH 16/39] feat: add maximum_profit_in_job_scheduling --- .templates/check_test_cases.py | 45 +++++ .templates/leetcode/json/flood_fill.json | 64 +++++++ .../json/implement_queue_using_stacks.json | 79 ++++++++ .templates/leetcode/json/insert_interval.json | 64 +++++++ .../json/k_closest_points_to_origin.json | 64 +++++++ .../json/kth_smallest_element_in_a_bst.json | 64 +++++++ .../json/largest_rectangle_in_histogram.json | 64 +++++++ .../leetcode/json/linked_list_cycle.json | 67 +++++++ .../leetcode/json/longest_palindrome.json | 64 +++++++ .../json/longest_palindromic_substring.json | 62 ++++++ ...ubstring_without_repeating_characters.json | 67 +++++++ ...mmon_ancestor_of_a_binary_search_tree.json | 65 +++++++ ...west_common_ancestor_of_a_binary_tree.json | 65 +++++++ .../leetcode/json/majority_element.json | 60 ++++++ .../json/maximum_depth_of_binary_tree.json | 62 ++++++ .../maximum_profit_in_job_scheduling.json | 67 +++++++ leetcode/climbing_stairs/solution.py | 4 +- leetcode/flood_fill/README.md | 51 +++++ leetcode/flood_fill/__init__.py | 0 leetcode/flood_fill/helpers.py | 8 + leetcode/flood_fill/playground.ipynb | 71 +++++++ leetcode/flood_fill/solution.py | 18 ++ leetcode/flood_fill/test_solution.py | 27 +++ .../implement_queue_using_stacks/README.md | 54 ++++++ .../implement_queue_using_stacks/__init__.py | 0 .../implement_queue_using_stacks/helpers.py | 22 +++ .../playground.ipynb | 70 +++++++ .../implement_queue_using_stacks/solution.py | 51 +++++ .../test_solution.py | 36 ++++ leetcode/insert_interval/README.md | 41 ++++ leetcode/insert_interval/__init__.py | 0 leetcode/insert_interval/helpers.py | 8 + leetcode/insert_interval/playground.ipynb | 69 +++++++ leetcode/insert_interval/solution.py | 23 +++ leetcode/insert_interval/test_solution.py | 30 +++ leetcode/k_closest_points_to_origin/README.md | 42 +++++ .../k_closest_points_to_origin/__init__.py | 0 .../k_closest_points_to_origin/helpers.py | 11 ++ .../playground.ipynb | 69 +++++++ .../k_closest_points_to_origin/solution.py | 16 ++ .../test_solution.py | 27 +++ .../kth_smallest_element_in_a_bst/README.md | 39 ++++ .../kth_smallest_element_in_a_bst/__init__.py | 0 .../kth_smallest_element_in_a_bst/helpers.py | 12 ++ .../playground.ipynb | 71 +++++++ .../kth_smallest_element_in_a_bst/solution.py | 42 +++++ .../test_solution.py | 27 +++ .../largest_rectangle_in_histogram/README.md | 38 ++++ .../__init__.py | 0 .../largest_rectangle_in_histogram/helpers.py | 8 + .../playground.ipynb | 68 +++++++ .../solution.py | 30 +++ .../test_solution.py | 37 ++++ leetcode/linked_list_cycle/README.md | 58 ++++++ leetcode/linked_list_cycle/__init__.py | 0 leetcode/linked_list_cycle/helpers.py | 32 ++++ leetcode/linked_list_cycle/playground.ipynb | 177 ++++++++++++++++++ leetcode/linked_list_cycle/solution.py | 19 ++ leetcode/linked_list_cycle/test_solution.py | 31 +++ leetcode/longest_palindrome/README.md | 38 ++++ leetcode/longest_palindrome/__init__.py | 0 leetcode/longest_palindrome/helpers.py | 8 + leetcode/longest_palindrome/playground.ipynb | 68 +++++++ leetcode/longest_palindrome/solution.py | 18 ++ leetcode/longest_palindrome/test_solution.py | 34 ++++ .../longest_palindromic_substring/README.md | 34 ++++ .../longest_palindromic_substring/__init__.py | 0 .../longest_palindromic_substring/helpers.py | 8 + .../playground.ipynb | 68 +++++++ .../longest_palindromic_substring/solution.py | 53 ++++++ .../test_solution.py | 33 ++++ .../README.md | 46 +++++ .../__init__.py | 0 .../helpers.py | 8 + .../playground.ipynb | 68 +++++++ .../solution.py | 16 ++ .../test_solution.py | 34 ++++ .../README.md | 52 +++++ .../__init__.py | 0 .../helpers.py | 19 ++ .../playground.ipynb | 72 +++++++ .../solution.py | 21 +++ .../test_solution.py | 29 +++ .../README.md | 50 +++++ .../__init__.py | 0 .../helpers.py | 18 ++ .../playground.ipynb | 72 +++++++ .../solution.py | 25 +++ .../test_solution.py | 29 +++ leetcode/majority_element/README.md | 37 ++++ leetcode/majority_element/__init__.py | 0 leetcode/majority_element/helpers.py | 8 + leetcode/majority_element/playground.ipynb | 68 +++++++ leetcode/majority_element/solution.py | 15 ++ leetcode/majority_element/test_solution.py | 30 +++ .../maximum_depth_of_binary_tree/README.md | 36 ++++ .../maximum_depth_of_binary_tree/__init__.py | 0 .../maximum_depth_of_binary_tree/helpers.py | 12 ++ .../playground.ipynb | 70 +++++++ .../maximum_depth_of_binary_tree/solution.py | 17 ++ .../test_solution.py | 29 +++ .../README.md | 54 ++++++ .../__init__.py | 0 .../helpers.py | 10 + .../playground.ipynb | 70 +++++++ .../solution.py | 22 +++ .../test_solution.py | 27 +++ 107 files changed, 3884 insertions(+), 2 deletions(-) create mode 100644 .templates/check_test_cases.py create mode 100644 .templates/leetcode/json/flood_fill.json create mode 100644 .templates/leetcode/json/implement_queue_using_stacks.json create mode 100644 .templates/leetcode/json/insert_interval.json create mode 100644 .templates/leetcode/json/k_closest_points_to_origin.json create mode 100644 .templates/leetcode/json/kth_smallest_element_in_a_bst.json create mode 100644 .templates/leetcode/json/largest_rectangle_in_histogram.json create mode 100644 .templates/leetcode/json/linked_list_cycle.json create mode 100644 .templates/leetcode/json/longest_palindrome.json create mode 100644 .templates/leetcode/json/longest_palindromic_substring.json create mode 100644 .templates/leetcode/json/longest_substring_without_repeating_characters.json create mode 100644 .templates/leetcode/json/lowest_common_ancestor_of_a_binary_search_tree.json create mode 100644 .templates/leetcode/json/lowest_common_ancestor_of_a_binary_tree.json create mode 100644 .templates/leetcode/json/majority_element.json create mode 100644 .templates/leetcode/json/maximum_depth_of_binary_tree.json create mode 100644 .templates/leetcode/json/maximum_profit_in_job_scheduling.json create mode 100644 leetcode/flood_fill/README.md create mode 100644 leetcode/flood_fill/__init__.py create mode 100644 leetcode/flood_fill/helpers.py create mode 100644 leetcode/flood_fill/playground.ipynb create mode 100644 leetcode/flood_fill/solution.py create mode 100644 leetcode/flood_fill/test_solution.py create mode 100644 leetcode/implement_queue_using_stacks/README.md create mode 100644 leetcode/implement_queue_using_stacks/__init__.py create mode 100644 leetcode/implement_queue_using_stacks/helpers.py create mode 100644 leetcode/implement_queue_using_stacks/playground.ipynb create mode 100644 leetcode/implement_queue_using_stacks/solution.py create mode 100644 leetcode/implement_queue_using_stacks/test_solution.py create mode 100644 leetcode/insert_interval/README.md create mode 100644 leetcode/insert_interval/__init__.py create mode 100644 leetcode/insert_interval/helpers.py create mode 100644 leetcode/insert_interval/playground.ipynb create mode 100644 leetcode/insert_interval/solution.py create mode 100644 leetcode/insert_interval/test_solution.py create mode 100644 leetcode/k_closest_points_to_origin/README.md create mode 100644 leetcode/k_closest_points_to_origin/__init__.py create mode 100644 leetcode/k_closest_points_to_origin/helpers.py create mode 100644 leetcode/k_closest_points_to_origin/playground.ipynb create mode 100644 leetcode/k_closest_points_to_origin/solution.py create mode 100644 leetcode/k_closest_points_to_origin/test_solution.py create mode 100644 leetcode/kth_smallest_element_in_a_bst/README.md create mode 100644 leetcode/kth_smallest_element_in_a_bst/__init__.py create mode 100644 leetcode/kth_smallest_element_in_a_bst/helpers.py create mode 100644 leetcode/kth_smallest_element_in_a_bst/playground.ipynb create mode 100644 leetcode/kth_smallest_element_in_a_bst/solution.py create mode 100644 leetcode/kth_smallest_element_in_a_bst/test_solution.py create mode 100644 leetcode/largest_rectangle_in_histogram/README.md create mode 100644 leetcode/largest_rectangle_in_histogram/__init__.py create mode 100644 leetcode/largest_rectangle_in_histogram/helpers.py create mode 100644 leetcode/largest_rectangle_in_histogram/playground.ipynb create mode 100644 leetcode/largest_rectangle_in_histogram/solution.py create mode 100644 leetcode/largest_rectangle_in_histogram/test_solution.py create mode 100644 leetcode/linked_list_cycle/README.md create mode 100644 leetcode/linked_list_cycle/__init__.py create mode 100644 leetcode/linked_list_cycle/helpers.py create mode 100644 leetcode/linked_list_cycle/playground.ipynb create mode 100644 leetcode/linked_list_cycle/solution.py create mode 100644 leetcode/linked_list_cycle/test_solution.py create mode 100644 leetcode/longest_palindrome/README.md create mode 100644 leetcode/longest_palindrome/__init__.py create mode 100644 leetcode/longest_palindrome/helpers.py create mode 100644 leetcode/longest_palindrome/playground.ipynb create mode 100644 leetcode/longest_palindrome/solution.py create mode 100644 leetcode/longest_palindrome/test_solution.py create mode 100644 leetcode/longest_palindromic_substring/README.md create mode 100644 leetcode/longest_palindromic_substring/__init__.py create mode 100644 leetcode/longest_palindromic_substring/helpers.py create mode 100644 leetcode/longest_palindromic_substring/playground.ipynb create mode 100644 leetcode/longest_palindromic_substring/solution.py create mode 100644 leetcode/longest_palindromic_substring/test_solution.py create mode 100644 leetcode/longest_substring_without_repeating_characters/README.md create mode 100644 leetcode/longest_substring_without_repeating_characters/__init__.py create mode 100644 leetcode/longest_substring_without_repeating_characters/helpers.py create mode 100644 leetcode/longest_substring_without_repeating_characters/playground.ipynb create mode 100644 leetcode/longest_substring_without_repeating_characters/solution.py create mode 100644 leetcode/longest_substring_without_repeating_characters/test_solution.py create mode 100644 leetcode/lowest_common_ancestor_of_a_binary_search_tree/README.md create mode 100644 leetcode/lowest_common_ancestor_of_a_binary_search_tree/__init__.py create mode 100644 leetcode/lowest_common_ancestor_of_a_binary_search_tree/helpers.py create mode 100644 leetcode/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb create mode 100644 leetcode/lowest_common_ancestor_of_a_binary_search_tree/solution.py create mode 100644 leetcode/lowest_common_ancestor_of_a_binary_search_tree/test_solution.py create mode 100644 leetcode/lowest_common_ancestor_of_a_binary_tree/README.md create mode 100644 leetcode/lowest_common_ancestor_of_a_binary_tree/__init__.py create mode 100644 leetcode/lowest_common_ancestor_of_a_binary_tree/helpers.py create mode 100644 leetcode/lowest_common_ancestor_of_a_binary_tree/playground.ipynb create mode 100644 leetcode/lowest_common_ancestor_of_a_binary_tree/solution.py create mode 100644 leetcode/lowest_common_ancestor_of_a_binary_tree/test_solution.py create mode 100644 leetcode/majority_element/README.md create mode 100644 leetcode/majority_element/__init__.py create mode 100644 leetcode/majority_element/helpers.py create mode 100644 leetcode/majority_element/playground.ipynb create mode 100644 leetcode/majority_element/solution.py create mode 100644 leetcode/majority_element/test_solution.py create mode 100644 leetcode/maximum_depth_of_binary_tree/README.md create mode 100644 leetcode/maximum_depth_of_binary_tree/__init__.py create mode 100644 leetcode/maximum_depth_of_binary_tree/helpers.py create mode 100644 leetcode/maximum_depth_of_binary_tree/playground.ipynb create mode 100644 leetcode/maximum_depth_of_binary_tree/solution.py create mode 100644 leetcode/maximum_depth_of_binary_tree/test_solution.py create mode 100644 leetcode/maximum_profit_in_job_scheduling/README.md create mode 100644 leetcode/maximum_profit_in_job_scheduling/__init__.py create mode 100644 leetcode/maximum_profit_in_job_scheduling/helpers.py create mode 100644 leetcode/maximum_profit_in_job_scheduling/playground.ipynb create mode 100644 leetcode/maximum_profit_in_job_scheduling/solution.py create mode 100644 leetcode/maximum_profit_in_job_scheduling/test_solution.py diff --git a/.templates/check_test_cases.py b/.templates/check_test_cases.py new file mode 100644 index 0000000..a0fa441 --- /dev/null +++ b/.templates/check_test_cases.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +import json +from pathlib import Path + +def count_test_cases(json_data): + """Count total test cases across all test methods.""" + total = 0 + + # Handle both direct test_methods and nested _test_methods.list + test_methods = json_data.get("test_methods", []) + if not test_methods and "_test_methods" in json_data: + test_methods = json_data["_test_methods"].get("list", []) + + for method in test_methods: + test_cases = method.get("test_cases", "") + if test_cases.strip(): + # Count tuples/lists in test_cases string + total += max(test_cases.count("("), test_cases.count("[")) + return total + +def main(): + json_dir = Path(".templates/leetcode/json") + files_with_few_tests = [] + + for json_file in json_dir.glob("*.json"): + try: + with open(json_file) as f: + data = json.load(f) + + test_count = count_test_cases(data) + if test_count <= 10: + files_with_few_tests.append((json_file.name, test_count)) + except Exception as e: + print(f"Error reading {json_file.name}: {e}") + + # Sort by test count + files_with_few_tests.sort(key=lambda x: x[1]) + + print(f"Files with ≤10 test cases ({len(files_with_few_tests)} total):") + for filename, count in files_with_few_tests: + print(f"{filename}: {count} test cases") + +if __name__ == "__main__": + main() diff --git a/.templates/leetcode/json/flood_fill.json b/.templates/leetcode/json/flood_fill.json new file mode 100644 index 0000000..4c660d1 --- /dev/null +++ b/.templates/leetcode/json/flood_fill.json @@ -0,0 +1,64 @@ +{ + "problem_name": "flood_fill", + "solution_class_name": "Solution", + "problem_number": "733", + "problem_title": "Flood Fill", + "difficulty": "Easy", + "topics": "Array, Depth-First Search, Breadth-First Search, Matrix", + "_tags": { "list": ["grind-75"] }, + "readme_description": "You are given an image represented by an `m x n` grid of integers `image`, where `image[i][j]` represents the pixel value of the image. You are also given three integers `sr`, `sc`, and `color`. Your task is to perform a **flood fill** on the image starting from the pixel `image[sr][sc]`.\n\nTo perform a **flood fill**:\n\n1. Begin with the starting pixel and change its color to `color`.\n2. Perform the same process for each pixel that is **directly adjacent** (pixels that share a side with the original pixel, either horizontally or vertically) and shares the **same color** as the starting pixel.\n3. Keep **repeating** this process by checking neighboring pixels of the *updated* pixels and modifying their color if it matches the original color of the starting pixel.\n4. The process **stops** when there are **no more** adjacent pixels of the original color to update.\n\nReturn the **modified** image after performing the flood fill.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2021/06/01/flood1-grid.jpg)\n\n```\nInput: image = [[1,1,1],[1,1,0],[1,0,1]], sr = 1, sc = 1, color = 2\nOutput: [[2,2,2],[2,2,0],[2,0,1]]\n```\n**Explanation:** From the center of the image with position `(sr, sc) = (1, 1)` (i.e., the red pixel), all pixels connected by a path of the same color as the starting pixel (i.e., the blue pixels) are colored with the new color. Note the bottom corner is not colored 2, because it is not horizontally or vertically connected to the starting pixel." + }, + { + "content": "```\nInput: image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, color = 0\nOutput: [[0,0,0],[0,0,0]]\n```\n**Explanation:** The starting pixel is already colored with 0, which is the same as the target color. Therefore, no changes are made to the image." + } + ] + }, + "readme_constraints": "- `m == image.length`\n- `n == image[i].length`\n- `1 <= m, n <= 50`\n- `0 <= image[i][j], color < 2^16`\n- `0 <= sr < m`\n- `0 <= sc < n`", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "flood_fill", + "helpers_run_signature": "(solution_class: type, image: list[list[int]], sr: int, sc: int, color: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.flood_fill(image, sr, sc, color)", + "helpers_assert_name": "flood_fill", + "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_flood_fill, run_flood_fill\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "FloodFill", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "flood_fill", + "signature": "(self, image: list[list[int]], sr: int, sc: int, color: int) -> list[list[int]]", + "body": " # TODO: Implement flood_fill\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_flood_fill", + "signature": "(self, image: list[list[int]], sr: int, sc: int, color: int, expected: list[list[int]])", + "parametrize": "image, sr, sc, color, expected", + "test_cases": "[([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 2, [[2, 2, 2], [2, 2, 0], [2, 0, 1]]), ([[0, 0, 0], [0, 0, 0]], 0, 0, 0, [[0, 0, 0], [0, 0, 0]]), ([[0, 0, 0], [0, 1, 1]], 1, 1, 1, [[0, 0, 0], [0, 1, 1]]), ([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 1, [[1, 1, 1], [1, 1, 0], [1, 0, 1]])]", + "body": " result = run_flood_fill(Solution, image, sr, sc, color)\n assert_flood_fill(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_flood_fill, assert_flood_fill\nfrom solution import Solution", + "playground_setup": "# Example test case\nimage = [[1, 1, 1], [1, 1, 0], [1, 0, 1]]\nsr = 1\nsc = 1\ncolor = 2\nexpected = [[2, 2, 2], [2, 2, 0], [2, 0, 1]]", + "playground_run": "result = run_flood_fill(Solution, image, sr, sc, color)\nresult", + "playground_assert": "assert_flood_fill(result, expected)" +} diff --git a/.templates/leetcode/json/implement_queue_using_stacks.json b/.templates/leetcode/json/implement_queue_using_stacks.json new file mode 100644 index 0000000..a412491 --- /dev/null +++ b/.templates/leetcode/json/implement_queue_using_stacks.json @@ -0,0 +1,79 @@ +{ + "problem_name": "implement_queue_using_stacks", + "solution_class_name": "MyQueue", + "problem_number": "232", + "problem_title": "Implement Queue using Stacks", + "difficulty": "Easy", + "topics": "Stack, Design, Queue", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Implement a first in first out (FIFO) queue using only two stacks. The implemented queue should support all the functions of a normal queue (`push`, `peek`, `pop`, and `empty`).\n\nImplement the `MyQueue` class:\n\n- `void push(int x)` Pushes element x to the back of the queue.\n- `int pop()` Removes the element from the front of the queue and returns it.\n- `int peek()` Returns the element at the front of the queue.\n- `boolean empty()` Returns `true` if the queue is empty, `false` otherwise.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput\n[\"MyQueue\", \"push\", \"push\", \"peek\", \"pop\", \"empty\"]\n[[], [1], [2], [], [], []]\nOutput\n[null, null, null, 1, 1, false]\n```\n**Explanation:**\n```\nMyQueue myQueue = new MyQueue();\nmyQueue.push(1); // queue is: [1]\nmyQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)\nmyQueue.peek(); // return 1\nmyQueue.pop(); // return 1, queue is [2]\nmyQueue.empty(); // return false\n```" + } + ] + }, + "readme_constraints": "- 1 <= x <= 9\n- At most 100 calls will be made to push, pop, peek, and empty.\n- All the calls to pop and peek are valid.", + "readme_additional": "**Notes:**\n- You must use **only** standard operations of a stack, which means only `push to top`, `peek/pop from top`, `size`, and `is empty` operations are valid.\n- Depending on your language, the stack may not be supported natively. You may simulate a stack using a list or deque (double-ended queue) as long as you use only a stack's standard operations.\n\n**Follow-up:** Can you implement the queue such that each operation is amortized `O(1)` time complexity? In other words, performing `n` operations will take overall `O(n)` time even if one of those operations may take longer.", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "my_queue", + "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[int]])", + "helpers_run_body": " queue = None\n results: list[int | None | bool] = []\n for i, op in enumerate(operations):\n if op == 'MyQueue':\n queue = solution_class()\n results.append(None)\n elif op == 'push' and queue is not None:\n queue.push(inputs[i][0])\n results.append(None)\n elif op == 'pop' and queue is not None:\n results.append(queue.pop())\n elif op == 'peek' and queue is not None:\n results.append(queue.peek())\n elif op == 'empty' and queue is not None:\n results.append(queue.empty())\n return results, queue", + "helpers_assert_name": "my_queue", + "helpers_assert_signature": "(result: list[int | None | bool], expected: list[int | None | bool]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_my_queue, run_my_queue\nfrom .solution import MyQueue", + "test_content": "", + "test_class_name": "ImplementQueueUsingStacks", + "test_class_content": "", + "_solution_methods": { + "list": [ + { + "name": "__init__", + "signature": "(self) -> None", + "body": " # TODO: Initialize\n pass" + }, + { + "name": "push", + "signature": "(self, x: int) -> None", + "body": " # TODO: Implement push\n pass" + }, + { + "name": "pop", + "signature": "(self) -> int", + "body": " # TODO: Implement pop\n return 0" + }, + { + "name": "peek", + "signature": "(self) -> int", + "body": " # TODO: Implement peek\n return 0" + }, + { + "name": "empty", + "signature": "(self) -> bool", + "body": " # TODO: Implement empty\n return True" + } + ] + }, + "_test_helper_methods": { "list": [] }, + "_test_methods": { + "list": [ + { + "name": "test_queue_operations", + "signature": "(self, operations: list[str], inputs: list[list[int]], expected: list[int | None | bool])", + "parametrize": "operations, inputs, expected", + "test_cases": "[(['MyQueue', 'push', 'push', 'peek', 'pop', 'empty'], [[], [1], [2], [], [], []], [None, None, None, 1, 1, False]), (['MyQueue', 'empty', 'push', 'peek', 'pop', 'empty'], [[], [], [1], [], [], []], [None, True, None, 1, 1, True]), (['MyQueue', 'push', 'push', 'push', 'pop', 'pop', 'peek', 'pop', 'empty'], [[], [1], [2], [3], [], [], [], [], []], [None, None, None, None, 1, 2, 3, 3, True])]", + "body": " result, _ = run_my_queue(MyQueue, operations, inputs)\n assert_my_queue(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_my_queue, assert_my_queue\nfrom solution import MyQueue", + "playground_setup": "# Example test case\noperations = ['MyQueue', 'push', 'push', 'peek', 'pop', 'empty']\ninputs = [[], [1], [2], [], [], []]\nexpected = [None, None, None, 1, 1, False]", + "playground_run": "result, queue = run_my_queue(MyQueue, operations, inputs)\nprint(result)\nqueue", + "playground_assert": "assert_my_queue(result, expected)" +} diff --git a/.templates/leetcode/json/insert_interval.json b/.templates/leetcode/json/insert_interval.json new file mode 100644 index 0000000..64e4a18 --- /dev/null +++ b/.templates/leetcode/json/insert_interval.json @@ -0,0 +1,64 @@ +{ + "problem_name": "insert_interval", + "solution_class_name": "Solution", + "problem_number": "57", + "problem_title": "Insert Interval", + "difficulty": "Medium", + "topics": "Array", + "_tags": { "list": ["grind-75"] }, + "readme_description": "You are given an array of non-overlapping intervals `intervals` where `intervals[i] = [starti, endi]` represent the start and the end of the ith interval and `intervals` is sorted in ascending order by `starti`. You are also given an interval `newInterval = [start, end]` that represents the start and end of another interval.\n\nInsert `newInterval` into `intervals` such that `intervals` is still sorted in ascending order by `starti` and `intervals` still does not have any overlapping intervals (merge overlapping intervals if necessary).\n\nReturn `intervals` after the insertion.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: intervals = [[1,3],[6,9]], newInterval = [2,5]\nOutput: [[1,5],[6,9]]\n```" + }, + { + "content": "```\nInput: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]\nOutput: [[1,2],[3,10],[12,16]]\nExplanation: Because the new interval [4,8] overlaps with [3,5],[6,7],[8,10].\n```" + } + ] + }, + "readme_constraints": "- 0 <= intervals.length <= 10^4\n- intervals[i].length == 2\n- 0 <= starti <= endi <= 10^5\n- intervals is sorted by starti in ascending order\n- newInterval.length == 2\n- 0 <= start <= end <= 10^5", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "insert", + "helpers_run_signature": "(solution_class: type, intervals: list[list[int]], new_interval: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.insert(intervals, new_interval)", + "helpers_assert_name": "insert", + "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_insert, run_insert\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "InsertInterval", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "insert", + "signature": "(self, intervals: list[list[int]], new_interval: list[int]) -> list[list[int]]", + "body": " # TODO: Implement insert\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_insert", + "signature": "(self, intervals: list[list[int]], new_interval: list[int], expected: list[list[int]])", + "parametrize": "intervals, new_interval, expected", + "test_cases": "[([[1,3],[6,9]], [2,5], [[1,5],[6,9]]), ([[1,2],[3,5],[6,7],[8,10],[12,16]], [4,8], [[1,2],[3,10],[12,16]]), ([], [5,7], [[5,7]]), ([[1,5]], [2,3], [[1,5]]), ([[1,5]], [6,8], [[1,5],[6,8]]), ([[1,5]], [0,0], [[0,0],[1,5]]), ([[3,5],[12,15]], [6,6], [[3,5],[6,6],[12,15]])]", + "body": " result = run_insert(Solution, intervals, new_interval)\n assert_insert(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_insert, assert_insert\nfrom solution import Solution", + "playground_setup": "# Example test case\nintervals = [[1,3],[6,9]]\nnew_interval = [2,5]\nexpected = [[1,5],[6,9]]", + "playground_run": "result = run_insert(Solution, intervals, new_interval)\nresult", + "playground_assert": "assert_insert(result, expected)" +} diff --git a/.templates/leetcode/json/k_closest_points_to_origin.json b/.templates/leetcode/json/k_closest_points_to_origin.json new file mode 100644 index 0000000..8a648e5 --- /dev/null +++ b/.templates/leetcode/json/k_closest_points_to_origin.json @@ -0,0 +1,64 @@ +{ + "problem_name": "k_closest_points_to_origin", + "solution_class_name": "Solution", + "problem_number": "973", + "problem_title": "K Closest Points to Origin", + "difficulty": "Medium", + "topics": "Array, Math, Divide and Conquer, Geometry, Sorting, Heap (Priority Queue), Quickselect", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an array of `points` where `points[i] = [xi, yi]` represents a point on the **X-Y** plane and an integer `k`, return the `k` closest points to the origin `(0, 0)`.\n\nThe distance between two points on the **X-Y** plane is the Euclidean distance (i.e., `\u221a(x1 - x2)\u00b2 + (y1 - y2)\u00b2`).\n\nYou may return the answer in **any order**. The answer is **guaranteed** to be **unique** (except for the order that it is in).", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2021/03/03/closestplane1.jpg)\n\n```\nInput: points = [[1,3],[-2,2]], k = 1\nOutput: [[-2,2]]\n```\n**Explanation:** The distance between (1, 3) and the origin is sqrt(10). The distance between (-2, 2) and the origin is sqrt(8). Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin. We only want the closest k = 1 points from the origin, so the answer is just [[-2,2]]." + }, + { + "content": "```\nInput: points = [[3,3],[5,-1],[-2,4]], k = 2\nOutput: [[3,3],[-2,4]]\n```\n**Explanation:** The answer [[-2,4],[3,3]] would also be accepted." + } + ] + }, + "readme_constraints": "- `1 <= k <= points.length <= 10^4`\n- `-10^4 <= xi, yi <= 10^4`", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "k_closest", + "helpers_run_signature": "(solution_class: type, points: list[list[int]], k: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.k_closest(points, k)", + "helpers_assert_name": "k_closest", + "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool", + "helpers_assert_body": " # Sort both result and expected for comparison since order doesn't matter\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.test_utils import logged_test\nfrom .helpers import assert_k_closest, run_k_closest\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "KClosestPointsToOrigin", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "k_closest", + "signature": "(self, points: list[list[int]], k: int) -> list[list[int]]", + "body": " # TODO: Implement k_closest\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_k_closest", + "signature": "(self, points: list[list[int]], k: int, expected: list[list[int]])", + "parametrize": "points, k, expected", + "test_cases": "[([[1, 3], [-2, 2]], 1, [[-2, 2]]), ([[3, 3], [5, -1], [-2, 4]], 2, [[3, 3], [-2, 4]]), ([[0, 1], [1, 0]], 2, [[0, 1], [1, 0]]), ([[1, 1], [1, 1], [1, 1]], 2, [[1, 1], [1, 1]]), ([[0, 0]], 1, [[0, 0]]), ([[1, 0], [2, 0], [3, 0]], 2, [[1, 0], [2, 0]])]", + "body": " result = run_k_closest(Solution, points, k)\n assert_k_closest(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_k_closest, assert_k_closest\nfrom solution import Solution", + "playground_setup": "# Example test case\npoints = [[1, 3], [-2, 2]]\nk = 1\nexpected = [[-2, 2]]", + "playground_run": "result = run_k_closest(Solution, points, k)\nresult", + "playground_assert": "assert_k_closest(result, expected)" +} diff --git a/.templates/leetcode/json/kth_smallest_element_in_a_bst.json b/.templates/leetcode/json/kth_smallest_element_in_a_bst.json new file mode 100644 index 0000000..e41818c --- /dev/null +++ b/.templates/leetcode/json/kth_smallest_element_in_a_bst.json @@ -0,0 +1,64 @@ +{ + "problem_name": "kth_smallest_element_in_a_bst", + "solution_class_name": "Solution", + "problem_number": "230", + "problem_title": "Kth Smallest Element in a BST", + "difficulty": "Medium", + "topics": "Tree, Depth-First Search, Binary Search Tree, Binary Tree", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given the `root` of a binary search tree, and an integer `k`, return the `k`th smallest value (1-indexed) of all the values of the nodes in the tree.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2021/01/28/kthtree1.jpg)\n\n```\nInput: root = [3,1,4,null,2], k = 1\nOutput: 1\n```" + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2021/01/28/kthtree2.jpg)\n\n```\nInput: root = [5,3,6,2,4,null,null,1], k = 3\nOutput: 3\n```" + } + ] + }, + "readme_constraints": "- The number of nodes in the tree is `n`.\n- `1 <= k <= n <= 10^4`\n- `0 <= Node.val <= 10^4`", + "readme_additional": "**Follow up:** If the BST is modified often (i.e., we can do insert and delete operations) and you need to find the kth smallest frequently, how would you optimize?", + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "", + "helpers_run_name": "kth_smallest", + "helpers_run_signature": "(solution_class: type, root_list: list[int | None], k: int)", + "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n implementation = solution_class()\n return implementation.kth_smallest(root, k)", + "helpers_assert_name": "kth_smallest", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "from leetcode_py import TreeNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_kth_smallest, run_kth_smallest\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "KthSmallestElementInABst", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "kth_smallest", + "signature": "(self, root: TreeNode[int] | None, k: int) -> int", + "body": " # TODO: Implement kth_smallest\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_kth_smallest", + "signature": "(self, root_list: list[int | None], k: int, expected: int)", + "parametrize": "root_list, k, expected", + "test_cases": "[([3, 1, 4, None, 2], 1, 1), ([5, 3, 6, 2, 4, None, None, 1], 3, 3), ([1], 1, 1), ([2, 1, 3], 2, 2), ([4, 2, 6, 1, 3, 5, 7], 4, 4), ([1, None, 2], 2, 2)]", + "body": " result = run_kth_smallest(Solution, root_list, k)\n assert_kth_smallest(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_kth_smallest, assert_kth_smallest\nfrom solution import Solution\nfrom leetcode_py import TreeNode", + "playground_setup": "# Example test case\nroot_list = [3, 1, 4, None, 2]\nk = 1\nexpected = 1", + "playground_run": "result = run_kth_smallest(Solution, root_list, k)\nresult", + "playground_assert": "assert_kth_smallest(result, expected)" +} diff --git a/.templates/leetcode/json/largest_rectangle_in_histogram.json b/.templates/leetcode/json/largest_rectangle_in_histogram.json new file mode 100644 index 0000000..2fe5780 --- /dev/null +++ b/.templates/leetcode/json/largest_rectangle_in_histogram.json @@ -0,0 +1,64 @@ +{ + "problem_name": "largest_rectangle_in_histogram", + "solution_class_name": "Solution", + "problem_number": "84", + "problem_title": "Largest Rectangle in Histogram", + "difficulty": "Hard", + "topics": "Array, Stack, Monotonic Stack", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an array of integers `heights` representing the histogram's bar height where the width of each bar is `1`, return the area of the largest rectangle in the histogram.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2021/01/04/histogram.jpg)\n\n```\nInput: heights = [2,1,5,6,2,3]\nOutput: 10\n```\n**Explanation:** The above is a histogram where width of each bar is 1. The largest rectangle is shown in the red area, which has an area = 10 units." + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2021/01/04/histogram-1.jpg)\n\n```\nInput: heights = [2,4]\nOutput: 4\n```" + } + ] + }, + "readme_constraints": "- `1 <= heights.length <= 10^5`\n- `0 <= heights[i] <= 10^4`", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "largest_rectangle_area", + "helpers_run_signature": "(solution_class: type, heights: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.largest_rectangle_area(heights)", + "helpers_assert_name": "largest_rectangle_area", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_largest_rectangle_area, run_largest_rectangle_area\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "LargestRectangleInHistogram", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "largest_rectangle_area", + "signature": "(self, heights: list[int]) -> int", + "body": " # TODO: Implement largest_rectangle_area\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_largest_rectangle_area", + "signature": "(self, heights: list[int], expected: int)", + "parametrize": "heights, expected", + "test_cases": "[([2, 1, 5, 6, 2, 3], 10), ([2, 4], 4), ([1], 1), ([0], 0), ([1, 1], 2), ([0, 0, 0], 0), ([1, 2, 3, 4, 5], 9), ([5, 4, 3, 2, 1], 9), ([3, 3, 3, 3], 12), ([2, 1, 2], 3), ([1, 3, 1], 3), ([6, 7, 5, 2, 4, 5, 9, 3], 16), ([4, 2, 0, 3, 2, 5], 6), ([1, 2, 2, 1], 4), ([0, 9], 9), ([9, 0], 9)]", + "body": " result = run_largest_rectangle_area(Solution, heights)\n assert_largest_rectangle_area(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_largest_rectangle_area, assert_largest_rectangle_area\nfrom solution import Solution", + "playground_setup": "# Example test case\nheights = [2, 1, 5, 6, 2, 3]\nexpected = 10", + "playground_run": "result = run_largest_rectangle_area(Solution, heights)\nresult", + "playground_assert": "assert_largest_rectangle_area(result, expected)" +} diff --git a/.templates/leetcode/json/linked_list_cycle.json b/.templates/leetcode/json/linked_list_cycle.json new file mode 100644 index 0000000..eeb89c9 --- /dev/null +++ b/.templates/leetcode/json/linked_list_cycle.json @@ -0,0 +1,67 @@ +{ + "problem_name": "linked_list_cycle", + "solution_class_name": "Solution", + "problem_number": "141", + "problem_title": "Linked List Cycle", + "difficulty": "Easy", + "topics": "Hash Table, Linked List, Two Pointers", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given `head`, the head of a linked list, determine if the linked list has a cycle in it.\n\nThere is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the `next` pointer. Internally, `pos` is used to denote the index of the node that tail's `next` pointer is connected to. **Note that `pos` is not passed as a parameter**.\n\nReturn `true` *if there is a cycle in the linked list*. Otherwise, return `false`.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist.png)\n\n```\nInput: head = [3,2,0,-4], pos = 1\nOutput: true\n```\n**Explanation:** There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed)." + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test2.png)\n\n```\nInput: head = [1,2], pos = 0\nOutput: true\n```\n**Explanation:** There is a cycle in the linked list, where the tail connects to the 0th node." + }, + { + "content": "![Example 3](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test3.png)\n\n```\nInput: head = [1], pos = -1\nOutput: false\n```\n**Explanation:** There is no cycle in the linked list." + } + ] + }, + "readme_constraints": "- The number of the nodes in the list is in the range `[0, 10^4]`.\n- `-10^5 <= Node.val <= 10^5`\n- `pos` is `-1` or a **valid index** in the linked-list.", + "readme_additional": "**Follow up:** Can you solve it using `O(1)` (i.e. constant) memory?", + "helpers_imports": "from leetcode_py import ListNode", + "helpers_content": "def create_cycle_list(values: list[int], pos: int) -> ListNode[int] | None:\n if not values:\n return None\n\n nodes = []\n head = ListNode(values[0])\n nodes.append(head)\n current = head\n\n for i in range(1, len(values)):\n current.next = ListNode(values[i])\n current = current.next\n nodes.append(current)\n\n if pos != -1 and pos < len(nodes):\n current.next = nodes[pos]\n\n return head", + "helpers_run_name": "has_cycle", + "helpers_run_signature": "(solution_class: type, values: list[int], pos: int)", + "helpers_run_body": " head = create_cycle_list(values, pos)\n implementation = solution_class()\n return implementation.has_cycle(head)", + "helpers_assert_name": "has_cycle", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "from leetcode_py import ListNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_has_cycle, run_has_cycle\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "LinkedListCycle", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "has_cycle", + "signature": "(self, head: ListNode[int] | None) -> bool", + "body": " # TODO: Implement has_cycle\n return False" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_has_cycle", + "signature": "(self, values: list[int], pos: int, expected: bool)", + "parametrize": "values, pos, expected", + "test_cases": "[([3, 2, 0, -4], 1, True), ([1, 2], 0, True), ([1], -1, False), ([], -1, False), ([1, 2, 3], -1, False), ([1, 2, 3, 4, 5], 0, True), ([1, 2, 3, 4, 5], 2, True), ([1, 2, 3, 4, 5], 4, True), ([1], 0, True), ([1, 2], 1, True)]", + "body": " result = run_has_cycle(Solution, values, pos)\n assert_has_cycle(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_has_cycle, assert_has_cycle, create_cycle_list\nfrom solution import Solution", + "playground_setup": "# Example test case\nvalues = [3, 2, 0, -4]\npos = 1\nexpected = True", + "playground_run": "result = run_has_cycle(Solution, values, pos)\nresult", + "playground_assert": "assert_has_cycle(result, expected)" +} diff --git a/.templates/leetcode/json/longest_palindrome.json b/.templates/leetcode/json/longest_palindrome.json new file mode 100644 index 0000000..8208c5d --- /dev/null +++ b/.templates/leetcode/json/longest_palindrome.json @@ -0,0 +1,64 @@ +{ + "problem_name": "longest_palindrome", + "solution_class_name": "Solution", + "problem_number": "409", + "problem_title": "Longest Palindrome", + "difficulty": "Easy", + "topics": "Hash Table, String, Greedy", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given a string `s` which consists of lowercase or uppercase letters, return the length of the longest palindrome that can be built with those letters.\n\nLetters are case sensitive, for example, \"Aa\" is not considered a palindrome.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: s = \"abccccdd\"\nOutput: 7\n```\n**Explanation:** One longest palindrome that can be built is \"dccaccd\", whose length is 7." + }, + { + "content": "```\nInput: s = \"a\"\nOutput: 1\n```\n**Explanation:** The longest palindrome that can be built is \"a\", whose length is 1." + } + ] + }, + "readme_constraints": "- `1 <= s.length <= 2000`\n- `s` consists of lowercase and/or uppercase English letters only.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "longest_palindrome", + "helpers_run_signature": "(solution_class: type, s: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.longest_palindrome(s)", + "helpers_assert_name": "longest_palindrome", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_longest_palindrome, run_longest_palindrome\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "LongestPalindrome", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "longest_palindrome", + "signature": "(self, s: str) -> int", + "body": " # TODO: Implement longest_palindrome\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_longest_palindrome", + "signature": "(self, s: str, expected: int)", + "parametrize": "s, expected", + "test_cases": "[('abccccdd', 7), ('a', 1), ('Aa', 1), ('aabbcc', 6), ('', 0), ('aA', 1), ('abcdef', 1), ('aabbccdd', 8), ('aaaa', 4), ('abcdefg', 1), ('AAaa', 4), ('racecar', 7), ('abcABC', 1)]", + "body": " result = run_longest_palindrome(Solution, s)\n assert_longest_palindrome(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_longest_palindrome, assert_longest_palindrome\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = 'abccccdd'\nexpected = 7", + "playground_run": "result = run_longest_palindrome(Solution, s)\nresult", + "playground_assert": "assert_longest_palindrome(result, expected)" +} diff --git a/.templates/leetcode/json/longest_palindromic_substring.json b/.templates/leetcode/json/longest_palindromic_substring.json new file mode 100644 index 0000000..de0e3fc --- /dev/null +++ b/.templates/leetcode/json/longest_palindromic_substring.json @@ -0,0 +1,62 @@ +{ + "problem_name": "longest_palindromic_substring", + "solution_class_name": "Solution", + "problem_number": "5", + "problem_title": "Longest Palindromic Substring", + "difficulty": "Medium", + "topics": "Two Pointers, String, Dynamic Programming", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given a string `s`, return the longest palindromic substring in `s`.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: s = \"babad\"\nOutput: \"bab\"\n```\n**Explanation:** \"aba\" is also a valid answer." + }, + { "content": "```\nInput: s = \"cbbd\"\nOutput: \"bb\"\n```" } + ] + }, + "readme_constraints": "- `1 <= s.length <= 1000`\n- `s` consist of only digits and English letters.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "longest_palindrome", + "helpers_run_signature": "(solution_class: type, s: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.longest_palindrome(s)", + "helpers_assert_name": "longest_palindrome", + "helpers_assert_signature": "(result: str, expected: set[str]) -> bool", + "helpers_assert_body": " assert result in expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_longest_palindrome, run_longest_palindrome\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "LongestPalindromicSubstring", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "longest_palindrome", + "signature": "(self, s: str) -> str", + "body": " # TODO: Implement longest_palindrome\n return \"\"" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_longest_palindrome", + "signature": "(self, s: str, expected: set[str])", + "parametrize": "s, expected", + "test_cases": "[('babad', {'bab', 'aba'}), ('cbbd', {'bb'}), ('a', {'a'}), ('ac', {'a', 'c'}), ('racecar', {'racecar'}), ('aabbaa', {'aabbaa'}), ('abacabad', {'abacaba'}), ('noon', {'noon'}), ('abccba', {'abccba'}), ('aa', {'aa'}), ('aba', {'aba'}), ('abcba', {'abcba'})]", + "body": " result = run_longest_palindrome(Solution, s)\n assert_longest_palindrome(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_longest_palindrome, assert_longest_palindrome\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = 'babad'\nexpected = {'bab', 'aba'}", + "playground_run": "result = run_longest_palindrome(Solution, s)\nresult", + "playground_assert": "assert_longest_palindrome(result, expected)" +} diff --git a/.templates/leetcode/json/longest_substring_without_repeating_characters.json b/.templates/leetcode/json/longest_substring_without_repeating_characters.json new file mode 100644 index 0000000..50c04c2 --- /dev/null +++ b/.templates/leetcode/json/longest_substring_without_repeating_characters.json @@ -0,0 +1,67 @@ +{ + "problem_name": "longest_substring_without_repeating_characters", + "solution_class_name": "Solution", + "problem_number": "3", + "problem_title": "Longest Substring Without Repeating Characters", + "difficulty": "Medium", + "topics": "Hash Table, String, Sliding Window", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given a string `s`, find the length of the **longest** **substring** without duplicate characters.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: s = \"abcabcbb\"\nOutput: 3\n```\n**Explanation:** The answer is \"abc\", with the length of 3." + }, + { + "content": "```\nInput: s = \"bbbbb\"\nOutput: 1\n```\n**Explanation:** The answer is \"b\", with the length of 1." + }, + { + "content": "```\nInput: s = \"pwwkew\"\nOutput: 3\n```\n**Explanation:** The answer is \"wke\", with the length of 3.\nNotice that the answer must be a substring, \"pwke\" is a subsequence and not a substring." + } + ] + }, + "readme_constraints": "- 0 <= s.length <= 5 * 10^4\n- s consists of English letters, digits, symbols and spaces.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "length_of_longest_substring", + "helpers_run_signature": "(solution_class: type, s: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.length_of_longest_substring(s)", + "helpers_assert_name": "length_of_longest_substring", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_length_of_longest_substring, run_length_of_longest_substring\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "LongestSubstringWithoutRepeatingCharacters", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "length_of_longest_substring", + "signature": "(self, s: str) -> int", + "body": " # TODO: Implement length_of_longest_substring\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_length_of_longest_substring", + "signature": "(self, s: str, expected: int)", + "parametrize": "s, expected", + "test_cases": "[('abcabcbb', 3), ('bbbbb', 1), ('pwwkew', 3), ('', 0), ('a', 1), ('au', 2), ('dvdf', 3), ('abcdef', 6), ('aab', 2), ('tmmzuxt', 5), (' ', 1), (' ', 1), ('abba', 2)]", + "body": " result = run_length_of_longest_substring(Solution, s)\n assert_length_of_longest_substring(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_length_of_longest_substring, assert_length_of_longest_substring\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = 'abcabcbb'\nexpected = 3", + "playground_run": "result = run_length_of_longest_substring(Solution, s)\nresult", + "playground_assert": "assert_length_of_longest_substring(result, expected)" +} diff --git a/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_search_tree.json b/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_search_tree.json new file mode 100644 index 0000000..fab73c4 --- /dev/null +++ b/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_search_tree.json @@ -0,0 +1,65 @@ +{ + "problem_name": "lowest_common_ancestor_of_a_binary_search_tree", + "solution_class_name": "Solution", + "problem_number": "235", + "problem_title": "Lowest Common Ancestor of a Binary Search Tree", + "difficulty": "Medium", + "topics": "Tree, Depth-First Search, Binary Search Tree, Binary Tree", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given a binary search tree (BST), find the lowest common ancestor (LCA) node of two given nodes in the BST.\n\nAccording to the definition of LCA on Wikipedia: \"The lowest common ancestor is defined between two nodes `p` and `q` as the lowest node in `T` that has both `p` and `q` as descendants (where we allow **a node to be a descendant of itself**).\"", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2018/12/14/binarysearchtree_improved.png)\n\n```\nInput: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8\nOutput: 6\n```\n**Explanation:** The LCA of nodes 2 and 8 is 6." + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2018/12/14/binarysearchtree_improved.png)\n\n```\nInput: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4\nOutput: 2\n```\n**Explanation:** The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition." + }, + { "content": "```\nInput: root = [2,1], p = 2, q = 1\nOutput: 2\n```" } + ] + }, + "readme_constraints": "- The number of nodes in the tree is in the range `[2, 10^5]`.\n- `-10^9 <= Node.val <= 10^9`\n- All `Node.val` are **unique**.\n- `p != q`\n- `p` and `q` will exist in the BST.", + "readme_additional": "", + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "", + "helpers_run_name": "lowest_common_ancestor", + "helpers_run_signature": "(solution_class: type, root_list: list[int | None], p_val: int, q_val: int)", + "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n assert root is not None\n p = root.find_node(p_val)\n q = root.find_node(q_val)\n assert p is not None and q is not None\n implementation = solution_class()\n return implementation.lowest_common_ancestor(root, p, q)", + "helpers_assert_name": "lowest_common_ancestor", + "helpers_assert_signature": "(result: TreeNode[int] | None, expected_val: int) -> bool", + "helpers_assert_body": " assert result is not None\n assert result.val == expected_val\n return True", + "solution_imports": "from leetcode_py import TreeNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "LowestCommonAncestorOfABinarySearchTree", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "lowest_common_ancestor", + "signature": "(self, root: TreeNode[int] | None, p: TreeNode[int], q: TreeNode[int]) -> TreeNode[int] | None", + "body": " # TODO: Implement lowest_common_ancestor\n return None" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_lowest_common_ancestor", + "signature": "(self, root_list: list[int | None], p_val: int, q_val: int, expected_val: int)", + "parametrize": "root_list, p_val, q_val, expected_val", + "test_cases": "[([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 8, 6), ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 4, 2), ([2, 1], 2, 1, 2), ([2, 1], 1, 2, 2), ([6, 2, 8, 0, 4, 7, 9], 0, 4, 2), ([6, 2, 8, 0, 4, 7, 9], 7, 9, 8)]", + "body": " result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)\n assert_lowest_common_ancestor(result, expected_val)" + } + ] + }, + "playground_imports": "from helpers import run_lowest_common_ancestor, assert_lowest_common_ancestor\nfrom solution import Solution\nfrom leetcode_py import TreeNode", + "playground_setup": "# Example test case\nroot_list = [6, 2, 8, 0, 4, 7, 9, None, None, 3, 5]\np_val = 2\nq_val = 8\nexpected_val = 6", + "playground_run": "result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)\nresult.val if result else None", + "playground_assert": "assert_lowest_common_ancestor(result, expected_val)" +} diff --git a/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_tree.json b/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_tree.json new file mode 100644 index 0000000..c1f4b0e --- /dev/null +++ b/.templates/leetcode/json/lowest_common_ancestor_of_a_binary_tree.json @@ -0,0 +1,65 @@ +{ + "problem_name": "lowest_common_ancestor_of_a_binary_tree", + "solution_class_name": "Solution", + "problem_number": "236", + "problem_title": "Lowest Common Ancestor of a Binary Tree", + "difficulty": "Medium", + "topics": "Tree, Depth-First Search, Binary Tree", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree.\n\nAccording to the definition of LCA on Wikipedia: \"The lowest common ancestor is defined between two nodes `p` and `q` as the lowest node in `T` that has both `p` and `q` as descendants (where we allow **a node to be a descendant of itself**).\"", + "_readme_examples": { + "list": [ + { + "content": "\"\"\n\n```\nInput: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1\nOutput: 3\nExplanation: The LCA of nodes 5 and 1 is 3.\n```" + }, + { + "content": "\"\"\n\n```\nInput: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4\nOutput: 5\nExplanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.\n```" + }, + { "content": "```\nInput: root = [1,2], p = 1, q = 2\nOutput: 1\n```" } + ] + }, + "readme_constraints": "- The number of nodes in the tree is in the range [2, 10^5].\n- -10^9 <= Node.val <= 10^9\n- All Node.val are unique.\n- p != q\n- p and q will exist in the tree.", + "readme_additional": "", + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "", + "helpers_run_name": "lowest_common_ancestor", + "helpers_run_signature": "(solution_class: type, root_list: list[int | None], p_val: int, q_val: int)", + "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n assert root is not None\n p = root.find_node(p_val)\n q = root.find_node(q_val)\n assert p is not None and q is not None\n implementation = solution_class()\n return implementation.lowest_common_ancestor(root, p, q)", + "helpers_assert_name": "lowest_common_ancestor", + "helpers_assert_signature": "(result: TreeNode[int], expected_val: int) -> bool", + "helpers_assert_body": " assert result.val == expected_val\n return True", + "solution_imports": "from leetcode_py import TreeNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "LowestCommonAncestorOfABinaryTree", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "lowest_common_ancestor", + "signature": "(self, root: TreeNode[int], p: TreeNode[int], q: TreeNode[int]) -> TreeNode[int]", + "body": " # TODO: Implement lowest_common_ancestor\n return root" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_lowest_common_ancestor", + "signature": "(self, root_list: list[int | None], p_val: int, q_val: int, expected_val: int)", + "parametrize": "root_list, p_val, q_val, expected_val", + "test_cases": "[([3,5,1,6,2,0,8,None,None,7,4], 5, 1, 3), ([3,5,1,6,2,0,8,None,None,7,4], 5, 4, 5), ([1,2], 1, 2, 1), ([2,1], 2, 1, 2), ([3,5,1,6,2,0,8,None,None,7,4], 6, 7, 5), ([3,5,1,6,2,0,8,None,None,7,4], 0, 8, 1)]", + "body": " result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)\n assert_lowest_common_ancestor(result, expected_val)" + } + ] + }, + "playground_imports": "from helpers import run_lowest_common_ancestor, assert_lowest_common_ancestor\nfrom solution import Solution\nfrom leetcode_py import TreeNode", + "playground_setup": "# Example test case\nroot_list = [3,5,1,6,2,0,8,None,None,7,4]\np_val = 5\nq_val = 1\nexpected_val = 3", + "playground_run": "result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)\nresult.val", + "playground_assert": "assert_lowest_common_ancestor(result, expected_val)" +} diff --git a/.templates/leetcode/json/majority_element.json b/.templates/leetcode/json/majority_element.json new file mode 100644 index 0000000..1fc620a --- /dev/null +++ b/.templates/leetcode/json/majority_element.json @@ -0,0 +1,60 @@ +{ + "problem_name": "majority_element", + "solution_class_name": "Solution", + "problem_number": "169", + "problem_title": "Majority Element", + "difficulty": "Easy", + "topics": "Array, Hash Table, Divide and Conquer, Sorting, Counting", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an array `nums` of size `n`, return the majority element.\n\nThe majority element is the element that appears more than `\u230an / 2\u230b` times. You may assume that the majority element always exists in the array.", + "_readme_examples": { + "list": [ + { "content": "```\nInput: nums = [3,2,3]\nOutput: 3\n```" }, + { "content": "```\nInput: nums = [2,2,1,1,1,2,2]\nOutput: 2\n```" } + ] + }, + "readme_constraints": "- n == nums.length\n- 1 <= n <= 5 * 10^4\n- -10^9 <= nums[i] <= 10^9", + "readme_additional": "**Follow-up:** Could you solve the problem in linear time and in O(1) space?", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "majority_element", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.majority_element(nums)", + "helpers_assert_name": "majority_element", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_majority_element, run_majority_element\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MajorityElement", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "majority_element", + "signature": "(self, nums: list[int]) -> int", + "body": " # TODO: Implement majority_element\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_majority_element", + "signature": "(self, nums: list[int], expected: int)", + "parametrize": "nums, expected", + "test_cases": "[([3,2,3], 3), ([2,2,1,1,1,2,2], 2), ([1], 1), ([1,1,2], 1), ([2,2,2,1,1], 2), ([5,5,5,5,1,2,3], 5), ([1,2,3,4,4,4,4], 4), ([0,0,0], 0), ([-1,-1,-1,1,1], -1)]", + "body": " result = run_majority_element(Solution, nums)\n assert_majority_element(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_majority_element, assert_majority_element\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [3,2,3]\nexpected = 3", + "playground_run": "result = run_majority_element(Solution, nums)\nresult", + "playground_assert": "assert_majority_element(result, expected)" +} diff --git a/.templates/leetcode/json/maximum_depth_of_binary_tree.json b/.templates/leetcode/json/maximum_depth_of_binary_tree.json new file mode 100644 index 0000000..fed5f39 --- /dev/null +++ b/.templates/leetcode/json/maximum_depth_of_binary_tree.json @@ -0,0 +1,62 @@ +{ + "problem_name": "maximum_depth_of_binary_tree", + "solution_class_name": "Solution", + "problem_number": "104", + "problem_title": "Maximum Depth of Binary Tree", + "difficulty": "Easy", + "topics": "Tree, Depth-First Search, Breadth-First Search, Binary Tree", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given the `root` of a binary tree, return *its maximum depth*.\n\nA binary tree's **maximum depth** is the number of nodes along the longest path from the root node down to the farthest leaf node.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2020/11/26/tmp-tree.jpg)\n\n```\nInput: root = [3,9,20,null,null,15,7]\nOutput: 3\n```" + }, + { "content": "```\nInput: root = [1,null,2]\nOutput: 2\n```" } + ] + }, + "readme_constraints": "- The number of nodes in the tree is in the range `[0, 10^4]`.\n- `-100 <= Node.val <= 100`", + "readme_additional": "", + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "", + "helpers_run_name": "max_depth", + "helpers_run_signature": "(solution_class: type, root_list: list[int | None])", + "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n implementation = solution_class()\n return implementation.max_depth(root)", + "helpers_assert_name": "max_depth", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "from leetcode_py import TreeNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_max_depth, run_max_depth\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MaximumDepthOfBinaryTree", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "max_depth", + "signature": "(self, root: TreeNode[int] | None) -> int", + "body": " # TODO: Implement max_depth\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_max_depth", + "signature": "(self, root_list: list[int | None], expected: int)", + "parametrize": "root_list, expected", + "test_cases": "[([3, 9, 20, None, None, 15, 7], 3), ([1, None, 2], 2), ([], 0), ([1], 1), ([1, 2], 2), ([1, 2, 3], 2), ([1, 2, 3, 4], 3), ([1, None, 2, None, 3], 3)]", + "body": " result = run_max_depth(Solution, root_list)\n assert_max_depth(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_max_depth, assert_max_depth\nfrom solution import Solution\nfrom leetcode_py import TreeNode", + "playground_setup": "# Example test case\nroot_list = [3, 9, 20, None, None, 15, 7]\nexpected = 3", + "playground_run": "result = run_max_depth(Solution, root_list)\nresult", + "playground_assert": "assert_max_depth(result, expected)" +} diff --git a/.templates/leetcode/json/maximum_profit_in_job_scheduling.json b/.templates/leetcode/json/maximum_profit_in_job_scheduling.json new file mode 100644 index 0000000..c765195 --- /dev/null +++ b/.templates/leetcode/json/maximum_profit_in_job_scheduling.json @@ -0,0 +1,67 @@ +{ + "problem_name": "maximum_profit_in_job_scheduling", + "solution_class_name": "Solution", + "problem_number": "1235", + "problem_title": "Maximum Profit in Job Scheduling", + "difficulty": "Hard", + "topics": "Array, Binary Search, Dynamic Programming, Sorting", + "_tags": { "list": ["grind-75"] }, + "readme_description": "We have `n` jobs, where every job is scheduled to be done from `startTime[i]` to `endTime[i]`, obtaining a profit of `profit[i]`.\n\nYou're given the `startTime`, `endTime` and `profit` arrays, return the maximum profit you can take such that there are no two jobs in the subset with overlapping time range.\n\nIf you choose a job that ends at time `X` you will be able to start another job that starts at time `X`.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2019/10/10/sample1_1584.png)\n\n```\nInput: startTime = [1,2,3,3], endTime = [3,4,5,6], profit = [50,10,40,70]\nOutput: 120\n```\n**Explanation:** The subset chosen is the first and fourth job. Time range [1-3]+[3-6] , we get profit of 120 = 50 + 70." + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2019/10/10/sample22_1584.png)\n\n```\nInput: startTime = [1,2,3,4,6], endTime = [3,5,10,6,9], profit = [20,20,100,70,60]\nOutput: 150\n```\n**Explanation:** The subset chosen is the first, fourth and fifth job. Profit obtained 150 = 20 + 70 + 60." + }, + { + "content": "![Example 3](https://assets.leetcode.com/uploads/2019/10/10/sample3_1584.png)\n\n```\nInput: startTime = [1,1,1], endTime = [2,3,4], profit = [5,6,4]\nOutput: 6\n```" + } + ] + }, + "readme_constraints": "- `1 <= startTime.length == endTime.length == profit.length <= 5 * 10^4`\n- `1 <= startTime[i] < endTime[i] <= 10^9`\n- `1 <= profit[i] <= 10^4`", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "job_scheduling", + "helpers_run_signature": "(solution_class: type, start_time: list[int], end_time: list[int], profit: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.job_scheduling(start_time, end_time, profit)", + "helpers_assert_name": "job_scheduling", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_job_scheduling, run_job_scheduling\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MaximumProfitInJobScheduling", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "job_scheduling", + "signature": "(self, start_time: list[int], end_time: list[int], profit: list[int]) -> int", + "body": " # TODO: Implement job_scheduling\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_job_scheduling", + "signature": "(self, start_time: list[int], end_time: list[int], profit: list[int], expected: int)", + "parametrize": "start_time, end_time, profit, expected", + "test_cases": "[([1, 2, 3, 3], [3, 4, 5, 6], [50, 10, 40, 70], 120), ([1, 2, 3, 4, 6], [3, 5, 10, 6, 9], [20, 20, 100, 70, 60], 150), ([1, 1, 1], [2, 3, 4], [5, 6, 4], 6), ([1], [2], [100], 100)]", + "body": " result = run_job_scheduling(Solution, start_time, end_time, profit)\n assert_job_scheduling(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_job_scheduling, assert_job_scheduling\nfrom solution import Solution", + "playground_setup": "# Example test case\nstart_time = [1, 2, 3, 3]\nend_time = [3, 4, 5, 6]\nprofit = [50, 10, 40, 70]\nexpected = 120", + "playground_run": "result = run_job_scheduling(Solution, start_time, end_time, profit)\nresult", + "playground_assert": "assert_job_scheduling(result, expected)" +} diff --git a/leetcode/climbing_stairs/solution.py b/leetcode/climbing_stairs/solution.py index 7b7f6a7..2e990a4 100644 --- a/leetcode/climbing_stairs/solution.py +++ b/leetcode/climbing_stairs/solution.py @@ -1,8 +1,8 @@ class Solution: + # Time: O(n) + # Space: O(1) def climb_stairs(self, n: int) -> int: - # Time: O(n) - # Space: O(1) # This follows Fibonacci pattern # Standard Fib: F(0)=0, F(1)=1, F(2)=1, F(3)=2, F(4)=3, F(5)=5... diff --git a/leetcode/flood_fill/README.md b/leetcode/flood_fill/README.md new file mode 100644 index 0000000..24f808f --- /dev/null +++ b/leetcode/flood_fill/README.md @@ -0,0 +1,51 @@ +# Flood Fill + +**Difficulty:** Easy +**Topics:** Array, Depth-First Search, Breadth-First Search, Matrix +**Tags:** grind-75 + +**LeetCode:** [Problem 733](https://leetcode.com/problems/flood-fill/description/) + +## Problem Description + +You are given an image represented by an `m x n` grid of integers `image`, where `image[i][j]` represents the pixel value of the image. You are also given three integers `sr`, `sc`, and `color`. Your task is to perform a **flood fill** on the image starting from the pixel `image[sr][sc]`. + +To perform a **flood fill**: + +1. Begin with the starting pixel and change its color to `color`. +2. Perform the same process for each pixel that is **directly adjacent** (pixels that share a side with the original pixel, either horizontally or vertically) and shares the **same color** as the starting pixel. +3. Keep **repeating** this process by checking neighboring pixels of the _updated_ pixels and modifying their color if it matches the original color of the starting pixel. +4. The process **stops** when there are **no more** adjacent pixels of the original color to update. + +Return the **modified** image after performing the flood fill. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2021/06/01/flood1-grid.jpg) + +``` +Input: image = [[1,1,1],[1,1,0],[1,0,1]], sr = 1, sc = 1, color = 2 +Output: [[2,2,2],[2,2,0],[2,0,1]] +``` + +**Explanation:** From the center of the image with position `(sr, sc) = (1, 1)` (i.e., the red pixel), all pixels connected by a path of the same color as the starting pixel (i.e., the blue pixels) are colored with the new color. Note the bottom corner is not colored 2, because it is not horizontally or vertically connected to the starting pixel. + +### Example 2: + +``` +Input: image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, color = 0 +Output: [[0,0,0],[0,0,0]] +``` + +**Explanation:** The starting pixel is already colored with 0, which is the same as the target color. Therefore, no changes are made to the image. + +## Constraints + +- `m == image.length` +- `n == image[i].length` +- `1 <= m, n <= 50` +- `0 <= image[i][j], color < 2^16` +- `0 <= sr < m` +- `0 <= sc < n` diff --git a/leetcode/flood_fill/__init__.py b/leetcode/flood_fill/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/flood_fill/helpers.py b/leetcode/flood_fill/helpers.py new file mode 100644 index 0000000..5aa521d --- /dev/null +++ b/leetcode/flood_fill/helpers.py @@ -0,0 +1,8 @@ +def run_flood_fill(solution_class: type, image: list[list[int]], sr: int, sc: int, color: int): + implementation = solution_class() + return implementation.flood_fill(image, sr, sc, color) + + +def assert_flood_fill(result: list[list[int]], expected: list[list[int]]) -> bool: + assert result == expected + return True diff --git a/leetcode/flood_fill/playground.ipynb b/leetcode/flood_fill/playground.ipynb new file mode 100644 index 0000000..583f7a1 --- /dev/null +++ b/leetcode/flood_fill/playground.ipynb @@ -0,0 +1,71 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_flood_fill, run_flood_fill\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "image = [[1, 1, 1], [1, 1, 0], [1, 0, 1]]\n", + "sr = 1\n", + "sc = 1\n", + "color = 2\n", + "expected = [[2, 2, 2], [2, 2, 0], [2, 0, 1]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_flood_fill(Solution, image, sr, sc, color)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_flood_fill(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/flood_fill/solution.py b/leetcode/flood_fill/solution.py new file mode 100644 index 0000000..ea6885b --- /dev/null +++ b/leetcode/flood_fill/solution.py @@ -0,0 +1,18 @@ +class Solution: + + # Time: O(m*n) + # Space: O(m*n) + def flood_fill(self, image: list[list[int]], sr: int, sc: int, color: int) -> list[list[int]]: + original = image[sr][sc] + if original == color: + return image + + def dfs(r: int, c: int) -> None: + if r < 0 or r >= len(image) or c < 0 or c >= len(image[0]) or image[r][c] != original: + return + image[r][c] = color + for dr, dc in [(1, 0), (-1, 0), (0, 1), (0, -1)]: + dfs(r + dr, c + dc) + + dfs(sr, sc) + return image diff --git a/leetcode/flood_fill/test_solution.py b/leetcode/flood_fill/test_solution.py new file mode 100644 index 0000000..ee4a476 --- /dev/null +++ b/leetcode/flood_fill/test_solution.py @@ -0,0 +1,27 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_flood_fill, run_flood_fill +from .solution import Solution + + +class TestFloodFill: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "image, sr, sc, color, expected", + [ + ([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 2, [[2, 2, 2], [2, 2, 0], [2, 0, 1]]), + ([[0, 0, 0], [0, 0, 0]], 0, 0, 0, [[0, 0, 0], [0, 0, 0]]), + ([[0, 0, 0], [0, 1, 1]], 1, 1, 1, [[0, 0, 0], [0, 1, 1]]), + ([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 1, [[1, 1, 1], [1, 1, 0], [1, 0, 1]]), + ], + ) + def test_flood_fill( + self, image: list[list[int]], sr: int, sc: int, color: int, expected: list[list[int]] + ): + result = run_flood_fill(Solution, image, sr, sc, color) + assert_flood_fill(result, expected) diff --git a/leetcode/implement_queue_using_stacks/README.md b/leetcode/implement_queue_using_stacks/README.md new file mode 100644 index 0000000..a1e659f --- /dev/null +++ b/leetcode/implement_queue_using_stacks/README.md @@ -0,0 +1,54 @@ +# Implement Queue using Stacks + +**Difficulty:** Easy +**Topics:** Stack, Design, Queue +**Tags:** grind-75 + +**LeetCode:** [Problem 232](https://leetcode.com/problems/implement-queue-using-stacks/description/) + +## Problem Description + +Implement a first in first out (FIFO) queue using only two stacks. The implemented queue should support all the functions of a normal queue (`push`, `peek`, `pop`, and `empty`). + +Implement the `MyQueue` class: + +- `void push(int x)` Pushes element x to the back of the queue. +- `int pop()` Removes the element from the front of the queue and returns it. +- `int peek()` Returns the element at the front of the queue. +- `boolean empty()` Returns `true` if the queue is empty, `false` otherwise. + +## Examples + +### Example 1: + +``` +Input +["MyQueue", "push", "push", "peek", "pop", "empty"] +[[], [1], [2], [], [], []] +Output +[null, null, null, 1, 1, false] +``` + +**Explanation:** + +``` +MyQueue myQueue = new MyQueue(); +myQueue.push(1); // queue is: [1] +myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) +myQueue.peek(); // return 1 +myQueue.pop(); // return 1, queue is [2] +myQueue.empty(); // return false +``` + +## Constraints + +- 1 <= x <= 9 +- At most 100 calls will be made to push, pop, peek, and empty. +- All the calls to pop and peek are valid. + +**Notes:** + +- You must use **only** standard operations of a stack, which means only `push to top`, `peek/pop from top`, `size`, and `is empty` operations are valid. +- Depending on your language, the stack may not be supported natively. You may simulate a stack using a list or deque (double-ended queue) as long as you use only a stack's standard operations. + +**Follow-up:** Can you implement the queue such that each operation is amortized `O(1)` time complexity? In other words, performing `n` operations will take overall `O(n)` time even if one of those operations may take longer. diff --git a/leetcode/implement_queue_using_stacks/__init__.py b/leetcode/implement_queue_using_stacks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/implement_queue_using_stacks/helpers.py b/leetcode/implement_queue_using_stacks/helpers.py new file mode 100644 index 0000000..504d419 --- /dev/null +++ b/leetcode/implement_queue_using_stacks/helpers.py @@ -0,0 +1,22 @@ +def run_my_queue(solution_class: type, operations: list[str], inputs: list[list[int]]): + queue = None + results: list[int | None | bool] = [] + for i, op in enumerate(operations): + if op == "MyQueue": + queue = solution_class() + results.append(None) + elif op == "push" and queue is not None: + queue.push(inputs[i][0]) + results.append(None) + elif op == "pop" and queue is not None: + results.append(queue.pop()) + elif op == "peek" and queue is not None: + results.append(queue.peek()) + elif op == "empty" and queue is not None: + results.append(queue.empty()) + return results, queue + + +def assert_my_queue(result: list[int | None | bool], expected: list[int | None | bool]) -> bool: + assert result == expected + return True diff --git a/leetcode/implement_queue_using_stacks/playground.ipynb b/leetcode/implement_queue_using_stacks/playground.ipynb new file mode 100644 index 0000000..735a590 --- /dev/null +++ b/leetcode/implement_queue_using_stacks/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_my_queue, run_my_queue\n", + "from solution import MyQueue" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "operations = [\"MyQueue\", \"push\", \"push\", \"peek\", \"pop\", \"empty\"]\n", + "inputs = [[], [1], [2], [], [], []]\n", + "expected = [None, None, None, 1, 1, False]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result, queue = run_my_queue(MyQueue, operations, inputs)\n", + "print(result)\n", + "queue" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_my_queue(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/implement_queue_using_stacks/solution.py b/leetcode/implement_queue_using_stacks/solution.py new file mode 100644 index 0000000..71581ee --- /dev/null +++ b/leetcode/implement_queue_using_stacks/solution.py @@ -0,0 +1,51 @@ +class MyQueue: + # Time: O(1) + # Space: O(n) + def __init__(self) -> None: + self.input_stack: list[int] = [] + self.output_stack: list[int] = [] + + # Time: O(1) + # Space: O(1) + def push(self, x: int) -> None: + self.input_stack.append(x) + + # Time: O(1) amortized + # Space: O(1) + def pop(self) -> int: + self._move_to_output() + return self.output_stack.pop() + + # Time: O(1) amortized + # Space: O(1) + def peek(self) -> int: + self._move_to_output() + return self.output_stack[-1] + + # Time: O(1) + # Space: O(1) + def empty(self) -> bool: + return not self.input_stack and not self.output_stack + + def _move_to_output(self) -> None: + if not self.output_stack: + while self.input_stack: + self.output_stack.append(self.input_stack.pop()) + + +# Amortized O(1) Explanation: +# Example with 4 push + 4 pop operations: +# +# push(1) # input: [1], output: [] - O(1) +# push(2) # input: [1,2], output: [] - O(1) +# push(3) # input: [1,2,3], output: [] - O(1) +# push(4) # input: [1,2,3,4], output: [] - O(1) +# +# pop() # Move all 4 to output: input: [], output: [4,3,2,1] then pop 1 - O(4) +# pop() # output: [4,3,2], just pop 2 - O(1) +# pop() # output: [4,3], just pop 3 - O(1) +# pop() # output: [4], just pop 4 - O(1) +# +# Total cost: 4 + 4 + 1 + 1 + 1 = 11 operations for 8 calls = 1.4 per operation +# Key: Each element moves exactly once from input to output, so expensive O(n) +# transfer is "spread out" over multiple cheap O(1) operations = amortized O(1) diff --git a/leetcode/implement_queue_using_stacks/test_solution.py b/leetcode/implement_queue_using_stacks/test_solution.py new file mode 100644 index 0000000..3363ac1 --- /dev/null +++ b/leetcode/implement_queue_using_stacks/test_solution.py @@ -0,0 +1,36 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_my_queue, run_my_queue +from .solution import MyQueue + + +class TestImplementQueueUsingStacks: + + @logged_test + @pytest.mark.parametrize( + "operations, inputs, expected", + [ + ( + ["MyQueue", "push", "push", "peek", "pop", "empty"], + [[], [1], [2], [], [], []], + [None, None, None, 1, 1, False], + ), + ( + ["MyQueue", "empty", "push", "peek", "pop", "empty"], + [[], [], [1], [], [], []], + [None, True, None, 1, 1, True], + ), + ( + ["MyQueue", "push", "push", "push", "pop", "pop", "peek", "pop", "empty"], + [[], [1], [2], [3], [], [], [], [], []], + [None, None, None, None, 1, 2, 3, 3, True], + ), + ], + ) + def test_queue_operations( + self, operations: list[str], inputs: list[list[int]], expected: list[int | None | bool] + ): + result, _ = run_my_queue(MyQueue, operations, inputs) + assert_my_queue(result, expected) diff --git a/leetcode/insert_interval/README.md b/leetcode/insert_interval/README.md new file mode 100644 index 0000000..aff876d --- /dev/null +++ b/leetcode/insert_interval/README.md @@ -0,0 +1,41 @@ +# Insert Interval + +**Difficulty:** Medium +**Topics:** Array +**Tags:** grind-75 + +**LeetCode:** [Problem 57](https://leetcode.com/problems/insert-interval/description/) + +## Problem Description + +You are given an array of non-overlapping intervals `intervals` where `intervals[i] = [starti, endi]` represent the start and the end of the ith interval and `intervals` is sorted in ascending order by `starti`. You are also given an interval `newInterval = [start, end]` that represents the start and end of another interval. + +Insert `newInterval` into `intervals` such that `intervals` is still sorted in ascending order by `starti` and `intervals` still does not have any overlapping intervals (merge overlapping intervals if necessary). + +Return `intervals` after the insertion. + +## Examples + +### Example 1: + +``` +Input: intervals = [[1,3],[6,9]], newInterval = [2,5] +Output: [[1,5],[6,9]] +``` + +### Example 2: + +``` +Input: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8] +Output: [[1,2],[3,10],[12,16]] +Explanation: Because the new interval [4,8] overlaps with [3,5],[6,7],[8,10]. +``` + +## Constraints + +- 0 <= intervals.length <= 10^4 +- intervals[i].length == 2 +- 0 <= starti <= endi <= 10^5 +- intervals is sorted by starti in ascending order +- newInterval.length == 2 +- 0 <= start <= end <= 10^5 diff --git a/leetcode/insert_interval/__init__.py b/leetcode/insert_interval/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/insert_interval/helpers.py b/leetcode/insert_interval/helpers.py new file mode 100644 index 0000000..19995b9 --- /dev/null +++ b/leetcode/insert_interval/helpers.py @@ -0,0 +1,8 @@ +def run_insert(solution_class: type, intervals: list[list[int]], new_interval: list[int]): + implementation = solution_class() + return implementation.insert(intervals, new_interval) + + +def assert_insert(result: list[list[int]], expected: list[list[int]]) -> bool: + assert result == expected + return True diff --git a/leetcode/insert_interval/playground.ipynb b/leetcode/insert_interval/playground.ipynb new file mode 100644 index 0000000..0e8d78f --- /dev/null +++ b/leetcode/insert_interval/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_insert, run_insert\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "intervals = [[1, 3], [6, 9]]\n", + "new_interval = [2, 5]\n", + "expected = [[1, 5], [6, 9]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_insert(Solution, intervals, new_interval)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_insert(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/insert_interval/solution.py b/leetcode/insert_interval/solution.py new file mode 100644 index 0000000..7a0b224 --- /dev/null +++ b/leetcode/insert_interval/solution.py @@ -0,0 +1,23 @@ +class Solution: + + # Time: O(n) + # Space: O(n) + def insert(self, intervals: list[list[int]], new_interval: list[int]) -> list[list[int]]: + result = [] + i = 0 + + # Add intervals before new_interval + while i < len(intervals) and intervals[i][1] < new_interval[0]: + result.append(intervals[i]) + i += 1 + + # Merge overlapping intervals + while i < len(intervals) and intervals[i][0] <= new_interval[1]: + new_interval[0] = min(new_interval[0], intervals[i][0]) + new_interval[1] = max(new_interval[1], intervals[i][1]) + i += 1 + result.append(new_interval) + + # Add remaining intervals + result.extend(intervals[i:]) + return result diff --git a/leetcode/insert_interval/test_solution.py b/leetcode/insert_interval/test_solution.py new file mode 100644 index 0000000..7e83e00 --- /dev/null +++ b/leetcode/insert_interval/test_solution.py @@ -0,0 +1,30 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_insert, run_insert +from .solution import Solution + + +class TestInsertInterval: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "intervals, new_interval, expected", + [ + ([[1, 3], [6, 9]], [2, 5], [[1, 5], [6, 9]]), + ([[1, 2], [3, 5], [6, 7], [8, 10], [12, 16]], [4, 8], [[1, 2], [3, 10], [12, 16]]), + ([], [5, 7], [[5, 7]]), + ([[1, 5]], [2, 3], [[1, 5]]), + ([[1, 5]], [6, 8], [[1, 5], [6, 8]]), + ([[1, 5]], [0, 0], [[0, 0], [1, 5]]), + ([[3, 5], [12, 15]], [6, 6], [[3, 5], [6, 6], [12, 15]]), + ], + ) + def test_insert( + self, intervals: list[list[int]], new_interval: list[int], expected: list[list[int]] + ): + result = run_insert(Solution, intervals, new_interval) + assert_insert(result, expected) diff --git a/leetcode/k_closest_points_to_origin/README.md b/leetcode/k_closest_points_to_origin/README.md new file mode 100644 index 0000000..54395ba --- /dev/null +++ b/leetcode/k_closest_points_to_origin/README.md @@ -0,0 +1,42 @@ +# K Closest Points to Origin + +**Difficulty:** Medium +**Topics:** Array, Math, Divide and Conquer, Geometry, Sorting, Heap (Priority Queue), Quickselect +**Tags:** grind-75 + +**LeetCode:** [Problem 973](https://leetcode.com/problems/k-closest-points-to-origin/description/) + +## Problem Description + +Given an array of `points` where `points[i] = [xi, yi]` represents a point on the **X-Y** plane and an integer `k`, return the `k` closest points to the origin `(0, 0)`. + +The distance between two points on the **X-Y** plane is the Euclidean distance (i.e., `√(x1 - x2)² + (y1 - y2)²`). + +You may return the answer in **any order**. The answer is **guaranteed** to be **unique** (except for the order that it is in). + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2021/03/03/closestplane1.jpg) + +``` +Input: points = [[1,3],[-2,2]], k = 1 +Output: [[-2,2]] +``` + +**Explanation:** The distance between (1, 3) and the origin is sqrt(10). The distance between (-2, 2) and the origin is sqrt(8). Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin. We only want the closest k = 1 points from the origin, so the answer is just [[-2,2]]. + +### Example 2: + +``` +Input: points = [[3,3],[5,-1],[-2,4]], k = 2 +Output: [[3,3],[-2,4]] +``` + +**Explanation:** The answer [[-2,4],[3,3]] would also be accepted. + +## Constraints + +- `1 <= k <= points.length <= 10^4` +- `-10^4 <= xi, yi <= 10^4` diff --git a/leetcode/k_closest_points_to_origin/__init__.py b/leetcode/k_closest_points_to_origin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/k_closest_points_to_origin/helpers.py b/leetcode/k_closest_points_to_origin/helpers.py new file mode 100644 index 0000000..9935f6b --- /dev/null +++ b/leetcode/k_closest_points_to_origin/helpers.py @@ -0,0 +1,11 @@ +def run_k_closest(solution_class: type, points: list[list[int]], k: int): + implementation = solution_class() + return implementation.k_closest(points, k) + + +def assert_k_closest(result: list[list[int]], expected: list[list[int]]) -> bool: + # Sort both result and expected for comparison since order doesn't matter + result_sorted = sorted(result) + expected_sorted = sorted(expected) + assert result_sorted == expected_sorted + return True diff --git a/leetcode/k_closest_points_to_origin/playground.ipynb b/leetcode/k_closest_points_to_origin/playground.ipynb new file mode 100644 index 0000000..fce66ec --- /dev/null +++ b/leetcode/k_closest_points_to_origin/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_k_closest, run_k_closest\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "points = [[1, 3], [-2, 2]]\n", + "k = 1\n", + "expected = [[-2, 2]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_k_closest(Solution, points, k)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_k_closest(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/k_closest_points_to_origin/solution.py b/leetcode/k_closest_points_to_origin/solution.py new file mode 100644 index 0000000..d053d9b --- /dev/null +++ b/leetcode/k_closest_points_to_origin/solution.py @@ -0,0 +1,16 @@ +import heapq + + +class Solution: + # Time: O(n log k) + # Space: O(k) + def k_closest(self, points: list[list[int]], k: int) -> list[list[int]]: + heap: list[tuple[int, list[int]]] = [] + + for x, y in points: + dist = x * x + y * y + heapq.heappush(heap, (-dist, [x, y])) + if len(heap) > k: + heapq.heappop(heap) + + return [point for _, point in heap] diff --git a/leetcode/k_closest_points_to_origin/test_solution.py b/leetcode/k_closest_points_to_origin/test_solution.py new file mode 100644 index 0000000..3ef3ad3 --- /dev/null +++ b/leetcode/k_closest_points_to_origin/test_solution.py @@ -0,0 +1,27 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_k_closest, run_k_closest +from .solution import Solution + + +class TestKClosestPointsToOrigin: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "points, k, expected", + [ + ([[1, 3], [-2, 2]], 1, [[-2, 2]]), + ([[3, 3], [5, -1], [-2, 4]], 2, [[3, 3], [-2, 4]]), + ([[0, 1], [1, 0]], 2, [[0, 1], [1, 0]]), + ([[1, 1], [1, 1], [1, 1]], 2, [[1, 1], [1, 1]]), + ([[0, 0]], 1, [[0, 0]]), + ([[1, 0], [2, 0], [3, 0]], 2, [[1, 0], [2, 0]]), + ], + ) + def test_k_closest(self, points: list[list[int]], k: int, expected: list[list[int]]): + result = run_k_closest(Solution, points, k) + assert_k_closest(result, expected) diff --git a/leetcode/kth_smallest_element_in_a_bst/README.md b/leetcode/kth_smallest_element_in_a_bst/README.md new file mode 100644 index 0000000..fefde64 --- /dev/null +++ b/leetcode/kth_smallest_element_in_a_bst/README.md @@ -0,0 +1,39 @@ +# Kth Smallest Element in a BST + +**Difficulty:** Medium +**Topics:** Tree, Depth-First Search, Binary Search Tree, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 230](https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/) + +## Problem Description + +Given the `root` of a binary search tree, and an integer `k`, return the `k`th smallest value (1-indexed) of all the values of the nodes in the tree. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2021/01/28/kthtree1.jpg) + +``` +Input: root = [3,1,4,null,2], k = 1 +Output: 1 +``` + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2021/01/28/kthtree2.jpg) + +``` +Input: root = [5,3,6,2,4,null,null,1], k = 3 +Output: 3 +``` + +## Constraints + +- The number of nodes in the tree is `n`. +- `1 <= k <= n <= 10^4` +- `0 <= Node.val <= 10^4` + +**Follow up:** If the BST is modified often (i.e., we can do insert and delete operations) and you need to find the kth smallest frequently, how would you optimize? diff --git a/leetcode/kth_smallest_element_in_a_bst/__init__.py b/leetcode/kth_smallest_element_in_a_bst/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/kth_smallest_element_in_a_bst/helpers.py b/leetcode/kth_smallest_element_in_a_bst/helpers.py new file mode 100644 index 0000000..9ba9875 --- /dev/null +++ b/leetcode/kth_smallest_element_in_a_bst/helpers.py @@ -0,0 +1,12 @@ +from leetcode_py import TreeNode + + +def run_kth_smallest(solution_class: type, root_list: list[int | None], k: int): + root = TreeNode[int].from_list(root_list) + implementation = solution_class() + return implementation.kth_smallest(root, k) + + +def assert_kth_smallest(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/kth_smallest_element_in_a_bst/playground.ipynb b/leetcode/kth_smallest_element_in_a_bst/playground.ipynb new file mode 100644 index 0000000..b65acaf --- /dev/null +++ b/leetcode/kth_smallest_element_in_a_bst/playground.ipynb @@ -0,0 +1,71 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_kth_smallest, run_kth_smallest\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import TreeNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root_list = [3, 1, 4, None, 2]\n", + "k = 1\n", + "expected = 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_kth_smallest(Solution, root_list, k)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_kth_smallest(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/kth_smallest_element_in_a_bst/solution.py b/leetcode/kth_smallest_element_in_a_bst/solution.py new file mode 100644 index 0000000..94c91d1 --- /dev/null +++ b/leetcode/kth_smallest_element_in_a_bst/solution.py @@ -0,0 +1,42 @@ +from leetcode_py import TreeNode + + +class Solution: + + # Inorder Recursive + # Time: O(k) + # Space: O(h) + def kth_smallest(self, root: TreeNode[int] | None, k: int) -> int: + def inorder(node: TreeNode[int] | None): + if not node: + return + yield from inorder(node.left) + yield node.val + yield from inorder(node.right) + + for i, val in enumerate(inorder(root)): + if i == k - 1: + return val + + raise ValueError(f"Tree has fewer than {k} nodes") + + +# Binary Tree Traversal Patterns +# +# def inorder(node): +# if node: +# inorder(node.left) +# print(node.val) +# inorder(node.right) +# +# def preorder(node): +# if node: +# print(node.val) +# preorder(node.left) +# preorder(node.right) +# +# def postorder(node): +# if node: +# postorder(node.left) +# postorder(node.right) +# print(node.val) diff --git a/leetcode/kth_smallest_element_in_a_bst/test_solution.py b/leetcode/kth_smallest_element_in_a_bst/test_solution.py new file mode 100644 index 0000000..9f1e4d8 --- /dev/null +++ b/leetcode/kth_smallest_element_in_a_bst/test_solution.py @@ -0,0 +1,27 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_kth_smallest, run_kth_smallest +from .solution import Solution + + +class TestKthSmallestElementInABst: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "root_list, k, expected", + [ + ([3, 1, 4, None, 2], 1, 1), + ([5, 3, 6, 2, 4, None, None, 1], 3, 3), + ([1], 1, 1), + ([2, 1, 3], 2, 2), + ([4, 2, 6, 1, 3, 5, 7], 4, 4), + ([1, None, 2], 2, 2), + ], + ) + def test_kth_smallest(self, root_list: list[int | None], k: int, expected: int): + result = run_kth_smallest(Solution, root_list, k) + assert_kth_smallest(result, expected) diff --git a/leetcode/largest_rectangle_in_histogram/README.md b/leetcode/largest_rectangle_in_histogram/README.md new file mode 100644 index 0000000..ac89d7c --- /dev/null +++ b/leetcode/largest_rectangle_in_histogram/README.md @@ -0,0 +1,38 @@ +# Largest Rectangle in Histogram + +**Difficulty:** Hard +**Topics:** Array, Stack, Monotonic Stack +**Tags:** grind-75 + +**LeetCode:** [Problem 84](https://leetcode.com/problems/largest-rectangle-in-histogram/description/) + +## Problem Description + +Given an array of integers `heights` representing the histogram's bar height where the width of each bar is `1`, return the area of the largest rectangle in the histogram. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2021/01/04/histogram.jpg) + +``` +Input: heights = [2,1,5,6,2,3] +Output: 10 +``` + +**Explanation:** The above is a histogram where width of each bar is 1. The largest rectangle is shown in the red area, which has an area = 10 units. + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2021/01/04/histogram-1.jpg) + +``` +Input: heights = [2,4] +Output: 4 +``` + +## Constraints + +- `1 <= heights.length <= 10^5` +- `0 <= heights[i] <= 10^4` diff --git a/leetcode/largest_rectangle_in_histogram/__init__.py b/leetcode/largest_rectangle_in_histogram/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/largest_rectangle_in_histogram/helpers.py b/leetcode/largest_rectangle_in_histogram/helpers.py new file mode 100644 index 0000000..ea578c6 --- /dev/null +++ b/leetcode/largest_rectangle_in_histogram/helpers.py @@ -0,0 +1,8 @@ +def run_largest_rectangle_area(solution_class: type, heights: list[int]): + implementation = solution_class() + return implementation.largest_rectangle_area(heights) + + +def assert_largest_rectangle_area(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/largest_rectangle_in_histogram/playground.ipynb b/leetcode/largest_rectangle_in_histogram/playground.ipynb new file mode 100644 index 0000000..8470983 --- /dev/null +++ b/leetcode/largest_rectangle_in_histogram/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_largest_rectangle_area, run_largest_rectangle_area\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "heights = [2, 1, 5, 6, 2, 3]\n", + "expected = 10" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_largest_rectangle_area(Solution, heights)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_largest_rectangle_area(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/largest_rectangle_in_histogram/solution.py b/leetcode/largest_rectangle_in_histogram/solution.py new file mode 100644 index 0000000..325e627 --- /dev/null +++ b/leetcode/largest_rectangle_in_histogram/solution.py @@ -0,0 +1,30 @@ +class Solution: + + # Time: O(n) + # Space: O(n) + # Monotonic stack approach + # Stack stores indices of bars in increasing height order + # When we find a shorter bar, we calculate area using previous bars + def largest_rectangle_area(self, heights: list[int]) -> int: + + stack: list[int] = [] # Stack of indices + max_area = 0 + + for i, height in enumerate(heights): + # While current height is less than stack top height + # Pop from stack and calculate area with popped height as smallest + while stack and heights[stack[-1]] > height: + max_area = max(max_area, self.calculate_area(heights, stack, i)) + + stack.append(i) + + while stack: + max_area = max(max_area, self.calculate_area(heights, stack, len(heights))) + + return max_area + + @staticmethod + def calculate_area(heights: list[int], stack: list[int], right_bound: int) -> int: + h = heights[stack.pop()] + w = right_bound if not stack else right_bound - stack[-1] - 1 + return h * w diff --git a/leetcode/largest_rectangle_in_histogram/test_solution.py b/leetcode/largest_rectangle_in_histogram/test_solution.py new file mode 100644 index 0000000..2ac5ee6 --- /dev/null +++ b/leetcode/largest_rectangle_in_histogram/test_solution.py @@ -0,0 +1,37 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_largest_rectangle_area, run_largest_rectangle_area +from .solution import Solution + + +class TestLargestRectangleInHistogram: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "heights, expected", + [ + ([2, 1, 5, 6, 2, 3], 10), + ([2, 4], 4), + ([1], 1), + ([0], 0), + ([1, 1], 2), + ([0, 0, 0], 0), + ([1, 2, 3, 4, 5], 9), + ([5, 4, 3, 2, 1], 9), + ([3, 3, 3, 3], 12), + ([2, 1, 2], 3), + ([1, 3, 1], 3), + ([6, 7, 5, 2, 4, 5, 9, 3], 16), + ([4, 2, 0, 3, 2, 5], 6), + ([1, 2, 2, 1], 4), + ([0, 9], 9), + ([9, 0], 9), + ], + ) + def test_largest_rectangle_area(self, heights: list[int], expected: int): + result = run_largest_rectangle_area(Solution, heights) + assert_largest_rectangle_area(result, expected) diff --git a/leetcode/linked_list_cycle/README.md b/leetcode/linked_list_cycle/README.md new file mode 100644 index 0000000..4ec75c4 --- /dev/null +++ b/leetcode/linked_list_cycle/README.md @@ -0,0 +1,58 @@ +# Linked List Cycle + +**Difficulty:** Easy +**Topics:** Hash Table, Linked List, Two Pointers +**Tags:** grind-75 + +**LeetCode:** [Problem 141](https://leetcode.com/problems/linked-list-cycle/description/) + +## Problem Description + +Given `head`, the head of a linked list, determine if the linked list has a cycle in it. + +There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the `next` pointer. Internally, `pos` is used to denote the index of the node that tail's `next` pointer is connected to. **Note that `pos` is not passed as a parameter**. + +Return `true` _if there is a cycle in the linked list_. Otherwise, return `false`. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist.png) + +``` +Input: head = [3,2,0,-4], pos = 1 +Output: true +``` + +**Explanation:** There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed). + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test2.png) + +``` +Input: head = [1,2], pos = 0 +Output: true +``` + +**Explanation:** There is a cycle in the linked list, where the tail connects to the 0th node. + +### Example 3: + +![Example 3](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test3.png) + +``` +Input: head = [1], pos = -1 +Output: false +``` + +**Explanation:** There is no cycle in the linked list. + +## Constraints + +- The number of the nodes in the list is in the range `[0, 10^4]`. +- `-10^5 <= Node.val <= 10^5` +- `pos` is `-1` or a **valid index** in the linked-list. + +**Follow up:** Can you solve it using `O(1)` (i.e. constant) memory? diff --git a/leetcode/linked_list_cycle/__init__.py b/leetcode/linked_list_cycle/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/linked_list_cycle/helpers.py b/leetcode/linked_list_cycle/helpers.py new file mode 100644 index 0000000..69a770a --- /dev/null +++ b/leetcode/linked_list_cycle/helpers.py @@ -0,0 +1,32 @@ +from leetcode_py import ListNode + + +def create_cycle_list(values: list[int], pos: int) -> ListNode[int] | None: + if not values: + return None + + nodes = [] + head = ListNode(values[0]) + nodes.append(head) + current = head + + for i in range(1, len(values)): + current.next = ListNode(values[i]) + current = current.next + nodes.append(current) + + if pos != -1 and pos < len(nodes): + current.next = nodes[pos] + + return head + + +def run_has_cycle(solution_class: type, values: list[int], pos: int): + head = create_cycle_list(values, pos) + implementation = solution_class() + return implementation.has_cycle(head) + + +def assert_has_cycle(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/linked_list_cycle/playground.ipynb b/leetcode/linked_list_cycle/playground.ipynb new file mode 100644 index 0000000..f4adc95 --- /dev/null +++ b/leetcode/linked_list_cycle/playground.ipynb @@ -0,0 +1,177 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_has_cycle, create_cycle_list, run_has_cycle\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "values = [3, 2, 0, -4]\n", + "pos = 1\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "66f97343", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_0\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "node_1\n", + "\n", + "2\n", + "\n", + "\n", + "\n", + "node_0->node_1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_2\n", + "\n", + "0\n", + "\n", + "\n", + "\n", + "node_1->node_2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_3\n", + "\n", + "-4\n", + "\n", + "\n", + "\n", + "node_2->node_3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "node_3->node_1\n", + "\n", + "\n", + "cycle\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "ListNode([3, 2, 0, -4])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "head = create_cycle_list(values, pos)\n", + "head" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "run", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_has_cycle(Solution, values, pos)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "assert", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_has_cycle(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/linked_list_cycle/solution.py b/leetcode/linked_list_cycle/solution.py new file mode 100644 index 0000000..4bc0686 --- /dev/null +++ b/leetcode/linked_list_cycle/solution.py @@ -0,0 +1,19 @@ +from leetcode_py import ListNode + + +class Solution: + + # Time: O(n) + # Space: O(1) + def has_cycle(self, head: ListNode[int] | None) -> bool: + fast = head + slow = head + + while fast and fast.next: + assert slow is not None + fast = fast.next.next + slow = slow.next + if fast is slow: + return True + + return False diff --git a/leetcode/linked_list_cycle/test_solution.py b/leetcode/linked_list_cycle/test_solution.py new file mode 100644 index 0000000..2660c0e --- /dev/null +++ b/leetcode/linked_list_cycle/test_solution.py @@ -0,0 +1,31 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_has_cycle, run_has_cycle +from .solution import Solution + + +class TestLinkedListCycle: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "values, pos, expected", + [ + ([3, 2, 0, -4], 1, True), + ([1, 2], 0, True), + ([1], -1, False), + ([], -1, False), + ([1, 2, 3], -1, False), + ([1, 2, 3, 4, 5], 0, True), + ([1, 2, 3, 4, 5], 2, True), + ([1, 2, 3, 4, 5], 4, True), + ([1], 0, True), + ([1, 2], 1, True), + ], + ) + def test_has_cycle(self, values: list[int], pos: int, expected: bool): + result = run_has_cycle(Solution, values, pos) + assert_has_cycle(result, expected) diff --git a/leetcode/longest_palindrome/README.md b/leetcode/longest_palindrome/README.md new file mode 100644 index 0000000..327f719 --- /dev/null +++ b/leetcode/longest_palindrome/README.md @@ -0,0 +1,38 @@ +# Longest Palindrome + +**Difficulty:** Easy +**Topics:** Hash Table, String, Greedy +**Tags:** grind-75 + +**LeetCode:** [Problem 409](https://leetcode.com/problems/longest-palindrome/description/) + +## Problem Description + +Given a string `s` which consists of lowercase or uppercase letters, return the length of the longest palindrome that can be built with those letters. + +Letters are case sensitive, for example, "Aa" is not considered a palindrome. + +## Examples + +### Example 1: + +``` +Input: s = "abccccdd" +Output: 7 +``` + +**Explanation:** One longest palindrome that can be built is "dccaccd", whose length is 7. + +### Example 2: + +``` +Input: s = "a" +Output: 1 +``` + +**Explanation:** The longest palindrome that can be built is "a", whose length is 1. + +## Constraints + +- `1 <= s.length <= 2000` +- `s` consists of lowercase and/or uppercase English letters only. diff --git a/leetcode/longest_palindrome/__init__.py b/leetcode/longest_palindrome/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/longest_palindrome/helpers.py b/leetcode/longest_palindrome/helpers.py new file mode 100644 index 0000000..4137bb4 --- /dev/null +++ b/leetcode/longest_palindrome/helpers.py @@ -0,0 +1,8 @@ +def run_longest_palindrome(solution_class: type, s: str): + implementation = solution_class() + return implementation.longest_palindrome(s) + + +def assert_longest_palindrome(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/longest_palindrome/playground.ipynb b/leetcode/longest_palindrome/playground.ipynb new file mode 100644 index 0000000..31ff59e --- /dev/null +++ b/leetcode/longest_palindrome/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_longest_palindrome, run_longest_palindrome\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "s = \"abccccdd\"\n", + "expected = 7" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_longest_palindrome(Solution, s)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_longest_palindrome(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/longest_palindrome/solution.py b/leetcode/longest_palindrome/solution.py new file mode 100644 index 0000000..d5bbba7 --- /dev/null +++ b/leetcode/longest_palindrome/solution.py @@ -0,0 +1,18 @@ +class Solution: + + # Time: O(n) + # Space: O(1) + def longest_palindrome(self, s: str) -> int: + char_count: dict[str, int] = {} + for char in s: + char_count[char] = char_count.get(char, 0) + 1 + + length = 0 + has_odd = False + + for count in char_count.values(): + length += count // 2 * 2 + if count % 2 == 1: + has_odd = True + + return length + (1 if has_odd else 0) diff --git a/leetcode/longest_palindrome/test_solution.py b/leetcode/longest_palindrome/test_solution.py new file mode 100644 index 0000000..5c8e279 --- /dev/null +++ b/leetcode/longest_palindrome/test_solution.py @@ -0,0 +1,34 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_longest_palindrome, run_longest_palindrome +from .solution import Solution + + +class TestLongestPalindrome: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, expected", + [ + ("abccccdd", 7), + ("a", 1), + ("Aa", 1), + ("aabbcc", 6), + ("", 0), + ("aA", 1), + ("abcdef", 1), + ("aabbccdd", 8), + ("aaaa", 4), + ("abcdefg", 1), + ("AAaa", 4), + ("racecar", 7), + ("abcABC", 1), + ], + ) + def test_longest_palindrome(self, s: str, expected: int): + result = run_longest_palindrome(Solution, s) + assert_longest_palindrome(result, expected) diff --git a/leetcode/longest_palindromic_substring/README.md b/leetcode/longest_palindromic_substring/README.md new file mode 100644 index 0000000..29669ab --- /dev/null +++ b/leetcode/longest_palindromic_substring/README.md @@ -0,0 +1,34 @@ +# Longest Palindromic Substring + +**Difficulty:** Medium +**Topics:** Two Pointers, String, Dynamic Programming +**Tags:** grind-75 + +**LeetCode:** [Problem 5](https://leetcode.com/problems/longest-palindromic-substring/description/) + +## Problem Description + +Given a string `s`, return the longest palindromic substring in `s`. + +## Examples + +### Example 1: + +``` +Input: s = "babad" +Output: "bab" +``` + +**Explanation:** "aba" is also a valid answer. + +### Example 2: + +``` +Input: s = "cbbd" +Output: "bb" +``` + +## Constraints + +- `1 <= s.length <= 1000` +- `s` consist of only digits and English letters. diff --git a/leetcode/longest_palindromic_substring/__init__.py b/leetcode/longest_palindromic_substring/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/longest_palindromic_substring/helpers.py b/leetcode/longest_palindromic_substring/helpers.py new file mode 100644 index 0000000..f3c979c --- /dev/null +++ b/leetcode/longest_palindromic_substring/helpers.py @@ -0,0 +1,8 @@ +def run_longest_palindrome(solution_class: type, s: str): + implementation = solution_class() + return implementation.longest_palindrome(s) + + +def assert_longest_palindrome(result: str, expected: set[str]) -> bool: + assert result in expected + return True diff --git a/leetcode/longest_palindromic_substring/playground.ipynb b/leetcode/longest_palindromic_substring/playground.ipynb new file mode 100644 index 0000000..be01046 --- /dev/null +++ b/leetcode/longest_palindromic_substring/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_longest_palindrome, run_longest_palindrome\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "s = \"babad\"\n", + "expected = {\"bab\", \"aba\"}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_longest_palindrome(Solution, s)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_longest_palindrome(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/longest_palindromic_substring/solution.py b/leetcode/longest_palindromic_substring/solution.py new file mode 100644 index 0000000..defb806 --- /dev/null +++ b/leetcode/longest_palindromic_substring/solution.py @@ -0,0 +1,53 @@ +class Solution: + + # Time: O(n^2) + # Space: O(1) + def longest_palindrome(self, s: str) -> str: + start = 0 + max_len = 0 + + for i in range(len(s)): + # Odd length palindromes (center at i) + len1 = self.expand(s, i, i) + # Even length palindromes (center between i and i+1) + len2 = self.expand(s, i, i + 1) + + curr_len = max(len1, len2) + if curr_len > max_len: + max_len = curr_len + start = i - (curr_len - 1) // 2 + + return s[start : start + max_len] + + @staticmethod + def expand(s: str, left: int, right: int) -> int: + while left >= 0 and right < len(s) and s[left] == s[right]: + left -= 1 + right += 1 + return right - left - 1 + + +class SolutionManacher: + # Time: O(n) + # Space: O(n) + def longest_palindrome(self, s: str) -> str: + t = "#".join("^{}$".format(s)) + n = len(t) + p = [0] * n + center = right = 0 + for i in range(1, n - 1): + mirror_value = 2 * center - i + p[i] = min(right - i, p[mirror_value]) if right > i else 0 + + while t[i + 1 + p[i]] == t[i - 1 - p[i]]: + p[i] += 1 + + if i + p[i] > right: + center, right = i, i + p[i] + + max_len = max(p) + center_index = p.index(max_len) + + # Map back to original string: (center_index - max_len) // 2 + start = (center_index - max_len) // 2 + return s[start : start + max_len] diff --git a/leetcode/longest_palindromic_substring/test_solution.py b/leetcode/longest_palindromic_substring/test_solution.py new file mode 100644 index 0000000..832e8fa --- /dev/null +++ b/leetcode/longest_palindromic_substring/test_solution.py @@ -0,0 +1,33 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_longest_palindrome, run_longest_palindrome +from .solution import Solution + + +class TestLongestPalindromicSubstring: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, expected", + [ + ("babad", {"bab", "aba"}), + ("cbbd", {"bb"}), + ("a", {"a"}), + ("ac", {"a", "c"}), + ("racecar", {"racecar"}), + ("aabbaa", {"aabbaa"}), + ("abacabad", {"abacaba"}), + ("noon", {"noon"}), + ("abccba", {"abccba"}), + ("aa", {"aa"}), + ("aba", {"aba"}), + ("abcba", {"abcba"}), + ], + ) + def test_longest_palindrome(self, s: str, expected: set[str]): + result = run_longest_palindrome(Solution, s) + assert_longest_palindrome(result, expected) diff --git a/leetcode/longest_substring_without_repeating_characters/README.md b/leetcode/longest_substring_without_repeating_characters/README.md new file mode 100644 index 0000000..53c884d --- /dev/null +++ b/leetcode/longest_substring_without_repeating_characters/README.md @@ -0,0 +1,46 @@ +# Longest Substring Without Repeating Characters + +**Difficulty:** Medium +**Topics:** Hash Table, String, Sliding Window +**Tags:** grind-75 + +**LeetCode:** [Problem 3](https://leetcode.com/problems/longest-substring-without-repeating-characters/description/) + +## Problem Description + +Given a string `s`, find the length of the **longest** **substring** without duplicate characters. + +## Examples + +### Example 1: + +``` +Input: s = "abcabcbb" +Output: 3 +``` + +**Explanation:** The answer is "abc", with the length of 3. + +### Example 2: + +``` +Input: s = "bbbbb" +Output: 1 +``` + +**Explanation:** The answer is "b", with the length of 1. + +### Example 3: + +``` +Input: s = "pwwkew" +Output: 3 +``` + +**Explanation:** The answer is "wke", with the length of 3. +Notice that the answer must be a substring, "pwke" is a subsequence and not a substring. + +## Constraints + +- 0 <= s.length <= 5 \* 10^4 +- s consists of English letters, digits, symbols and spaces. diff --git a/leetcode/longest_substring_without_repeating_characters/__init__.py b/leetcode/longest_substring_without_repeating_characters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/longest_substring_without_repeating_characters/helpers.py b/leetcode/longest_substring_without_repeating_characters/helpers.py new file mode 100644 index 0000000..86678c6 --- /dev/null +++ b/leetcode/longest_substring_without_repeating_characters/helpers.py @@ -0,0 +1,8 @@ +def run_length_of_longest_substring(solution_class: type, s: str): + implementation = solution_class() + return implementation.length_of_longest_substring(s) + + +def assert_length_of_longest_substring(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/longest_substring_without_repeating_characters/playground.ipynb b/leetcode/longest_substring_without_repeating_characters/playground.ipynb new file mode 100644 index 0000000..0c23372 --- /dev/null +++ b/leetcode/longest_substring_without_repeating_characters/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_length_of_longest_substring, run_length_of_longest_substring\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "s = \"abcabcbb\"\n", + "expected = 3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_length_of_longest_substring(Solution, s)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_length_of_longest_substring(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/longest_substring_without_repeating_characters/solution.py b/leetcode/longest_substring_without_repeating_characters/solution.py new file mode 100644 index 0000000..98b8468 --- /dev/null +++ b/leetcode/longest_substring_without_repeating_characters/solution.py @@ -0,0 +1,16 @@ +class Solution: + + # Time: O(n) + # Space: O(min(m, n)) where m is charset size + def length_of_longest_substring(self, s: str) -> int: + seen: set[str] = set() + left = max_len = 0 + + for right in range(len(s)): + while s[right] in seen: + seen.remove(s[left]) + left += 1 + seen.add(s[right]) + max_len = max(max_len, right - left + 1) + + return max_len diff --git a/leetcode/longest_substring_without_repeating_characters/test_solution.py b/leetcode/longest_substring_without_repeating_characters/test_solution.py new file mode 100644 index 0000000..3193ea9 --- /dev/null +++ b/leetcode/longest_substring_without_repeating_characters/test_solution.py @@ -0,0 +1,34 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_length_of_longest_substring, run_length_of_longest_substring +from .solution import Solution + + +class TestLongestSubstringWithoutRepeatingCharacters: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, expected", + [ + ("abcabcbb", 3), + ("bbbbb", 1), + ("pwwkew", 3), + ("", 0), + ("a", 1), + ("au", 2), + ("dvdf", 3), + ("abcdef", 6), + ("aab", 2), + ("tmmzuxt", 5), + (" ", 1), + (" ", 1), + ("abba", 2), + ], + ) + def test_length_of_longest_substring(self, s: str, expected: int): + result = run_length_of_longest_substring(Solution, s) + assert_length_of_longest_substring(result, expected) diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/README.md b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/README.md new file mode 100644 index 0000000..a5b2f27 --- /dev/null +++ b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/README.md @@ -0,0 +1,52 @@ +# Lowest Common Ancestor of a Binary Search Tree + +**Difficulty:** Medium +**Topics:** Tree, Depth-First Search, Binary Search Tree, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 235](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/) + +## Problem Description + +Given a binary search tree (BST), find the lowest common ancestor (LCA) node of two given nodes in the BST. + +According to the definition of LCA on Wikipedia: "The lowest common ancestor is defined between two nodes `p` and `q` as the lowest node in `T` that has both `p` and `q` as descendants (where we allow **a node to be a descendant of itself**)." + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2018/12/14/binarysearchtree_improved.png) + +``` +Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 +Output: 6 +``` + +**Explanation:** The LCA of nodes 2 and 8 is 6. + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2018/12/14/binarysearchtree_improved.png) + +``` +Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 +Output: 2 +``` + +**Explanation:** The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition. + +### Example 3: + +``` +Input: root = [2,1], p = 2, q = 1 +Output: 2 +``` + +## Constraints + +- The number of nodes in the tree is in the range `[2, 10^5]`. +- `-10^9 <= Node.val <= 10^9` +- All `Node.val` are **unique**. +- `p != q` +- `p` and `q` will exist in the BST. diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/__init__.py b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/helpers.py b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/helpers.py new file mode 100644 index 0000000..101bbb1 --- /dev/null +++ b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/helpers.py @@ -0,0 +1,19 @@ +from leetcode_py import TreeNode + + +def run_lowest_common_ancestor( + solution_class: type, root_list: list[int | None], p_val: int, q_val: int +): + root = TreeNode[int].from_list(root_list) + assert root is not None + p = root.find_node(p_val) + q = root.find_node(q_val) + assert p is not None and q is not None + implementation = solution_class() + return implementation.lowest_common_ancestor(root, p, q) + + +def assert_lowest_common_ancestor(result: TreeNode[int] | None, expected_val: int) -> bool: + assert result is not None + assert result.val == expected_val + return True diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb new file mode 100644 index 0000000..6f5c85e --- /dev/null +++ b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb @@ -0,0 +1,72 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import TreeNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root_list = [6, 2, 8, 0, 4, 7, 9, None, None, 3, 5]\n", + "p_val = 2\n", + "q_val = 8\n", + "expected_val = 6" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)\n", + "result.val if result else None" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_lowest_common_ancestor(result, expected_val)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/solution.py b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/solution.py new file mode 100644 index 0000000..1d88ec8 --- /dev/null +++ b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/solution.py @@ -0,0 +1,21 @@ +from leetcode_py import TreeNode + + +class Solution: + + # Time: O(log n) average, O(n) worst case + # Space: O(1) iterative, O(log n) recursive + def lowest_common_ancestor( + self, root: TreeNode[int] | None, p: TreeNode[int], q: TreeNode[int] + ) -> TreeNode[int] | None: + while root: + # Both nodes are in left subtree + if p.val < root.val and q.val < root.val: + root = root.left + # Both nodes are in right subtree + elif p.val > root.val and q.val > root.val: + root = root.right + # Split point - one node on each side or one is the root + else: + return root + return None diff --git a/leetcode/lowest_common_ancestor_of_a_binary_search_tree/test_solution.py b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/test_solution.py new file mode 100644 index 0000000..d92a905 --- /dev/null +++ b/leetcode/lowest_common_ancestor_of_a_binary_search_tree/test_solution.py @@ -0,0 +1,29 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor +from .solution import Solution + + +class TestLowestCommonAncestorOfABinarySearchTree: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "root_list, p_val, q_val, expected_val", + [ + ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 8, 6), + ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 4, 2), + ([2, 1], 2, 1, 2), + ([2, 1], 1, 2, 2), + ([6, 2, 8, 0, 4, 7, 9], 0, 4, 2), + ([6, 2, 8, 0, 4, 7, 9], 7, 9, 8), + ], + ) + def test_lowest_common_ancestor( + self, root_list: list[int | None], p_val: int, q_val: int, expected_val: int + ): + result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val) + assert_lowest_common_ancestor(result, expected_val) diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/README.md b/leetcode/lowest_common_ancestor_of_a_binary_tree/README.md new file mode 100644 index 0000000..1c562a6 --- /dev/null +++ b/leetcode/lowest_common_ancestor_of_a_binary_tree/README.md @@ -0,0 +1,50 @@ +# Lowest Common Ancestor of a Binary Tree + +**Difficulty:** Medium +**Topics:** Tree, Depth-First Search, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 236](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/) + +## Problem Description + +Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. + +According to the definition of LCA on Wikipedia: "The lowest common ancestor is defined between two nodes `p` and `q` as the lowest node in `T` that has both `p` and `q` as descendants (where we allow **a node to be a descendant of itself**)." + +## Examples + +### Example 1: + + + +``` +Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 +Output: 3 +Explanation: The LCA of nodes 5 and 1 is 3. +``` + +### Example 2: + + + +``` +Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 +Output: 5 +Explanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition. +``` + +### Example 3: + +``` +Input: root = [1,2], p = 1, q = 2 +Output: 1 +``` + +## Constraints + +- The number of nodes in the tree is in the range [2, 10^5]. +- -10^9 <= Node.val <= 10^9 +- All Node.val are unique. +- p != q +- p and q will exist in the tree. diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/__init__.py b/leetcode/lowest_common_ancestor_of_a_binary_tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/helpers.py b/leetcode/lowest_common_ancestor_of_a_binary_tree/helpers.py new file mode 100644 index 0000000..d3c072c --- /dev/null +++ b/leetcode/lowest_common_ancestor_of_a_binary_tree/helpers.py @@ -0,0 +1,18 @@ +from leetcode_py import TreeNode + + +def run_lowest_common_ancestor( + solution_class: type, root_list: list[int | None], p_val: int, q_val: int +): + root = TreeNode[int].from_list(root_list) + assert root is not None + p = root.find_node(p_val) + q = root.find_node(q_val) + assert p is not None and q is not None + implementation = solution_class() + return implementation.lowest_common_ancestor(root, p, q) + + +def assert_lowest_common_ancestor(result: TreeNode[int], expected_val: int) -> bool: + assert result.val == expected_val + return True diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/playground.ipynb b/leetcode/lowest_common_ancestor_of_a_binary_tree/playground.ipynb new file mode 100644 index 0000000..0251030 --- /dev/null +++ b/leetcode/lowest_common_ancestor_of_a_binary_tree/playground.ipynb @@ -0,0 +1,72 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import TreeNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root_list = [3, 5, 1, 6, 2, 0, 8, None, None, 7, 4]\n", + "p_val = 5\n", + "q_val = 1\n", + "expected_val = 3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val)\n", + "result.val" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_lowest_common_ancestor(result, expected_val)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/solution.py b/leetcode/lowest_common_ancestor_of_a_binary_tree/solution.py new file mode 100644 index 0000000..0075ae4 --- /dev/null +++ b/leetcode/lowest_common_ancestor_of_a_binary_tree/solution.py @@ -0,0 +1,25 @@ +from leetcode_py import TreeNode + + +class Solution: + # Time: O(n) + # Space: O(h) + def lowest_common_ancestor( + self, root: TreeNode[int], p: TreeNode[int], q: TreeNode[int] + ) -> TreeNode[int]: + result = self._lca(root, p, q) + assert result is not None + return result + + def _lca( + self, root: TreeNode[int] | None, p: TreeNode[int], q: TreeNode[int] + ) -> TreeNode[int] | None: + if not root or root == p or root == q: + return root + + left = self._lca(root.left, p, q) + right = self._lca(root.right, p, q) + + if left and right: + return root + return left or right diff --git a/leetcode/lowest_common_ancestor_of_a_binary_tree/test_solution.py b/leetcode/lowest_common_ancestor_of_a_binary_tree/test_solution.py new file mode 100644 index 0000000..0f1d86a --- /dev/null +++ b/leetcode/lowest_common_ancestor_of_a_binary_tree/test_solution.py @@ -0,0 +1,29 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_lowest_common_ancestor, run_lowest_common_ancestor +from .solution import Solution + + +class TestLowestCommonAncestorOfABinaryTree: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "root_list, p_val, q_val, expected_val", + [ + ([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 5, 1, 3), + ([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 5, 4, 5), + ([1, 2], 1, 2, 1), + ([2, 1], 2, 1, 2), + ([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 6, 7, 5), + ([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 0, 8, 1), + ], + ) + def test_lowest_common_ancestor( + self, root_list: list[int | None], p_val: int, q_val: int, expected_val: int + ): + result = run_lowest_common_ancestor(Solution, root_list, p_val, q_val) + assert_lowest_common_ancestor(result, expected_val) diff --git a/leetcode/majority_element/README.md b/leetcode/majority_element/README.md new file mode 100644 index 0000000..3822272 --- /dev/null +++ b/leetcode/majority_element/README.md @@ -0,0 +1,37 @@ +# Majority Element + +**Difficulty:** Easy +**Topics:** Array, Hash Table, Divide and Conquer, Sorting, Counting +**Tags:** grind-75 + +**LeetCode:** [Problem 169](https://leetcode.com/problems/majority-element/description/) + +## Problem Description + +Given an array `nums` of size `n`, return the majority element. + +The majority element is the element that appears more than `⌊n / 2⌋` times. You may assume that the majority element always exists in the array. + +## Examples + +### Example 1: + +``` +Input: nums = [3,2,3] +Output: 3 +``` + +### Example 2: + +``` +Input: nums = [2,2,1,1,1,2,2] +Output: 2 +``` + +## Constraints + +- n == nums.length +- 1 <= n <= 5 \* 10^4 +- -10^9 <= nums[i] <= 10^9 + +**Follow-up:** Could you solve the problem in linear time and in O(1) space? diff --git a/leetcode/majority_element/__init__.py b/leetcode/majority_element/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/majority_element/helpers.py b/leetcode/majority_element/helpers.py new file mode 100644 index 0000000..c7e6d09 --- /dev/null +++ b/leetcode/majority_element/helpers.py @@ -0,0 +1,8 @@ +def run_majority_element(solution_class: type, nums: list[int]): + implementation = solution_class() + return implementation.majority_element(nums) + + +def assert_majority_element(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/majority_element/playground.ipynb b/leetcode/majority_element/playground.ipynb new file mode 100644 index 0000000..969279e --- /dev/null +++ b/leetcode/majority_element/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_majority_element, run_majority_element\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [3, 2, 3]\n", + "expected = 3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_majority_element(Solution, nums)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_majority_element(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/majority_element/solution.py b/leetcode/majority_element/solution.py new file mode 100644 index 0000000..cf2f1dc --- /dev/null +++ b/leetcode/majority_element/solution.py @@ -0,0 +1,15 @@ +class Solution: + + # Time: O(n) + # Space: O(1) + # Boyer-Moore Voting Algorithm + def majority_element(self, nums: list[int]) -> int: + candidate = 0 + count = 0 + + for num in nums: + if count == 0: + candidate = num + count += 1 if num == candidate else -1 + + return candidate diff --git a/leetcode/majority_element/test_solution.py b/leetcode/majority_element/test_solution.py new file mode 100644 index 0000000..d4c439f --- /dev/null +++ b/leetcode/majority_element/test_solution.py @@ -0,0 +1,30 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_majority_element, run_majority_element +from .solution import Solution + + +class TestMajorityElement: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([3, 2, 3], 3), + ([2, 2, 1, 1, 1, 2, 2], 2), + ([1], 1), + ([1, 1, 2], 1), + ([2, 2, 2, 1, 1], 2), + ([5, 5, 5, 5, 1, 2, 3], 5), + ([1, 2, 3, 4, 4, 4, 4], 4), + ([0, 0, 0], 0), + ([-1, -1, -1, 1, 1], -1), + ], + ) + def test_majority_element(self, nums: list[int], expected: int): + result = run_majority_element(Solution, nums) + assert_majority_element(result, expected) diff --git a/leetcode/maximum_depth_of_binary_tree/README.md b/leetcode/maximum_depth_of_binary_tree/README.md new file mode 100644 index 0000000..4663e24 --- /dev/null +++ b/leetcode/maximum_depth_of_binary_tree/README.md @@ -0,0 +1,36 @@ +# Maximum Depth of Binary Tree + +**Difficulty:** Easy +**Topics:** Tree, Depth-First Search, Breadth-First Search, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 104](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/) + +## Problem Description + +Given the `root` of a binary tree, return _its maximum depth_. + +A binary tree's **maximum depth** is the number of nodes along the longest path from the root node down to the farthest leaf node. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2020/11/26/tmp-tree.jpg) + +``` +Input: root = [3,9,20,null,null,15,7] +Output: 3 +``` + +### Example 2: + +``` +Input: root = [1,null,2] +Output: 2 +``` + +## Constraints + +- The number of nodes in the tree is in the range `[0, 10^4]`. +- `-100 <= Node.val <= 100` diff --git a/leetcode/maximum_depth_of_binary_tree/__init__.py b/leetcode/maximum_depth_of_binary_tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/maximum_depth_of_binary_tree/helpers.py b/leetcode/maximum_depth_of_binary_tree/helpers.py new file mode 100644 index 0000000..eeca53c --- /dev/null +++ b/leetcode/maximum_depth_of_binary_tree/helpers.py @@ -0,0 +1,12 @@ +from leetcode_py import TreeNode + + +def run_max_depth(solution_class: type, root_list: list[int | None]): + root = TreeNode[int].from_list(root_list) + implementation = solution_class() + return implementation.max_depth(root) + + +def assert_max_depth(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/maximum_depth_of_binary_tree/playground.ipynb b/leetcode/maximum_depth_of_binary_tree/playground.ipynb new file mode 100644 index 0000000..53406b7 --- /dev/null +++ b/leetcode/maximum_depth_of_binary_tree/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_max_depth, run_max_depth\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import TreeNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root_list = [3, 9, 20, None, None, 15, 7]\n", + "expected = 3" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_max_depth(Solution, root_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_max_depth(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/maximum_depth_of_binary_tree/solution.py b/leetcode/maximum_depth_of_binary_tree/solution.py new file mode 100644 index 0000000..56abe42 --- /dev/null +++ b/leetcode/maximum_depth_of_binary_tree/solution.py @@ -0,0 +1,17 @@ +from leetcode_py import TreeNode + + +class Solution: + + # Time: O(?) + # Space: O(?) + def max_depth(self, root: TreeNode[int] | None) -> int: + # Time: O(n) + # Space: O(h) + if not root: + return 0 + + left_depth = self.max_depth(root.left) + right_depth = self.max_depth(root.right) + + return 1 + max(left_depth, right_depth) diff --git a/leetcode/maximum_depth_of_binary_tree/test_solution.py b/leetcode/maximum_depth_of_binary_tree/test_solution.py new file mode 100644 index 0000000..229701e --- /dev/null +++ b/leetcode/maximum_depth_of_binary_tree/test_solution.py @@ -0,0 +1,29 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_max_depth, run_max_depth +from .solution import Solution + + +class TestMaximumDepthOfBinaryTree: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "root_list, expected", + [ + ([3, 9, 20, None, None, 15, 7], 3), + ([1, None, 2], 2), + ([], 0), + ([1], 1), + ([1, 2], 2), + ([1, 2, 3], 2), + ([1, 2, 3, 4], 3), + ([1, None, 2, None, 3], 3), + ], + ) + def test_max_depth(self, root_list: list[int | None], expected: int): + result = run_max_depth(Solution, root_list) + assert_max_depth(result, expected) diff --git a/leetcode/maximum_profit_in_job_scheduling/README.md b/leetcode/maximum_profit_in_job_scheduling/README.md new file mode 100644 index 0000000..100bfa7 --- /dev/null +++ b/leetcode/maximum_profit_in_job_scheduling/README.md @@ -0,0 +1,54 @@ +# Maximum Profit in Job Scheduling + +**Difficulty:** Hard +**Topics:** Array, Binary Search, Dynamic Programming, Sorting +**Tags:** grind-75 + +**LeetCode:** [Problem 1235](https://leetcode.com/problems/maximum-profit-in-job-scheduling/description/) + +## Problem Description + +We have `n` jobs, where every job is scheduled to be done from `startTime[i]` to `endTime[i]`, obtaining a profit of `profit[i]`. + +You're given the `startTime`, `endTime` and `profit` arrays, return the maximum profit you can take such that there are no two jobs in the subset with overlapping time range. + +If you choose a job that ends at time `X` you will be able to start another job that starts at time `X`. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2019/10/10/sample1_1584.png) + +``` +Input: startTime = [1,2,3,3], endTime = [3,4,5,6], profit = [50,10,40,70] +Output: 120 +``` + +**Explanation:** The subset chosen is the first and fourth job. Time range [1-3]+[3-6] , we get profit of 120 = 50 + 70. + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2019/10/10/sample22_1584.png) + +``` +Input: startTime = [1,2,3,4,6], endTime = [3,5,10,6,9], profit = [20,20,100,70,60] +Output: 150 +``` + +**Explanation:** The subset chosen is the first, fourth and fifth job. Profit obtained 150 = 20 + 70 + 60. + +### Example 3: + +![Example 3](https://assets.leetcode.com/uploads/2019/10/10/sample3_1584.png) + +``` +Input: startTime = [1,1,1], endTime = [2,3,4], profit = [5,6,4] +Output: 6 +``` + +## Constraints + +- `1 <= startTime.length == endTime.length == profit.length <= 5 * 10^4` +- `1 <= startTime[i] < endTime[i] <= 10^9` +- `1 <= profit[i] <= 10^4` diff --git a/leetcode/maximum_profit_in_job_scheduling/__init__.py b/leetcode/maximum_profit_in_job_scheduling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/maximum_profit_in_job_scheduling/helpers.py b/leetcode/maximum_profit_in_job_scheduling/helpers.py new file mode 100644 index 0000000..06e90fe --- /dev/null +++ b/leetcode/maximum_profit_in_job_scheduling/helpers.py @@ -0,0 +1,10 @@ +def run_job_scheduling( + solution_class: type, start_time: list[int], end_time: list[int], profit: list[int] +): + implementation = solution_class() + return implementation.job_scheduling(start_time, end_time, profit) + + +def assert_job_scheduling(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/maximum_profit_in_job_scheduling/playground.ipynb b/leetcode/maximum_profit_in_job_scheduling/playground.ipynb new file mode 100644 index 0000000..cf2e659 --- /dev/null +++ b/leetcode/maximum_profit_in_job_scheduling/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_job_scheduling, run_job_scheduling\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "start_time = [1, 2, 3, 3]\n", + "end_time = [3, 4, 5, 6]\n", + "profit = [50, 10, 40, 70]\n", + "expected = 120" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_job_scheduling(Solution, start_time, end_time, profit)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_job_scheduling(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/maximum_profit_in_job_scheduling/solution.py b/leetcode/maximum_profit_in_job_scheduling/solution.py new file mode 100644 index 0000000..964c396 --- /dev/null +++ b/leetcode/maximum_profit_in_job_scheduling/solution.py @@ -0,0 +1,22 @@ +import bisect + + +class Solution: + # Time: O(n log n) + # Space: O(n) + def job_scheduling(self, start_time: list[int], end_time: list[int], profit: list[int]) -> int: + jobs = sorted(zip(end_time, start_time, profit)) + dp = [0] * len(jobs) + + for i, (end, start, p) in enumerate(jobs): + # Binary search for latest non-overlapping job + j = bisect.bisect_right([job[0] for job in jobs[:i]], start) - 1 + + # Take current job + best profit from non-overlapping jobs + take = p + (dp[j] if j >= 0 else 0) + # Skip current job + skip = dp[i - 1] if i > 0 else 0 + + dp[i] = max(take, skip) + + return dp[-1] if jobs else 0 diff --git a/leetcode/maximum_profit_in_job_scheduling/test_solution.py b/leetcode/maximum_profit_in_job_scheduling/test_solution.py new file mode 100644 index 0000000..3cfe330 --- /dev/null +++ b/leetcode/maximum_profit_in_job_scheduling/test_solution.py @@ -0,0 +1,27 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_job_scheduling, run_job_scheduling +from .solution import Solution + + +class TestMaximumProfitInJobScheduling: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "start_time, end_time, profit, expected", + [ + ([1, 2, 3, 3], [3, 4, 5, 6], [50, 10, 40, 70], 120), + ([1, 2, 3, 4, 6], [3, 5, 10, 6, 9], [20, 20, 100, 70, 60], 150), + ([1, 1, 1], [2, 3, 4], [5, 6, 4], 6), + ([1], [2], [100], 100), + ], + ) + def test_job_scheduling( + self, start_time: list[int], end_time: list[int], profit: list[int], expected: int + ): + result = run_job_scheduling(Solution, start_time, end_time, profit) + assert_job_scheduling(result, expected) From ca892d7a1c4c810f733ebfc973449ca5e26782d7 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Thu, 11 Sep 2025 17:10:52 +0700 Subject: [PATCH 17/39] feat: add partition_equal_subset_sum --- .../leetcode/json/maximum_subarray.json | 67 ++++++++++ .templates/leetcode/json/merge_intervals.json | 67 ++++++++++ .../leetcode/json/merge_k_sorted_lists.json | 63 +++++++++ .../leetcode/json/merge_two_sorted_lists.json | 6 +- .../json/middle_of_the_linked_list.json | 64 +++++++++ .templates/leetcode/json/min_stack.json | 79 +++++++++++ .../leetcode/json/minimum_height_trees.json | 64 +++++++++ .../json/minimum_window_substring.json | 67 ++++++++++ .../leetcode/json/number_of_islands.json | 64 +++++++++ .../json/partition_equal_subset_sum.json | 64 +++++++++ .../maximum_depth_of_binary_tree/solution.py | 6 +- leetcode/maximum_subarray/README.md | 47 +++++++ leetcode/maximum_subarray/__init__.py | 0 leetcode/maximum_subarray/helpers.py | 8 ++ leetcode/maximum_subarray/playground.ipynb | 68 ++++++++++ leetcode/maximum_subarray/solution.py | 12 ++ leetcode/maximum_subarray/test_solution.py | 28 ++++ leetcode/merge_intervals/README.md | 46 +++++++ leetcode/merge_intervals/__init__.py | 0 leetcode/merge_intervals/helpers.py | 8 ++ leetcode/merge_intervals/playground.ipynb | 68 ++++++++++ leetcode/merge_intervals/solution.py | 15 +++ leetcode/merge_intervals/test_solution.py | 27 ++++ leetcode/merge_k_sorted_lists/README.md | 61 +++++++++ leetcode/merge_k_sorted_lists/__init__.py | 0 leetcode/merge_k_sorted_lists/helpers.py | 13 ++ .../merge_k_sorted_lists/playground.ipynb | 70 ++++++++++ leetcode/merge_k_sorted_lists/solution.py | 38 ++++++ .../merge_k_sorted_lists/test_solution.py | 30 +++++ .../merge_two_sorted_lists/playground.ipynb | 123 ++---------------- leetcode/merge_two_sorted_lists/solution.py | 2 +- .../merge_two_sorted_lists/test_solution.py | 6 +- leetcode/middle_of_the_linked_list/README.md | 42 ++++++ .../middle_of_the_linked_list/__init__.py | 0 leetcode/middle_of_the_linked_list/helpers.py | 13 ++ .../playground.ipynb | 70 ++++++++++ .../middle_of_the_linked_list/solution.py | 14 ++ .../test_solution.py | 28 ++++ leetcode/min_stack/README.md | 53 ++++++++ leetcode/min_stack/__init__.py | 0 leetcode/min_stack/helpers.py | 23 ++++ leetcode/min_stack/playground.ipynb | 69 ++++++++++ leetcode/min_stack/solution.py | 30 +++++ leetcode/min_stack/test_solution.py | 34 +++++ leetcode/minimum_height_trees/README.md | 47 +++++++ leetcode/minimum_height_trees/__init__.py | 0 leetcode/minimum_height_trees/helpers.py | 8 ++ .../minimum_height_trees/playground.ipynb | 69 ++++++++++ leetcode/minimum_height_trees/solution.py | 29 +++++ .../minimum_height_trees/test_solution.py | 26 ++++ leetcode/minimum_window_substring/README.md | 51 ++++++++ leetcode/minimum_window_substring/__init__.py | 0 leetcode/minimum_window_substring/helpers.py | 8 ++ .../minimum_window_substring/playground.ipynb | 69 ++++++++++ leetcode/minimum_window_substring/solution.py | 49 +++++++ .../minimum_window_substring/test_solution.py | 26 ++++ leetcode/number_of_islands/README.md | 46 +++++++ leetcode/number_of_islands/__init__.py | 0 leetcode/number_of_islands/helpers.py | 8 ++ leetcode/number_of_islands/playground.ipynb | 73 +++++++++++ leetcode/number_of_islands/solution.py | 28 ++++ leetcode/number_of_islands/test_solution.py | 42 ++++++ leetcode/partition_equal_subset_sum/README.md | 36 +++++ .../partition_equal_subset_sum/__init__.py | 0 .../partition_equal_subset_sum/helpers.py | 8 ++ .../playground.ipynb | 68 ++++++++++ .../partition_equal_subset_sum/solution.py | 22 ++++ .../test_solution.py | 28 ++++ 68 files changed, 2273 insertions(+), 125 deletions(-) create mode 100644 .templates/leetcode/json/maximum_subarray.json create mode 100644 .templates/leetcode/json/merge_intervals.json create mode 100644 .templates/leetcode/json/merge_k_sorted_lists.json create mode 100644 .templates/leetcode/json/middle_of_the_linked_list.json create mode 100644 .templates/leetcode/json/min_stack.json create mode 100644 .templates/leetcode/json/minimum_height_trees.json create mode 100644 .templates/leetcode/json/minimum_window_substring.json create mode 100644 .templates/leetcode/json/number_of_islands.json create mode 100644 .templates/leetcode/json/partition_equal_subset_sum.json create mode 100644 leetcode/maximum_subarray/README.md create mode 100644 leetcode/maximum_subarray/__init__.py create mode 100644 leetcode/maximum_subarray/helpers.py create mode 100644 leetcode/maximum_subarray/playground.ipynb create mode 100644 leetcode/maximum_subarray/solution.py create mode 100644 leetcode/maximum_subarray/test_solution.py create mode 100644 leetcode/merge_intervals/README.md create mode 100644 leetcode/merge_intervals/__init__.py create mode 100644 leetcode/merge_intervals/helpers.py create mode 100644 leetcode/merge_intervals/playground.ipynb create mode 100644 leetcode/merge_intervals/solution.py create mode 100644 leetcode/merge_intervals/test_solution.py create mode 100644 leetcode/merge_k_sorted_lists/README.md create mode 100644 leetcode/merge_k_sorted_lists/__init__.py create mode 100644 leetcode/merge_k_sorted_lists/helpers.py create mode 100644 leetcode/merge_k_sorted_lists/playground.ipynb create mode 100644 leetcode/merge_k_sorted_lists/solution.py create mode 100644 leetcode/merge_k_sorted_lists/test_solution.py create mode 100644 leetcode/middle_of_the_linked_list/README.md create mode 100644 leetcode/middle_of_the_linked_list/__init__.py create mode 100644 leetcode/middle_of_the_linked_list/helpers.py create mode 100644 leetcode/middle_of_the_linked_list/playground.ipynb create mode 100644 leetcode/middle_of_the_linked_list/solution.py create mode 100644 leetcode/middle_of_the_linked_list/test_solution.py create mode 100644 leetcode/min_stack/README.md create mode 100644 leetcode/min_stack/__init__.py create mode 100644 leetcode/min_stack/helpers.py create mode 100644 leetcode/min_stack/playground.ipynb create mode 100644 leetcode/min_stack/solution.py create mode 100644 leetcode/min_stack/test_solution.py create mode 100644 leetcode/minimum_height_trees/README.md create mode 100644 leetcode/minimum_height_trees/__init__.py create mode 100644 leetcode/minimum_height_trees/helpers.py create mode 100644 leetcode/minimum_height_trees/playground.ipynb create mode 100644 leetcode/minimum_height_trees/solution.py create mode 100644 leetcode/minimum_height_trees/test_solution.py create mode 100644 leetcode/minimum_window_substring/README.md create mode 100644 leetcode/minimum_window_substring/__init__.py create mode 100644 leetcode/minimum_window_substring/helpers.py create mode 100644 leetcode/minimum_window_substring/playground.ipynb create mode 100644 leetcode/minimum_window_substring/solution.py create mode 100644 leetcode/minimum_window_substring/test_solution.py create mode 100644 leetcode/number_of_islands/README.md create mode 100644 leetcode/number_of_islands/__init__.py create mode 100644 leetcode/number_of_islands/helpers.py create mode 100644 leetcode/number_of_islands/playground.ipynb create mode 100644 leetcode/number_of_islands/solution.py create mode 100644 leetcode/number_of_islands/test_solution.py create mode 100644 leetcode/partition_equal_subset_sum/README.md create mode 100644 leetcode/partition_equal_subset_sum/__init__.py create mode 100644 leetcode/partition_equal_subset_sum/helpers.py create mode 100644 leetcode/partition_equal_subset_sum/playground.ipynb create mode 100644 leetcode/partition_equal_subset_sum/solution.py create mode 100644 leetcode/partition_equal_subset_sum/test_solution.py diff --git a/.templates/leetcode/json/maximum_subarray.json b/.templates/leetcode/json/maximum_subarray.json new file mode 100644 index 0000000..bb4e76a --- /dev/null +++ b/.templates/leetcode/json/maximum_subarray.json @@ -0,0 +1,67 @@ +{ + "problem_name": "maximum_subarray", + "solution_class_name": "Solution", + "problem_number": "53", + "problem_title": "Maximum Subarray", + "difficulty": "Medium", + "topics": "Array, Divide and Conquer, Dynamic Programming", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an integer array `nums`, find the subarray with the largest sum, and return its sum.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums = [-2,1,-3,4,-1,2,1,-5,4]\nOutput: 6\n```\n**Explanation:** The subarray [4,-1,2,1] has the largest sum 6." + }, + { + "content": "```\nInput: nums = [1]\nOutput: 1\n```\n**Explanation:** The subarray [1] has the largest sum 1." + }, + { + "content": "```\nInput: nums = [5,4,-1,7,8]\nOutput: 23\n```\n**Explanation:** The subarray [5,4,-1,7,8] has the largest sum 23." + } + ] + }, + "readme_constraints": "- `1 <= nums.length <= 10^5`\n- `-10^4 <= nums[i] <= 10^4`", + "readme_additional": "**Follow up:** If you have figured out the `O(n)` solution, try coding another solution using the **divide and conquer** approach, which is more subtle.", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "max_sub_array", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.max_sub_array(nums)", + "helpers_assert_name": "max_sub_array", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_max_sub_array, run_max_sub_array\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MaximumSubarray", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "max_sub_array", + "signature": "(self, nums: list[int]) -> int", + "body": " # TODO: Implement max_sub_array\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_max_sub_array", + "signature": "(self, nums: list[int], expected: int)", + "parametrize": "nums, expected", + "test_cases": "[([-2, 1, -3, 4, -1, 2, 1, -5, 4], 6), ([1], 1), ([5, 4, -1, 7, 8], 23), ([-1], -1), ([-2, -1], -1), ([1, 2, 3, 4, 5], 15), ([-5, -2, -8, -1], -1)]", + "body": " result = run_max_sub_array(Solution, nums)\n assert_max_sub_array(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_max_sub_array, assert_max_sub_array\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]\nexpected = 6", + "playground_run": "result = run_max_sub_array(Solution, nums)\nresult", + "playground_assert": "assert_max_sub_array(result, expected)" +} diff --git a/.templates/leetcode/json/merge_intervals.json b/.templates/leetcode/json/merge_intervals.json new file mode 100644 index 0000000..7c0efbf --- /dev/null +++ b/.templates/leetcode/json/merge_intervals.json @@ -0,0 +1,67 @@ +{ + "problem_name": "merge_intervals", + "solution_class_name": "Solution", + "problem_number": "56", + "problem_title": "Merge Intervals", + "difficulty": "Medium", + "topics": "Array, Sorting", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an array of `intervals` where `intervals[i] = [starti, endi]`, merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: intervals = [[1,3],[2,6],[8,10],[15,18]]\nOutput: [[1,6],[8,10],[15,18]]\n```\n**Explanation:** Since intervals [1,3] and [2,6] overlap, merge them into [1,6]." + }, + { + "content": "```\nInput: intervals = [[1,4],[4,5]]\nOutput: [[1,5]]\n```\n**Explanation:** Intervals [1,4] and [4,5] are considered overlapping." + }, + { + "content": "```\nInput: intervals = [[4,7],[1,4]]\nOutput: [[1,7]]\n```\n**Explanation:** Intervals [1,4] and [4,7] are considered overlapping." + } + ] + }, + "readme_constraints": "- `1 <= intervals.length <= 10^4`\n- `intervals[i].length == 2`\n- `0 <= starti <= endi <= 10^4`", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "merge", + "helpers_run_signature": "(solution_class: type, intervals: list[list[int]])", + "helpers_run_body": " implementation = solution_class()\n return implementation.merge(intervals)", + "helpers_assert_name": "merge", + "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_merge, run_merge\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MergeIntervals", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "merge", + "signature": "(self, intervals: list[list[int]]) -> list[list[int]]", + "body": " # TODO: Implement merge\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_merge", + "signature": "(self, intervals: list[list[int]], expected: list[list[int]])", + "parametrize": "intervals, expected", + "test_cases": "[([[1,3],[2,6],[8,10],[15,18]], [[1,6],[8,10],[15,18]]), ([[1,4],[4,5]], [[1,5]]), ([[4,7],[1,4]], [[1,7]]), ([[1,3]], [[1,3]]), ([[1,4],[2,3]], [[1,4]]), ([[1,2],[3,4],[5,6]], [[1,2],[3,4],[5,6]])]", + "body": " result = run_merge(Solution, intervals)\n assert_merge(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_merge, assert_merge\nfrom solution import Solution", + "playground_setup": "# Example test case\nintervals = [[1,3],[2,6],[8,10],[15,18]]\nexpected = [[1,6],[8,10],[15,18]]", + "playground_run": "result = run_merge(Solution, intervals)\nresult", + "playground_assert": "assert_merge(result, expected)" +} diff --git a/.templates/leetcode/json/merge_k_sorted_lists.json b/.templates/leetcode/json/merge_k_sorted_lists.json new file mode 100644 index 0000000..3031430 --- /dev/null +++ b/.templates/leetcode/json/merge_k_sorted_lists.json @@ -0,0 +1,63 @@ +{ + "problem_name": "merge_k_sorted_lists", + "solution_class_name": "Solution", + "problem_number": "23", + "problem_title": "Merge k Sorted Lists", + "difficulty": "Hard", + "topics": "Linked List, Divide and Conquer, Heap (Priority Queue), Merge Sort", + "_tags": { "list": ["grind-75"] }, + "readme_description": "You are given an array of `k` linked-lists `lists`, each linked-list is sorted in ascending order.\n\n*Merge all the linked-lists into one sorted linked-list and return it.*", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: lists = [[1,4,5],[1,3,4],[2,6]]\nOutput: [1,1,2,3,4,4,5,6]\n```\n**Explanation:** The linked-lists are:\n```\n[\n 1->4->5,\n 1->3->4,\n 2->6\n]\n```\nmerging them into one sorted linked list:\n```\n1->1->2->3->4->4->5->6\n```" + }, + { "content": "```\nInput: lists = []\nOutput: []\n```" }, + { "content": "```\nInput: lists = [[]]\nOutput: []\n```" } + ] + }, + "readme_constraints": "- `k == lists.length`\n- `0 <= k <= 10^4`\n- `0 <= lists[i].length <= 500`\n- `-10^4 <= lists[i][j] <= 10^4`\n- `lists[i]` is sorted in ascending order.\n- The sum of `lists[i].length` will not exceed `10^4`.", + "readme_additional": "", + "helpers_imports": "from leetcode_py import ListNode", + "helpers_content": "", + "helpers_run_name": "merge_k_lists", + "helpers_run_signature": "(solution_class: type, lists_data: list[list[int]])", + "helpers_run_body": " lists = [ListNode[int].from_list(lst) for lst in lists_data]\n implementation = solution_class()\n return implementation.merge_k_lists(lists)", + "helpers_assert_name": "merge_k_lists", + "helpers_assert_signature": "(result: ListNode[int] | None, expected_data: list[int]) -> bool", + "helpers_assert_body": " expected = ListNode[int].from_list(expected_data)\n assert result == expected\n return True", + "solution_imports": "from leetcode_py import ListNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_merge_k_lists, run_merge_k_lists\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MergeKSortedLists", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "merge_k_lists", + "signature": "(self, lists: list[ListNode[int] | None]) -> ListNode[int] | None", + "body": " # TODO: Implement merge_k_lists\n return None" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_merge_k_lists", + "signature": "(self, lists_data: list[list[int]], expected_data: list[int])", + "parametrize": "lists_data, expected_data", + "test_cases": "[([[1, 4, 5], [1, 3, 4], [2, 6]], [1, 1, 2, 3, 4, 4, 5, 6]), ([], []), ([[]], []), ([[1]], [1]), ([[1, 2], [3, 4]], [1, 2, 3, 4]), ([[5], [1, 3], [2, 4, 6]], [1, 2, 3, 4, 5, 6]), ([[-1, 0, 1], [-2, 2]], [-2, -1, 0, 1, 2]), ([[1, 1, 1], [2, 2, 2]], [1, 1, 1, 2, 2, 2]), ([[], [1], []], [1])]", + "body": " result = run_merge_k_lists(Solution, lists_data)\n assert_merge_k_lists(result, expected_data)" + } + ] + }, + "playground_imports": "from helpers import run_merge_k_lists, assert_merge_k_lists\nfrom solution import Solution\nfrom leetcode_py import ListNode", + "playground_setup": "# Example test case\nlists_data = [[1, 4, 5], [1, 3, 4], [2, 6]]\nexpected_data = [1, 1, 2, 3, 4, 4, 5, 6]", + "playground_run": "result = run_merge_k_lists(Solution, lists_data)\nListNode[int].to_list(result) if result else []", + "playground_assert": "assert_merge_k_lists(result, expected_data)" +} diff --git a/.templates/leetcode/json/merge_two_sorted_lists.json b/.templates/leetcode/json/merge_two_sorted_lists.json index d9c2b1c..fe7dfb6 100644 --- a/.templates/leetcode/json/merge_two_sorted_lists.json +++ b/.templates/leetcode/json/merge_two_sorted_lists.json @@ -51,13 +51,13 @@ "name": "test_merge_two_lists", "signature": "(self, list1_vals: list[int], list2_vals: list[int], expected_vals: list[int])", "parametrize": "list1_vals, list2_vals, expected_vals", - "test_cases": "[([1, 2, 4], [1, 3, 4], [1, 1, 2, 3, 4, 4]), ([], [], []), ([], [0], [0]), ([0], [], [0]), ([1], [2], [1, 2]), ([2], [1], [1, 2]), ([1, 1, 1], [2, 2, 2], [1, 1, 1, 2, 2, 2]), ([1, 3, 5], [2, 4, 6], [1, 2, 3, 4, 5, 6]), ([-10, -5, 0], [-8, -3, 1], [-10, -8, -5, -3, 0, 1]), ([5], [1, 2, 3, 4], [1, 2, 3, 4, 5]), ([1, 2, 3, 4], [5], [1, 2, 3, 4, 5])]", + "test_cases": "[([1, 2, 4], [1, 3, 4], [1, 1, 2, 3, 4, 4]), ([], [], []), ([], [0], [0]), ([1], [2], [1, 2]), ([2], [1], [1, 2]), ([1, 3, 5], [2, 4, 6], [1, 2, 3, 4, 5, 6]), ([1, 1, 1], [2, 2, 2], [1, 1, 1, 2, 2, 2])]", "body": " result = run_merge_two_lists(Solution, list1_vals, list2_vals)\n assert_merge_two_lists(result, expected_vals)" } ] }, - "playground_imports": "from helpers import run_merge_two_lists, assert_merge_two_lists\nfrom solution import Solution", + "playground_imports": "from helpers import run_merge_two_lists, assert_merge_two_lists\nfrom solution import Solution\nfrom leetcode_py import ListNode", "playground_setup": "# Example test case\nlist1_vals = [1, 2, 4]\nlist2_vals = [1, 3, 4]\nexpected_vals = [1, 1, 2, 3, 4, 4]", - "playground_run": "result = run_merge_two_lists(Solution, list1_vals, list2_vals)\nresult", + "playground_run": "result = run_merge_two_lists(Solution, list1_vals, list2_vals)\nListNode[int].to_list(result) if result else []", "playground_assert": "assert_merge_two_lists(result, expected_vals)" } diff --git a/.templates/leetcode/json/middle_of_the_linked_list.json b/.templates/leetcode/json/middle_of_the_linked_list.json new file mode 100644 index 0000000..9e6b152 --- /dev/null +++ b/.templates/leetcode/json/middle_of_the_linked_list.json @@ -0,0 +1,64 @@ +{ + "problem_name": "middle_of_the_linked_list", + "solution_class_name": "Solution", + "problem_number": "876", + "problem_title": "Middle of the Linked List", + "difficulty": "Easy", + "topics": "Linked List, Two Pointers", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given the `head` of a singly linked list, return *the middle node of the linked list*.\n\nIf there are two middle nodes, return **the second middle** node.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2021/07/23/lc-midlist1.jpg)\n\n```\nInput: head = [1,2,3,4,5]\nOutput: [3,4,5]\n```\n**Explanation:** The middle node of the list is node 3." + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2021/07/23/lc-midlist2.jpg)\n\n```\nInput: head = [1,2,3,4,5,6]\nOutput: [4,5,6]\n```\n**Explanation:** Since the list has two middle nodes with values 3 and 4, we return the second one." + } + ] + }, + "readme_constraints": "- The number of nodes in the list is in the range `[1, 100]`.\n- `1 <= Node.val <= 100`", + "readme_additional": "", + "helpers_imports": "from leetcode_py import ListNode", + "helpers_content": "", + "helpers_run_name": "middle_node", + "helpers_run_signature": "(solution_class: type, head_list: list[int])", + "helpers_run_body": " head = ListNode[int].from_list(head_list)\n implementation = solution_class()\n return implementation.middle_node(head)", + "helpers_assert_name": "middle_node", + "helpers_assert_signature": "(result: ListNode[int] | None, expected_list: list[int]) -> bool", + "helpers_assert_body": " expected = ListNode[int].from_list(expected_list)\n assert result == expected\n return True", + "solution_imports": "from leetcode_py import ListNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_middle_node, run_middle_node\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MiddleOfTheLinkedList", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "middle_node", + "signature": "(self, head: ListNode[int] | None) -> ListNode[int] | None", + "body": " # TODO: Implement middle_node\n return None" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_middle_node", + "signature": "(self, head_list: list[int], expected_list: list[int])", + "parametrize": "head_list, expected_list", + "test_cases": "[([1, 2, 3, 4, 5], [3, 4, 5]), ([1, 2, 3, 4, 5, 6], [4, 5, 6]), ([1], [1]), ([1, 2], [2]), ([1, 2, 3], [2, 3]), ([1, 2, 3, 4], [3, 4]), ([10, 20, 30, 40, 50, 60, 70], [40, 50, 60, 70])]", + "body": " result = run_middle_node(Solution, head_list)\n assert_middle_node(result, expected_list)" + } + ] + }, + "playground_imports": "from helpers import run_middle_node, assert_middle_node\nfrom solution import Solution\nfrom leetcode_py import ListNode", + "playground_setup": "# Example test case\nhead_list = [1, 2, 3, 4, 5]\nexpected_list = [3, 4, 5]", + "playground_run": "result = run_middle_node(Solution, head_list)\nListNode[int].to_list(result) if result else []", + "playground_assert": "assert_middle_node(result, expected_list)" +} diff --git a/.templates/leetcode/json/min_stack.json b/.templates/leetcode/json/min_stack.json new file mode 100644 index 0000000..ad8210a --- /dev/null +++ b/.templates/leetcode/json/min_stack.json @@ -0,0 +1,79 @@ +{ + "problem_name": "min_stack", + "solution_class_name": "MinStack", + "problem_number": "155", + "problem_title": "Min Stack", + "difficulty": "Medium", + "topics": "Stack, Design", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.\n\nImplement the `MinStack` class:\n\n- `MinStack()` initializes the stack object.\n- `void push(int val)` pushes the element `val` onto the stack.\n- `void pop()` removes the element on the top of the stack.\n- `int top()` gets the top element of the stack.\n- `int getMin()` retrieves the minimum element in the stack.\n\nYou must implement a solution with `O(1)` time complexity for each function.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput\n[\"MinStack\",\"push\",\"push\",\"push\",\"getMin\",\"pop\",\"top\",\"getMin\"]\n[[],[-2],[0],[-3],[],[],[],[]]\n\nOutput\n[null,null,null,null,-3,null,0,-2]\n```\n**Explanation:**\n```\nMinStack minStack = new MinStack();\nminStack.push(-2);\nminStack.push(0);\nminStack.push(-3);\nminStack.getMin(); // return -3\nminStack.pop();\nminStack.top(); // return 0\nminStack.getMin(); // return -2\n```" + } + ] + }, + "readme_constraints": "- `-2^31 <= val <= 2^31 - 1`\n- Methods `pop`, `top` and `getMin` operations will always be called on **non-empty** stacks.\n- At most `3 * 10^4` calls will be made to `push`, `pop`, `top`, and `getMin`.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "min_stack_operations", + "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[int]])", + "helpers_run_body": " stack: solution_class | None = None\n results: list[int | None] = []\n for i, op in enumerate(operations):\n if op == 'MinStack':\n stack = solution_class()\n results.append(None)\n elif op == 'push' and stack is not None:\n stack.push(inputs[i][0])\n results.append(None)\n elif op == 'pop' and stack is not None:\n stack.pop()\n results.append(None)\n elif op == 'top' and stack is not None:\n results.append(stack.top())\n elif op == 'getMin' and stack is not None:\n results.append(stack.get_min())\n return results", + "helpers_assert_name": "min_stack_operations", + "helpers_assert_signature": "(result: list[int | None], expected: list[int | None]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_min_stack_operations, run_min_stack_operations\nfrom .solution import MinStack", + "test_content": "", + "test_class_name": "TestMinStack", + "test_class_content": "", + "_solution_methods": { + "list": [ + { + "name": "__init__", + "signature": "(self) -> None", + "body": " # TODO: Initialize MinStack\n pass" + }, + { + "name": "push", + "signature": "(self, val: int) -> None", + "body": " # TODO: Implement push\n pass" + }, + { + "name": "pop", + "signature": "(self) -> None", + "body": " # TODO: Implement pop\n pass" + }, + { + "name": "top", + "signature": "(self) -> int", + "body": " # TODO: Implement top\n return 0" + }, + { + "name": "get_min", + "signature": "(self) -> int", + "body": " # TODO: Implement get_min\n return 0" + } + ] + }, + "_test_helper_methods": { "list": [] }, + "_test_methods": { + "list": [ + { + "name": "test_min_stack", + "signature": "(self, operations: list[str], inputs: list[list[int]], expected: list[int | None])", + "parametrize": "operations, inputs, expected", + "test_cases": "[(['MinStack', 'push', 'push', 'push', 'getMin', 'pop', 'top', 'getMin'], [[], [-2], [0], [-3], [], [], [], []], [None, None, None, None, -3, None, 0, -2]), (['MinStack', 'push', 'top', 'getMin', 'pop'], [[], [5], [], [], []], [None, None, 5, 5, None]), (['MinStack', 'push', 'push', 'push', 'getMin', 'pop', 'getMin', 'pop', 'getMin'], [[], [1], [1], [2], [], [], [], [], []], [None, None, None, None, 1, None, 1, None, 1])]", + "body": " result = run_min_stack_operations(MinStack, operations, inputs)\n assert_min_stack_operations(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_min_stack_operations, assert_min_stack_operations\nfrom solution import MinStack", + "playground_setup": "# Example test case\noperations = ['MinStack', 'push', 'push', 'push', 'getMin', 'pop', 'top', 'getMin']\ninputs = [[], [-2], [0], [-3], [], [], [], []]\nexpected = [None, None, None, None, -3, None, 0, -2]", + "playground_run": "result = run_min_stack_operations(MinStack, operations, inputs)\nresult", + "playground_assert": "assert_min_stack_operations(result, expected)" +} diff --git a/.templates/leetcode/json/minimum_height_trees.json b/.templates/leetcode/json/minimum_height_trees.json new file mode 100644 index 0000000..bc83ead --- /dev/null +++ b/.templates/leetcode/json/minimum_height_trees.json @@ -0,0 +1,64 @@ +{ + "problem_name": "minimum_height_trees", + "solution_class_name": "Solution", + "problem_number": "310", + "problem_title": "Minimum Height Trees", + "difficulty": "Medium", + "topics": "Depth-First Search, Breadth-First Search, Graph, Topological Sort", + "_tags": { "list": ["grind-75"] }, + "readme_description": "A tree is an undirected graph in which any two vertices are connected by *exactly* one path. In other words, any connected graph without simple cycles is a tree.\n\nGiven a tree of `n` nodes labelled from `0` to `n - 1`, and an array of `n - 1` `edges` where `edges[i] = [ai, bi]` indicates that there is an undirected edge between the two nodes `ai` and `bi` in the tree, you can choose any node of the tree as the root. When you select a node `x` as the root, the result tree has height `h`. Among all possible rooted trees, those with minimum height (i.e. `min(h)`) are called **minimum height trees** (MHTs).\n\nReturn *a list of all **MHTs'** root labels*. You can return the answer in **any order**.\n\nThe **height** of a rooted tree is the number of edges on the longest downward path between the root and a leaf.", + "_readme_examples": { + "list": [ + { + "content": "\"\"\n\n```\nInput: n = 4, edges = [[1,0],[1,2],[1,3]]\nOutput: [1]\nExplanation: As shown, the height of the tree is 1 when the root is the node with label 1 which is the only MHT.\n```" + }, + { + "content": "\"\"\n\n```\nInput: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]\nOutput: [3,4]\n```" + } + ] + }, + "readme_constraints": "- `1 <= n <= 2 * 10^4`\n- `edges.length == n - 1`\n- `0 <= ai, bi < n`\n- `ai != bi`\n- All the pairs `(ai, bi)` are distinct.\n- The given input is **guaranteed** to be a tree and there will be **no repeated** edges.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "find_min_height_trees", + "helpers_run_signature": "(solution_class: type, n: int, edges: list[list[int]])", + "helpers_run_body": " implementation = solution_class()\n return implementation.find_min_height_trees(n, edges)", + "helpers_assert_name": "find_min_height_trees", + "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool", + "helpers_assert_body": " assert sorted(result) == sorted(expected)\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_find_min_height_trees, run_find_min_height_trees\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MinimumHeightTrees", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "find_min_height_trees", + "signature": "(self, n: int, edges: list[list[int]]) -> list[int]", + "body": " # TODO: Implement find_min_height_trees\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_find_min_height_trees", + "signature": "(self, n: int, edges: list[list[int]], expected: list[int])", + "parametrize": "n, edges, expected", + "test_cases": "[(4, [[1,0],[1,2],[1,3]], [1]), (6, [[3,0],[3,1],[3,2],[3,4],[5,4]], [3,4]), (1, [], [0]), (2, [[0,1]], [0,1]), (3, [[0,1],[1,2]], [1])]", + "body": " result = run_find_min_height_trees(Solution, n, edges)\n assert_find_min_height_trees(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_find_min_height_trees, assert_find_min_height_trees\nfrom solution import Solution", + "playground_setup": "# Example test case\nn = 4\nedges = [[1,0],[1,2],[1,3]]\nexpected = [1]", + "playground_run": "result = run_find_min_height_trees(Solution, n, edges)\nresult", + "playground_assert": "assert_find_min_height_trees(result, expected)" +} diff --git a/.templates/leetcode/json/minimum_window_substring.json b/.templates/leetcode/json/minimum_window_substring.json new file mode 100644 index 0000000..6a146ad --- /dev/null +++ b/.templates/leetcode/json/minimum_window_substring.json @@ -0,0 +1,67 @@ +{ + "problem_name": "minimum_window_substring", + "solution_class_name": "Solution", + "problem_number": "76", + "problem_title": "Minimum Window Substring", + "difficulty": "Hard", + "topics": "Hash Table, String, Sliding Window", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given two strings `s` and `t` of lengths `m` and `n` respectively, return the **minimum window substring** of `s` such that every character in `t` (including duplicates) is included in the window. If there is no such substring, return the empty string `\"\"`.\n\nThe testcases will be generated such that the answer is unique.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: s = \"ADOBECODEBANC\", t = \"ABC\"\nOutput: \"BANC\"\n```\n**Explanation:** The minimum window substring \"BANC\" includes 'A', 'B', and 'C' from string t." + }, + { + "content": "```\nInput: s = \"a\", t = \"a\"\nOutput: \"a\"\n```\n**Explanation:** The entire string s is the minimum window." + }, + { + "content": "```\nInput: s = \"a\", t = \"aa\"\nOutput: \"\"\n```\n**Explanation:** Both 'a's from t must be included in the window. Since the largest window of s only has one 'a', return empty string." + } + ] + }, + "readme_constraints": "- `m == s.length`\n- `n == t.length`\n- `1 <= m, n <= 10^5`\n- `s` and `t` consist of uppercase and lowercase English letters.", + "readme_additional": "**Follow up:** Could you find an algorithm that runs in `O(m + n)` time?", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "min_window", + "helpers_run_signature": "(solution_class: type, s: str, t: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.min_window(s, t)", + "helpers_assert_name": "min_window", + "helpers_assert_signature": "(result: str, expected: str) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_min_window, run_min_window\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MinimumWindowSubstring", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "min_window", + "signature": "(self, s: str, t: str) -> str", + "body": " # TODO: Implement min_window\n return \"\"" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_min_window", + "signature": "(self, s: str, t: str, expected: str)", + "parametrize": "s, t, expected", + "test_cases": "[(\"ADOBECODEBANC\", \"ABC\", \"BANC\"), (\"a\", \"a\", \"a\"), (\"a\", \"aa\", \"\"), (\"ab\", \"b\", \"b\"), (\"abc\", \"cba\", \"abc\")]", + "body": " result = run_min_window(Solution, s, t)\n assert_min_window(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_min_window, assert_min_window\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = 'ADOBECODEBANC'\nt = 'ABC'\nexpected = 'BANC'", + "playground_run": "result = run_min_window(Solution, s, t)\nresult", + "playground_assert": "assert_min_window(result, expected)" +} diff --git a/.templates/leetcode/json/number_of_islands.json b/.templates/leetcode/json/number_of_islands.json new file mode 100644 index 0000000..d1cf6ef --- /dev/null +++ b/.templates/leetcode/json/number_of_islands.json @@ -0,0 +1,64 @@ +{ + "problem_name": "number_of_islands", + "solution_class_name": "Solution", + "problem_number": "200", + "problem_title": "Number of Islands", + "difficulty": "Medium", + "topics": "Array, Depth-First Search, Breadth-First Search, Union Find, Matrix", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an `m x n` 2D binary grid `grid` which represents a map of `'1'`s (land) and `'0'`s (water), return *the number of islands*.\n\nAn **island** is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: grid = [\n [\"1\",\"1\",\"1\",\"1\",\"0\"],\n [\"1\",\"1\",\"0\",\"1\",\"0\"],\n [\"1\",\"1\",\"0\",\"0\",\"0\"],\n [\"0\",\"0\",\"0\",\"0\",\"0\"]\n]\nOutput: 1\n```" + }, + { + "content": "```\nInput: grid = [\n [\"1\",\"1\",\"0\",\"0\",\"0\"],\n [\"1\",\"1\",\"0\",\"0\",\"0\"],\n [\"0\",\"0\",\"1\",\"0\",\"0\"],\n [\"0\",\"0\",\"0\",\"1\",\"1\"]\n]\nOutput: 3\n```" + } + ] + }, + "readme_constraints": "- `m == grid.length`\n- `n == grid[i].length`\n- `1 <= m, n <= 300`\n- `grid[i][j]` is `'0'` or `'1'`.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "num_islands", + "helpers_run_signature": "(solution_class: type, grid: list[list[str]])", + "helpers_run_body": " implementation = solution_class()\n return implementation.num_islands(grid)", + "helpers_assert_name": "num_islands", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_num_islands, run_num_islands\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "NumberOfIslands", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "num_islands", + "signature": "(self, grid: list[list[str]]) -> int", + "body": " # TODO: Implement num_islands\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_num_islands", + "signature": "(self, grid: list[list[str]], expected: int)", + "parametrize": "grid, expected", + "test_cases": "[([['1','1','1','1','0'],['1','1','0','1','0'],['1','1','0','0','0'],['0','0','0','0','0']], 1), ([['1','1','0','0','0'],['1','1','0','0','0'],['0','0','1','0','0'],['0','0','0','1','1']], 3), ([['1']], 1), ([['0']], 0), ([['1','0','1'],['0','1','0'],['1','0','1']], 5)]", + "body": " result = run_num_islands(Solution, grid)\n assert_num_islands(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_num_islands, assert_num_islands\nfrom solution import Solution", + "playground_setup": "# Example test case\ngrid = [\n ['1','1','1','1','0'],\n ['1','1','0','1','0'],\n ['1','1','0','0','0'],\n ['0','0','0','0','0']\n]\nexpected = 1", + "playground_run": "result = run_num_islands(Solution, grid)\nresult", + "playground_assert": "assert_num_islands(result, expected)" +} diff --git a/.templates/leetcode/json/partition_equal_subset_sum.json b/.templates/leetcode/json/partition_equal_subset_sum.json new file mode 100644 index 0000000..da63474 --- /dev/null +++ b/.templates/leetcode/json/partition_equal_subset_sum.json @@ -0,0 +1,64 @@ +{ + "problem_name": "partition_equal_subset_sum", + "solution_class_name": "Solution", + "problem_number": "416", + "problem_title": "Partition Equal Subset Sum", + "difficulty": "Medium", + "topics": "Array, Dynamic Programming", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an integer array `nums`, return `true` if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or `false` otherwise.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums = [1,5,11,5]\nOutput: true\n```\n**Explanation:** The array can be partitioned as [1, 5, 5] and [11]." + }, + { + "content": "```\nInput: nums = [1,2,3,5]\nOutput: false\n```\n**Explanation:** The array cannot be partitioned into equal sum subsets." + } + ] + }, + "readme_constraints": "- 1 <= nums.length <= 200\n- 1 <= nums[i] <= 100", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "can_partition", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.can_partition(nums)", + "helpers_assert_name": "can_partition", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_can_partition, run_can_partition\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "PartitionEqualSubsetSum", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "can_partition", + "signature": "(self, nums: list[int]) -> bool", + "body": " # TODO: Implement can_partition\n return False" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_can_partition", + "signature": "(self, nums: list[int], expected: bool)", + "parametrize": "nums, expected", + "test_cases": "[([1, 5, 11, 5], True), ([1, 2, 3, 5], False), ([1, 1], True), ([1], False), ([2, 2, 1, 1], True), ([100], False), ([1, 2, 5], False)]", + "body": " result = run_can_partition(Solution, nums)\n assert_can_partition(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_can_partition, assert_can_partition\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [1, 5, 11, 5]\nexpected = True", + "playground_run": "result = run_can_partition(Solution, nums)\nresult", + "playground_assert": "assert_can_partition(result, expected)" +} diff --git a/leetcode/maximum_depth_of_binary_tree/solution.py b/leetcode/maximum_depth_of_binary_tree/solution.py index 56abe42..d20d39d 100644 --- a/leetcode/maximum_depth_of_binary_tree/solution.py +++ b/leetcode/maximum_depth_of_binary_tree/solution.py @@ -3,11 +3,9 @@ class Solution: - # Time: O(?) - # Space: O(?) + # Time: O(n) + # Space: O(h) def max_depth(self, root: TreeNode[int] | None) -> int: - # Time: O(n) - # Space: O(h) if not root: return 0 diff --git a/leetcode/maximum_subarray/README.md b/leetcode/maximum_subarray/README.md new file mode 100644 index 0000000..e4d7afa --- /dev/null +++ b/leetcode/maximum_subarray/README.md @@ -0,0 +1,47 @@ +# Maximum Subarray + +**Difficulty:** Medium +**Topics:** Array, Divide and Conquer, Dynamic Programming +**Tags:** grind-75 + +**LeetCode:** [Problem 53](https://leetcode.com/problems/maximum-subarray/description/) + +## Problem Description + +Given an integer array `nums`, find the subarray with the largest sum, and return its sum. + +## Examples + +### Example 1: + +``` +Input: nums = [-2,1,-3,4,-1,2,1,-5,4] +Output: 6 +``` + +**Explanation:** The subarray [4,-1,2,1] has the largest sum 6. + +### Example 2: + +``` +Input: nums = [1] +Output: 1 +``` + +**Explanation:** The subarray [1] has the largest sum 1. + +### Example 3: + +``` +Input: nums = [5,4,-1,7,8] +Output: 23 +``` + +**Explanation:** The subarray [5,4,-1,7,8] has the largest sum 23. + +## Constraints + +- `1 <= nums.length <= 10^5` +- `-10^4 <= nums[i] <= 10^4` + +**Follow up:** If you have figured out the `O(n)` solution, try coding another solution using the **divide and conquer** approach, which is more subtle. diff --git a/leetcode/maximum_subarray/__init__.py b/leetcode/maximum_subarray/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/maximum_subarray/helpers.py b/leetcode/maximum_subarray/helpers.py new file mode 100644 index 0000000..e455a38 --- /dev/null +++ b/leetcode/maximum_subarray/helpers.py @@ -0,0 +1,8 @@ +def run_max_sub_array(solution_class: type, nums: list[int]): + implementation = solution_class() + return implementation.max_sub_array(nums) + + +def assert_max_sub_array(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/maximum_subarray/playground.ipynb b/leetcode/maximum_subarray/playground.ipynb new file mode 100644 index 0000000..bd4f64e --- /dev/null +++ b/leetcode/maximum_subarray/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_max_sub_array, run_max_sub_array\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]\n", + "expected = 6" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_max_sub_array(Solution, nums)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_max_sub_array(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/maximum_subarray/solution.py b/leetcode/maximum_subarray/solution.py new file mode 100644 index 0000000..0458a8c --- /dev/null +++ b/leetcode/maximum_subarray/solution.py @@ -0,0 +1,12 @@ +class Solution: + + # Time: O(n) + # Space: O(1) + def max_sub_array(self, nums: list[int]) -> int: + max_sum = current_sum = nums[0] + + for i in range(1, len(nums)): + current_sum = max(nums[i], current_sum + nums[i]) + max_sum = max(max_sum, current_sum) + + return max_sum diff --git a/leetcode/maximum_subarray/test_solution.py b/leetcode/maximum_subarray/test_solution.py new file mode 100644 index 0000000..1690628 --- /dev/null +++ b/leetcode/maximum_subarray/test_solution.py @@ -0,0 +1,28 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_max_sub_array, run_max_sub_array +from .solution import Solution + + +class TestMaximumSubarray: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([-2, 1, -3, 4, -1, 2, 1, -5, 4], 6), + ([1], 1), + ([5, 4, -1, 7, 8], 23), + ([-1], -1), + ([-2, -1], -1), + ([1, 2, 3, 4, 5], 15), + ([-5, -2, -8, -1], -1), + ], + ) + def test_max_sub_array(self, nums: list[int], expected: int): + result = run_max_sub_array(Solution, nums) + assert_max_sub_array(result, expected) diff --git a/leetcode/merge_intervals/README.md b/leetcode/merge_intervals/README.md new file mode 100644 index 0000000..0f648cf --- /dev/null +++ b/leetcode/merge_intervals/README.md @@ -0,0 +1,46 @@ +# Merge Intervals + +**Difficulty:** Medium +**Topics:** Array, Sorting +**Tags:** grind-75 + +**LeetCode:** [Problem 56](https://leetcode.com/problems/merge-intervals/description/) + +## Problem Description + +Given an array of `intervals` where `intervals[i] = [starti, endi]`, merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input. + +## Examples + +### Example 1: + +``` +Input: intervals = [[1,3],[2,6],[8,10],[15,18]] +Output: [[1,6],[8,10],[15,18]] +``` + +**Explanation:** Since intervals [1,3] and [2,6] overlap, merge them into [1,6]. + +### Example 2: + +``` +Input: intervals = [[1,4],[4,5]] +Output: [[1,5]] +``` + +**Explanation:** Intervals [1,4] and [4,5] are considered overlapping. + +### Example 3: + +``` +Input: intervals = [[4,7],[1,4]] +Output: [[1,7]] +``` + +**Explanation:** Intervals [1,4] and [4,7] are considered overlapping. + +## Constraints + +- `1 <= intervals.length <= 10^4` +- `intervals[i].length == 2` +- `0 <= starti <= endi <= 10^4` diff --git a/leetcode/merge_intervals/__init__.py b/leetcode/merge_intervals/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/merge_intervals/helpers.py b/leetcode/merge_intervals/helpers.py new file mode 100644 index 0000000..a8d5049 --- /dev/null +++ b/leetcode/merge_intervals/helpers.py @@ -0,0 +1,8 @@ +def run_merge(solution_class: type, intervals: list[list[int]]): + implementation = solution_class() + return implementation.merge(intervals) + + +def assert_merge(result: list[list[int]], expected: list[list[int]]) -> bool: + assert result == expected + return True diff --git a/leetcode/merge_intervals/playground.ipynb b/leetcode/merge_intervals/playground.ipynb new file mode 100644 index 0000000..f5c7c1e --- /dev/null +++ b/leetcode/merge_intervals/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_merge, run_merge\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "intervals = [[1, 3], [2, 6], [8, 10], [15, 18]]\n", + "expected = [[1, 6], [8, 10], [15, 18]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_merge(Solution, intervals)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_merge(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/merge_intervals/solution.py b/leetcode/merge_intervals/solution.py new file mode 100644 index 0000000..a1ea4dd --- /dev/null +++ b/leetcode/merge_intervals/solution.py @@ -0,0 +1,15 @@ +class Solution: + + # Time: O(n log n) + # Space: O(1) + def merge(self, intervals: list[list[int]]) -> list[list[int]]: + intervals.sort() + merged = [intervals[0]] + + for start, end in intervals[1:]: + if start <= merged[-1][1]: + merged[-1][1] = max(merged[-1][1], end) + else: + merged.append([start, end]) + + return merged diff --git a/leetcode/merge_intervals/test_solution.py b/leetcode/merge_intervals/test_solution.py new file mode 100644 index 0000000..045bc7f --- /dev/null +++ b/leetcode/merge_intervals/test_solution.py @@ -0,0 +1,27 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_merge, run_merge +from .solution import Solution + + +class TestMergeIntervals: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "intervals, expected", + [ + ([[1, 3], [2, 6], [8, 10], [15, 18]], [[1, 6], [8, 10], [15, 18]]), + ([[1, 4], [4, 5]], [[1, 5]]), + ([[4, 7], [1, 4]], [[1, 7]]), + ([[1, 3]], [[1, 3]]), + ([[1, 4], [2, 3]], [[1, 4]]), + ([[1, 2], [3, 4], [5, 6]], [[1, 2], [3, 4], [5, 6]]), + ], + ) + def test_merge(self, intervals: list[list[int]], expected: list[list[int]]): + result = run_merge(Solution, intervals) + assert_merge(result, expected) diff --git a/leetcode/merge_k_sorted_lists/README.md b/leetcode/merge_k_sorted_lists/README.md new file mode 100644 index 0000000..7912c38 --- /dev/null +++ b/leetcode/merge_k_sorted_lists/README.md @@ -0,0 +1,61 @@ +# Merge k Sorted Lists + +**Difficulty:** Hard +**Topics:** Linked List, Divide and Conquer, Heap (Priority Queue), Merge Sort +**Tags:** grind-75 + +**LeetCode:** [Problem 23](https://leetcode.com/problems/merge-k-sorted-lists/description/) + +## Problem Description + +You are given an array of `k` linked-lists `lists`, each linked-list is sorted in ascending order. + +_Merge all the linked-lists into one sorted linked-list and return it._ + +## Examples + +### Example 1: + +``` +Input: lists = [[1,4,5],[1,3,4],[2,6]] +Output: [1,1,2,3,4,4,5,6] +``` + +**Explanation:** The linked-lists are: + +``` +[ + 1->4->5, + 1->3->4, + 2->6 +] +``` + +merging them into one sorted linked list: + +``` +1->1->2->3->4->4->5->6 +``` + +### Example 2: + +``` +Input: lists = [] +Output: [] +``` + +### Example 3: + +``` +Input: lists = [[]] +Output: [] +``` + +## Constraints + +- `k == lists.length` +- `0 <= k <= 10^4` +- `0 <= lists[i].length <= 500` +- `-10^4 <= lists[i][j] <= 10^4` +- `lists[i]` is sorted in ascending order. +- The sum of `lists[i].length` will not exceed `10^4`. diff --git a/leetcode/merge_k_sorted_lists/__init__.py b/leetcode/merge_k_sorted_lists/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/merge_k_sorted_lists/helpers.py b/leetcode/merge_k_sorted_lists/helpers.py new file mode 100644 index 0000000..b76a2b7 --- /dev/null +++ b/leetcode/merge_k_sorted_lists/helpers.py @@ -0,0 +1,13 @@ +from leetcode_py import ListNode + + +def run_merge_k_lists(solution_class: type, lists_data: list[list[int]]): + lists = [ListNode[int].from_list(lst) for lst in lists_data] + implementation = solution_class() + return implementation.merge_k_lists(lists) + + +def assert_merge_k_lists(result: ListNode[int] | None, expected_data: list[int]) -> bool: + expected = ListNode[int].from_list(expected_data) + assert result == expected + return True diff --git a/leetcode/merge_k_sorted_lists/playground.ipynb b/leetcode/merge_k_sorted_lists/playground.ipynb new file mode 100644 index 0000000..44dff96 --- /dev/null +++ b/leetcode/merge_k_sorted_lists/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_merge_k_lists, run_merge_k_lists\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import ListNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "lists_data = [[1, 4, 5], [1, 3, 4], [2, 6]]\n", + "expected_data = [1, 1, 2, 3, 4, 4, 5, 6]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_merge_k_lists(Solution, lists_data)\n", + "ListNode[int].to_list(result) if result else []" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_merge_k_lists(result, expected_data)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/merge_k_sorted_lists/solution.py b/leetcode/merge_k_sorted_lists/solution.py new file mode 100644 index 0000000..7de08d9 --- /dev/null +++ b/leetcode/merge_k_sorted_lists/solution.py @@ -0,0 +1,38 @@ +from leetcode_py import ListNode + + +class Solution: + + # Time: O(n log k) where n is total nodes, k is number of lists + # Space: O(log k) for recursion stack + def merge_k_lists(self, lists: list[ListNode[int] | None]) -> ListNode[int] | None: + if not lists: + return None + return self._divide_conquer(lists, 0, len(lists) - 1) + + def _divide_conquer( + self, lists: list[ListNode[int] | None], left: int, right: int + ) -> ListNode[int] | None: + if left == right: + return lists[left] + + mid = (left + right) // 2 + l1 = self._divide_conquer(lists, left, mid) + l2 = self._divide_conquer(lists, mid + 1, right) + return self._merge_two(l1, l2) + + def _merge_two(self, l1: ListNode[int] | None, l2: ListNode[int] | None) -> ListNode[int] | None: + dummy = ListNode(0) + curr = dummy + + while l1 and l2: + if l1.val <= l2.val: + curr.next = l1 + l1 = l1.next + else: + curr.next = l2 + l2 = l2.next + curr = curr.next + + curr.next = l1 or l2 + return dummy.next diff --git a/leetcode/merge_k_sorted_lists/test_solution.py b/leetcode/merge_k_sorted_lists/test_solution.py new file mode 100644 index 0000000..ea7d200 --- /dev/null +++ b/leetcode/merge_k_sorted_lists/test_solution.py @@ -0,0 +1,30 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_merge_k_lists, run_merge_k_lists +from .solution import Solution + + +class TestMergeKSortedLists: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "lists_data, expected_data", + [ + ([[1, 4, 5], [1, 3, 4], [2, 6]], [1, 1, 2, 3, 4, 4, 5, 6]), + ([], []), + ([[]], []), + ([[1]], [1]), + ([[1, 2], [3, 4]], [1, 2, 3, 4]), + ([[5], [1, 3], [2, 4, 6]], [1, 2, 3, 4, 5, 6]), + ([[-1, 0, 1], [-2, 2]], [-2, -1, 0, 1, 2]), + ([[1, 1, 1], [2, 2, 2]], [1, 1, 1, 2, 2, 2]), + ([[], [1], []], [1]), + ], + ) + def test_merge_k_lists(self, lists_data: list[list[int]], expected_data: list[int]): + result = run_merge_k_lists(Solution, lists_data) + assert_merge_k_lists(result, expected_data) diff --git a/leetcode/merge_two_sorted_lists/playground.ipynb b/leetcode/merge_two_sorted_lists/playground.ipynb index ee8a46d..0ec2f71 100644 --- a/leetcode/merge_two_sorted_lists/playground.ipynb +++ b/leetcode/merge_two_sorted_lists/playground.ipynb @@ -2,18 +2,20 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "imports", "metadata": {}, "outputs": [], "source": [ "from helpers import assert_merge_two_lists, run_merge_two_lists\n", - "from solution import Solution" + "from solution import Solution\n", + "\n", + "from leetcode_py import ListNode" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "setup", "metadata": {}, "outputs": [], @@ -26,123 +28,21 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "run", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_0\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "node_1\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "node_0->node_1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_2\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "node_1->node_2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_3\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "node_2->node_3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_4\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "node_3->node_4\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_5\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "node_4->node_5\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "ListNode([1, 1, 2, 3, 4, 4])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "result = run_merge_two_lists(Solution, list1_vals, list2_vals)\n", - "result" + "ListNode[int].to_list(result) if result else []" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "assert", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "assert_merge_two_lists(result, expected_vals)" ] @@ -162,8 +62,7 @@ "file_extension": ".py", "mimetype": "text/x-python", "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", + "nbconvert_exporter": "python3", "version": "3.13.7" } }, diff --git a/leetcode/merge_two_sorted_lists/solution.py b/leetcode/merge_two_sorted_lists/solution.py index b61d652..5c0535e 100644 --- a/leetcode/merge_two_sorted_lists/solution.py +++ b/leetcode/merge_two_sorted_lists/solution.py @@ -2,12 +2,12 @@ class Solution: + # Time: O(m + n) # Space: O(1) def merge_two_lists( self, list1: ListNode[int] | None, list2: ListNode[int] | None ) -> ListNode[int] | None: - dummy = ListNode(0) current = dummy diff --git a/leetcode/merge_two_sorted_lists/test_solution.py b/leetcode/merge_two_sorted_lists/test_solution.py index 92b63ad..c0deaf5 100644 --- a/leetcode/merge_two_sorted_lists/test_solution.py +++ b/leetcode/merge_two_sorted_lists/test_solution.py @@ -17,14 +17,10 @@ def setup_method(self): ([1, 2, 4], [1, 3, 4], [1, 1, 2, 3, 4, 4]), ([], [], []), ([], [0], [0]), - ([0], [], [0]), ([1], [2], [1, 2]), ([2], [1], [1, 2]), - ([1, 1, 1], [2, 2, 2], [1, 1, 1, 2, 2, 2]), ([1, 3, 5], [2, 4, 6], [1, 2, 3, 4, 5, 6]), - ([-10, -5, 0], [-8, -3, 1], [-10, -8, -5, -3, 0, 1]), - ([5], [1, 2, 3, 4], [1, 2, 3, 4, 5]), - ([1, 2, 3, 4], [5], [1, 2, 3, 4, 5]), + ([1, 1, 1], [2, 2, 2], [1, 1, 1, 2, 2, 2]), ], ) def test_merge_two_lists( diff --git a/leetcode/middle_of_the_linked_list/README.md b/leetcode/middle_of_the_linked_list/README.md new file mode 100644 index 0000000..1f36bf2 --- /dev/null +++ b/leetcode/middle_of_the_linked_list/README.md @@ -0,0 +1,42 @@ +# Middle of the Linked List + +**Difficulty:** Easy +**Topics:** Linked List, Two Pointers +**Tags:** grind-75 + +**LeetCode:** [Problem 876](https://leetcode.com/problems/middle-of-the-linked-list/description/) + +## Problem Description + +Given the `head` of a singly linked list, return _the middle node of the linked list_. + +If there are two middle nodes, return **the second middle** node. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2021/07/23/lc-midlist1.jpg) + +``` +Input: head = [1,2,3,4,5] +Output: [3,4,5] +``` + +**Explanation:** The middle node of the list is node 3. + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2021/07/23/lc-midlist2.jpg) + +``` +Input: head = [1,2,3,4,5,6] +Output: [4,5,6] +``` + +**Explanation:** Since the list has two middle nodes with values 3 and 4, we return the second one. + +## Constraints + +- The number of nodes in the list is in the range `[1, 100]`. +- `1 <= Node.val <= 100` diff --git a/leetcode/middle_of_the_linked_list/__init__.py b/leetcode/middle_of_the_linked_list/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/middle_of_the_linked_list/helpers.py b/leetcode/middle_of_the_linked_list/helpers.py new file mode 100644 index 0000000..f630738 --- /dev/null +++ b/leetcode/middle_of_the_linked_list/helpers.py @@ -0,0 +1,13 @@ +from leetcode_py import ListNode + + +def run_middle_node(solution_class: type, head_list: list[int]): + head = ListNode[int].from_list(head_list) + implementation = solution_class() + return implementation.middle_node(head) + + +def assert_middle_node(result: ListNode[int] | None, expected_list: list[int]) -> bool: + expected = ListNode[int].from_list(expected_list) + assert result == expected + return True diff --git a/leetcode/middle_of_the_linked_list/playground.ipynb b/leetcode/middle_of_the_linked_list/playground.ipynb new file mode 100644 index 0000000..d078f51 --- /dev/null +++ b/leetcode/middle_of_the_linked_list/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_middle_node, run_middle_node\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import ListNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "head_list = [1, 2, 3, 4, 5]\n", + "expected_list = [3, 4, 5]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_middle_node(Solution, head_list)\n", + "ListNode[int].to_list(result) if result else []" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_middle_node(result, expected_list)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/middle_of_the_linked_list/solution.py b/leetcode/middle_of_the_linked_list/solution.py new file mode 100644 index 0000000..eeb11fd --- /dev/null +++ b/leetcode/middle_of_the_linked_list/solution.py @@ -0,0 +1,14 @@ +from leetcode_py import ListNode + + +class Solution: + + # Time: O(n) + # Space: O(1) + def middle_node(self, head: ListNode[int] | None) -> ListNode[int] | None: + slow = fast = head + while fast and fast.next: + assert slow is not None + slow = slow.next + fast = fast.next.next + return slow diff --git a/leetcode/middle_of_the_linked_list/test_solution.py b/leetcode/middle_of_the_linked_list/test_solution.py new file mode 100644 index 0000000..5bf4440 --- /dev/null +++ b/leetcode/middle_of_the_linked_list/test_solution.py @@ -0,0 +1,28 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_middle_node, run_middle_node +from .solution import Solution + + +class TestMiddleOfTheLinkedList: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "head_list, expected_list", + [ + ([1, 2, 3, 4, 5], [3, 4, 5]), + ([1, 2, 3, 4, 5, 6], [4, 5, 6]), + ([1], [1]), + ([1, 2], [2]), + ([1, 2, 3], [2, 3]), + ([1, 2, 3, 4], [3, 4]), + ([10, 20, 30, 40, 50, 60, 70], [40, 50, 60, 70]), + ], + ) + def test_middle_node(self, head_list: list[int], expected_list: list[int]): + result = run_middle_node(Solution, head_list) + assert_middle_node(result, expected_list) diff --git a/leetcode/min_stack/README.md b/leetcode/min_stack/README.md new file mode 100644 index 0000000..46edefb --- /dev/null +++ b/leetcode/min_stack/README.md @@ -0,0 +1,53 @@ +# Min Stack + +**Difficulty:** Medium +**Topics:** Stack, Design +**Tags:** grind-75 + +**LeetCode:** [Problem 155](https://leetcode.com/problems/min-stack/description/) + +## Problem Description + +Design a stack that supports push, pop, top, and retrieving the minimum element in constant time. + +Implement the `MinStack` class: + +- `MinStack()` initializes the stack object. +- `void push(int val)` pushes the element `val` onto the stack. +- `void pop()` removes the element on the top of the stack. +- `int top()` gets the top element of the stack. +- `int getMin()` retrieves the minimum element in the stack. + +You must implement a solution with `O(1)` time complexity for each function. + +## Examples + +### Example 1: + +``` +Input +["MinStack","push","push","push","getMin","pop","top","getMin"] +[[],[-2],[0],[-3],[],[],[],[]] + +Output +[null,null,null,null,-3,null,0,-2] +``` + +**Explanation:** + +``` +MinStack minStack = new MinStack(); +minStack.push(-2); +minStack.push(0); +minStack.push(-3); +minStack.getMin(); // return -3 +minStack.pop(); +minStack.top(); // return 0 +minStack.getMin(); // return -2 +``` + +## Constraints + +- `-2^31 <= val <= 2^31 - 1` +- Methods `pop`, `top` and `getMin` operations will always be called on **non-empty** stacks. +- At most `3 * 10^4` calls will be made to `push`, `pop`, `top`, and `getMin`. diff --git a/leetcode/min_stack/__init__.py b/leetcode/min_stack/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/min_stack/helpers.py b/leetcode/min_stack/helpers.py new file mode 100644 index 0000000..87bd4c6 --- /dev/null +++ b/leetcode/min_stack/helpers.py @@ -0,0 +1,23 @@ +def run_min_stack_operations(solution_class: type, operations: list[str], inputs: list[list[int]]): + stack = None + results: list[int | None] = [] + for i, op in enumerate(operations): + if op == "MinStack": + stack = solution_class() + results.append(None) + elif op == "push" and stack is not None: + stack.push(inputs[i][0]) + results.append(None) + elif op == "pop" and stack is not None: + stack.pop() + results.append(None) + elif op == "top" and stack is not None: + results.append(stack.top()) + elif op == "getMin" and stack is not None: + results.append(stack.get_min()) + return results + + +def assert_min_stack_operations(result: list[int | None], expected: list[int | None]) -> bool: + assert result == expected + return True diff --git a/leetcode/min_stack/playground.ipynb b/leetcode/min_stack/playground.ipynb new file mode 100644 index 0000000..0d24cc7 --- /dev/null +++ b/leetcode/min_stack/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_min_stack_operations, run_min_stack_operations\n", + "from solution import MinStack" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "operations = [\"MinStack\", \"push\", \"push\", \"push\", \"getMin\", \"pop\", \"top\", \"getMin\"]\n", + "inputs = [[], [-2], [0], [-3], [], [], [], []]\n", + "expected = [None, None, None, None, -3, None, 0, -2]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_min_stack_operations(MinStack, operations, inputs)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_min_stack_operations(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/min_stack/solution.py b/leetcode/min_stack/solution.py new file mode 100644 index 0000000..92ffe03 --- /dev/null +++ b/leetcode/min_stack/solution.py @@ -0,0 +1,30 @@ +class MinStack: + # Time: O(1) for all operations + # Space: O(n) where n is number of elements + def __init__(self) -> None: + self.stack: list[int] = [] + self.min_stack: list[int] = [] + + # Time: O(1) + # Space: O(1) + def push(self, val: int) -> None: + self.stack.append(val) + if not self.min_stack or val <= self.min_stack[-1]: + self.min_stack.append(val) + + # Time: O(1) + # Space: O(1) + def pop(self) -> None: + if self.stack[-1] == self.min_stack[-1]: + self.min_stack.pop() + self.stack.pop() + + # Time: O(1) + # Space: O(1) + def top(self) -> int: + return self.stack[-1] + + # Time: O(1) + # Space: O(1) + def get_min(self) -> int: + return self.min_stack[-1] diff --git a/leetcode/min_stack/test_solution.py b/leetcode/min_stack/test_solution.py new file mode 100644 index 0000000..0604301 --- /dev/null +++ b/leetcode/min_stack/test_solution.py @@ -0,0 +1,34 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_min_stack_operations, run_min_stack_operations +from .solution import MinStack + + +class TestTestMinStack: + + @logged_test + @pytest.mark.parametrize( + "operations, inputs, expected", + [ + ( + ["MinStack", "push", "push", "push", "getMin", "pop", "top", "getMin"], + [[], [-2], [0], [-3], [], [], [], []], + [None, None, None, None, -3, None, 0, -2], + ), + ( + ["MinStack", "push", "top", "getMin", "pop"], + [[], [5], [], [], []], + [None, None, 5, 5, None], + ), + ( + ["MinStack", "push", "push", "push", "getMin", "pop", "getMin", "pop", "getMin"], + [[], [1], [1], [2], [], [], [], [], []], + [None, None, None, None, 1, None, 1, None, 1], + ), + ], + ) + def test_min_stack(self, operations: list[str], inputs: list[list[int]], expected: list[int | None]): + result = run_min_stack_operations(MinStack, operations, inputs) + assert_min_stack_operations(result, expected) diff --git a/leetcode/minimum_height_trees/README.md b/leetcode/minimum_height_trees/README.md new file mode 100644 index 0000000..15ad725 --- /dev/null +++ b/leetcode/minimum_height_trees/README.md @@ -0,0 +1,47 @@ +# Minimum Height Trees + +**Difficulty:** Medium +**Topics:** Depth-First Search, Breadth-First Search, Graph, Topological Sort +**Tags:** grind-75 + +**LeetCode:** [Problem 310](https://leetcode.com/problems/minimum-height-trees/description/) + +## Problem Description + +A tree is an undirected graph in which any two vertices are connected by _exactly_ one path. In other words, any connected graph without simple cycles is a tree. + +Given a tree of `n` nodes labelled from `0` to `n - 1`, and an array of `n - 1` `edges` where `edges[i] = [ai, bi]` indicates that there is an undirected edge between the two nodes `ai` and `bi` in the tree, you can choose any node of the tree as the root. When you select a node `x` as the root, the result tree has height `h`. Among all possible rooted trees, those with minimum height (i.e. `min(h)`) are called **minimum height trees** (MHTs). + +Return _a list of all **MHTs'** root labels_. You can return the answer in **any order**. + +The **height** of a rooted tree is the number of edges on the longest downward path between the root and a leaf. + +## Examples + +### Example 1: + + + +``` +Input: n = 4, edges = [[1,0],[1,2],[1,3]] +Output: [1] +Explanation: As shown, the height of the tree is 1 when the root is the node with label 1 which is the only MHT. +``` + +### Example 2: + + + +``` +Input: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]] +Output: [3,4] +``` + +## Constraints + +- `1 <= n <= 2 * 10^4` +- `edges.length == n - 1` +- `0 <= ai, bi < n` +- `ai != bi` +- All the pairs `(ai, bi)` are distinct. +- The given input is **guaranteed** to be a tree and there will be **no repeated** edges. diff --git a/leetcode/minimum_height_trees/__init__.py b/leetcode/minimum_height_trees/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/minimum_height_trees/helpers.py b/leetcode/minimum_height_trees/helpers.py new file mode 100644 index 0000000..c6ebe38 --- /dev/null +++ b/leetcode/minimum_height_trees/helpers.py @@ -0,0 +1,8 @@ +def run_find_min_height_trees(solution_class: type, n: int, edges: list[list[int]]): + implementation = solution_class() + return implementation.find_min_height_trees(n, edges) + + +def assert_find_min_height_trees(result: list[int], expected: list[int]) -> bool: + assert sorted(result) == sorted(expected) + return True diff --git a/leetcode/minimum_height_trees/playground.ipynb b/leetcode/minimum_height_trees/playground.ipynb new file mode 100644 index 0000000..8cf7734 --- /dev/null +++ b/leetcode/minimum_height_trees/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_find_min_height_trees, run_find_min_height_trees\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "n = 4\n", + "edges = [[1, 0], [1, 2], [1, 3]]\n", + "expected = [1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_find_min_height_trees(Solution, n, edges)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_find_min_height_trees(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/minimum_height_trees/solution.py b/leetcode/minimum_height_trees/solution.py new file mode 100644 index 0000000..37120f7 --- /dev/null +++ b/leetcode/minimum_height_trees/solution.py @@ -0,0 +1,29 @@ +class Solution: + + # Time: O(V) + # Space: O(V) + def find_min_height_trees(self, n: int, edges: list[list[int]]) -> list[int]: + from collections import defaultdict, deque + + if n == 1: + return [0] + + graph = defaultdict(set) + for u, v in edges: + graph[u].add(v) + graph[v].add(u) + + leaves = deque([i for i in range(n) if len(graph[i]) == 1]) + + remaining = n + while remaining > 2: + size = len(leaves) + remaining -= size + for _ in range(size): + leaf = leaves.popleft() + neighbor = graph[leaf].pop() + graph[neighbor].remove(leaf) + if len(graph[neighbor]) == 1: + leaves.append(neighbor) + + return list(leaves) diff --git a/leetcode/minimum_height_trees/test_solution.py b/leetcode/minimum_height_trees/test_solution.py new file mode 100644 index 0000000..d9a27c4 --- /dev/null +++ b/leetcode/minimum_height_trees/test_solution.py @@ -0,0 +1,26 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_find_min_height_trees, run_find_min_height_trees +from .solution import Solution + + +class TestMinimumHeightTrees: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "n, edges, expected", + [ + (4, [[1, 0], [1, 2], [1, 3]], [1]), + (6, [[3, 0], [3, 1], [3, 2], [3, 4], [5, 4]], [3, 4]), + (1, [], [0]), + (2, [[0, 1]], [0, 1]), + (3, [[0, 1], [1, 2]], [1]), + ], + ) + def test_find_min_height_trees(self, n: int, edges: list[list[int]], expected: list[int]): + result = run_find_min_height_trees(Solution, n, edges) + assert_find_min_height_trees(result, expected) diff --git a/leetcode/minimum_window_substring/README.md b/leetcode/minimum_window_substring/README.md new file mode 100644 index 0000000..f54b7da --- /dev/null +++ b/leetcode/minimum_window_substring/README.md @@ -0,0 +1,51 @@ +# Minimum Window Substring + +**Difficulty:** Hard +**Topics:** Hash Table, String, Sliding Window +**Tags:** grind-75 + +**LeetCode:** [Problem 76](https://leetcode.com/problems/minimum-window-substring/description/) + +## Problem Description + +Given two strings `s` and `t` of lengths `m` and `n` respectively, return the **minimum window substring** of `s` such that every character in `t` (including duplicates) is included in the window. If there is no such substring, return the empty string `""`. + +The testcases will be generated such that the answer is unique. + +## Examples + +### Example 1: + +``` +Input: s = "ADOBECODEBANC", t = "ABC" +Output: "BANC" +``` + +**Explanation:** The minimum window substring "BANC" includes 'A', 'B', and 'C' from string t. + +### Example 2: + +``` +Input: s = "a", t = "a" +Output: "a" +``` + +**Explanation:** The entire string s is the minimum window. + +### Example 3: + +``` +Input: s = "a", t = "aa" +Output: "" +``` + +**Explanation:** Both 'a's from t must be included in the window. Since the largest window of s only has one 'a', return empty string. + +## Constraints + +- `m == s.length` +- `n == t.length` +- `1 <= m, n <= 10^5` +- `s` and `t` consist of uppercase and lowercase English letters. + +**Follow up:** Could you find an algorithm that runs in `O(m + n)` time? diff --git a/leetcode/minimum_window_substring/__init__.py b/leetcode/minimum_window_substring/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/minimum_window_substring/helpers.py b/leetcode/minimum_window_substring/helpers.py new file mode 100644 index 0000000..ac1ecc5 --- /dev/null +++ b/leetcode/minimum_window_substring/helpers.py @@ -0,0 +1,8 @@ +def run_min_window(solution_class: type, s: str, t: str): + implementation = solution_class() + return implementation.min_window(s, t) + + +def assert_min_window(result: str, expected: str) -> bool: + assert result == expected + return True diff --git a/leetcode/minimum_window_substring/playground.ipynb b/leetcode/minimum_window_substring/playground.ipynb new file mode 100644 index 0000000..c35e737 --- /dev/null +++ b/leetcode/minimum_window_substring/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_min_window, run_min_window\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "s = \"ADOBECODEBANC\"\n", + "t = \"ABC\"\n", + "expected = \"BANC\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_min_window(Solution, s, t)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_min_window(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/minimum_window_substring/solution.py b/leetcode/minimum_window_substring/solution.py new file mode 100644 index 0000000..db9711a --- /dev/null +++ b/leetcode/minimum_window_substring/solution.py @@ -0,0 +1,49 @@ +from collections import Counter + + +class Solution: + # Sliding Window + # Time: O(m + n) where m = len(s), n = len(t) + # Space: O(k) where k is unique chars in t + def min_window(self, s: str, t: str) -> str: + + if not t or len(t) > len(s): + return "" + + need = Counter(t) + + left = 0 + formed = 0 + required = len(need) + window_counts: dict[str, int] = {} + + # Result: (window length, left, right) + ans: tuple[float, int | None, int | None] = (float("inf"), None, None) + + for right in range(len(s)): + char = s[right] + window_counts[char] = window_counts.get(char, 0) + 1 + + # Check if current char frequency matches desired frequency in t + if char in need and window_counts[char] == need[char]: + formed += 1 + + # Contract window until it's no longer valid + while left <= right and formed == required: + char = s[left] + + # Update result if this window is smaller + if right - left + 1 < ans[0]: + ans = (right - left + 1, left, right) + + # Remove from left + window_counts[char] -= 1 + if char in need and window_counts[char] < need[char]: + formed -= 1 + + left += 1 + + if ans[0] == float("inf"): + return "" + assert ans[1] is not None and ans[2] is not None + return s[ans[1] : ans[2] + 1] diff --git a/leetcode/minimum_window_substring/test_solution.py b/leetcode/minimum_window_substring/test_solution.py new file mode 100644 index 0000000..2be62d0 --- /dev/null +++ b/leetcode/minimum_window_substring/test_solution.py @@ -0,0 +1,26 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_min_window, run_min_window +from .solution import Solution + + +class TestMinimumWindowSubstring: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, t, expected", + [ + ("ADOBECODEBANC", "ABC", "BANC"), + ("a", "a", "a"), + ("a", "aa", ""), + ("ab", "b", "b"), + ("abc", "cba", "abc"), + ], + ) + def test_min_window(self, s: str, t: str, expected: str): + result = run_min_window(Solution, s, t) + assert_min_window(result, expected) diff --git a/leetcode/number_of_islands/README.md b/leetcode/number_of_islands/README.md new file mode 100644 index 0000000..ada9064 --- /dev/null +++ b/leetcode/number_of_islands/README.md @@ -0,0 +1,46 @@ +# Number of Islands + +**Difficulty:** Medium +**Topics:** Array, Depth-First Search, Breadth-First Search, Union Find, Matrix +**Tags:** grind-75 + +**LeetCode:** [Problem 200](https://leetcode.com/problems/number-of-islands/description/) + +## Problem Description + +Given an `m x n` 2D binary grid `grid` which represents a map of `'1'`s (land) and `'0'`s (water), return _the number of islands_. + +An **island** is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water. + +## Examples + +### Example 1: + +``` +Input: grid = [ + ["1","1","1","1","0"], + ["1","1","0","1","0"], + ["1","1","0","0","0"], + ["0","0","0","0","0"] +] +Output: 1 +``` + +### Example 2: + +``` +Input: grid = [ + ["1","1","0","0","0"], + ["1","1","0","0","0"], + ["0","0","1","0","0"], + ["0","0","0","1","1"] +] +Output: 3 +``` + +## Constraints + +- `m == grid.length` +- `n == grid[i].length` +- `1 <= m, n <= 300` +- `grid[i][j]` is `'0'` or `'1'`. diff --git a/leetcode/number_of_islands/__init__.py b/leetcode/number_of_islands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/number_of_islands/helpers.py b/leetcode/number_of_islands/helpers.py new file mode 100644 index 0000000..291c636 --- /dev/null +++ b/leetcode/number_of_islands/helpers.py @@ -0,0 +1,8 @@ +def run_num_islands(solution_class: type, grid: list[list[str]]): + implementation = solution_class() + return implementation.num_islands(grid) + + +def assert_num_islands(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/number_of_islands/playground.ipynb b/leetcode/number_of_islands/playground.ipynb new file mode 100644 index 0000000..57ebc3f --- /dev/null +++ b/leetcode/number_of_islands/playground.ipynb @@ -0,0 +1,73 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_num_islands, run_num_islands\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "grid = [\n", + " [\"1\", \"1\", \"1\", \"1\", \"0\"],\n", + " [\"1\", \"1\", \"0\", \"1\", \"0\"],\n", + " [\"1\", \"1\", \"0\", \"0\", \"0\"],\n", + " [\"0\", \"0\", \"0\", \"0\", \"0\"],\n", + "]\n", + "expected = 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_num_islands(Solution, grid)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_num_islands(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/number_of_islands/solution.py b/leetcode/number_of_islands/solution.py new file mode 100644 index 0000000..6327f54 --- /dev/null +++ b/leetcode/number_of_islands/solution.py @@ -0,0 +1,28 @@ +class Solution: + + # Time: O(m * n) where m = rows, n = cols + # Space: O(m * n) for recursion stack in worst case + def num_islands(self, grid: list[list[str]]) -> int: + if not grid or not grid[0]: + return 0 + VISITED = "0" + UNVISITED_ISLAND = "1" + + rows, cols = len(grid), len(grid[0]) + islands = 0 + + def dfs(r: int, c: int) -> None: + if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] != UNVISITED_ISLAND: + return + + grid[r][c] = VISITED + for dr, dc in [(1, 0), (-1, 0), (0, 1), (0, -1)]: + dfs(r + dr, c + dc) + + for r in range(rows): + for c in range(cols): + if grid[r][c] == UNVISITED_ISLAND: + islands += 1 + dfs(r, c) + + return islands diff --git a/leetcode/number_of_islands/test_solution.py b/leetcode/number_of_islands/test_solution.py new file mode 100644 index 0000000..2428a04 --- /dev/null +++ b/leetcode/number_of_islands/test_solution.py @@ -0,0 +1,42 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_num_islands, run_num_islands +from .solution import Solution + + +class TestNumberOfIslands: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "grid, expected", + [ + ( + [ + ["1", "1", "1", "1", "0"], + ["1", "1", "0", "1", "0"], + ["1", "1", "0", "0", "0"], + ["0", "0", "0", "0", "0"], + ], + 1, + ), + ( + [ + ["1", "1", "0", "0", "0"], + ["1", "1", "0", "0", "0"], + ["0", "0", "1", "0", "0"], + ["0", "0", "0", "1", "1"], + ], + 3, + ), + ([["1"]], 1), + ([["0"]], 0), + ([["1", "0", "1"], ["0", "1", "0"], ["1", "0", "1"]], 5), + ], + ) + def test_num_islands(self, grid: list[list[str]], expected: int): + result = run_num_islands(Solution, grid) + assert_num_islands(result, expected) diff --git a/leetcode/partition_equal_subset_sum/README.md b/leetcode/partition_equal_subset_sum/README.md new file mode 100644 index 0000000..82f48a5 --- /dev/null +++ b/leetcode/partition_equal_subset_sum/README.md @@ -0,0 +1,36 @@ +# Partition Equal Subset Sum + +**Difficulty:** Medium +**Topics:** Array, Dynamic Programming +**Tags:** grind-75 + +**LeetCode:** [Problem 416](https://leetcode.com/problems/partition-equal-subset-sum/description/) + +## Problem Description + +Given an integer array `nums`, return `true` if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or `false` otherwise. + +## Examples + +### Example 1: + +``` +Input: nums = [1,5,11,5] +Output: true +``` + +**Explanation:** The array can be partitioned as [1, 5, 5] and [11]. + +### Example 2: + +``` +Input: nums = [1,2,3,5] +Output: false +``` + +**Explanation:** The array cannot be partitioned into equal sum subsets. + +## Constraints + +- 1 <= nums.length <= 200 +- 1 <= nums[i] <= 100 diff --git a/leetcode/partition_equal_subset_sum/__init__.py b/leetcode/partition_equal_subset_sum/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/partition_equal_subset_sum/helpers.py b/leetcode/partition_equal_subset_sum/helpers.py new file mode 100644 index 0000000..20a018a --- /dev/null +++ b/leetcode/partition_equal_subset_sum/helpers.py @@ -0,0 +1,8 @@ +def run_can_partition(solution_class: type, nums: list[int]): + implementation = solution_class() + return implementation.can_partition(nums) + + +def assert_can_partition(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/partition_equal_subset_sum/playground.ipynb b/leetcode/partition_equal_subset_sum/playground.ipynb new file mode 100644 index 0000000..5597f66 --- /dev/null +++ b/leetcode/partition_equal_subset_sum/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_can_partition, run_can_partition\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [1, 5, 11, 5]\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_can_partition(Solution, nums)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_can_partition(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/partition_equal_subset_sum/solution.py b/leetcode/partition_equal_subset_sum/solution.py new file mode 100644 index 0000000..e3a254d --- /dev/null +++ b/leetcode/partition_equal_subset_sum/solution.py @@ -0,0 +1,22 @@ +class Solution: + + # Time: O(n * sum) + # Space: O(sum) + def can_partition(self, nums: list[int]) -> bool: + total = sum(nums) + if total % 2: + return False + + target = total // 2 + dp = [False] * (target + 1) + dp[0] = True + + for num in nums: + for j in range(target, num - 1, -1): + dp[j] = dp[j] or dp[j - num] + + # Early termination: found target sum! + if dp[target]: + return True + + return False diff --git a/leetcode/partition_equal_subset_sum/test_solution.py b/leetcode/partition_equal_subset_sum/test_solution.py new file mode 100644 index 0000000..1eee974 --- /dev/null +++ b/leetcode/partition_equal_subset_sum/test_solution.py @@ -0,0 +1,28 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_can_partition, run_can_partition +from .solution import Solution + + +class TestPartitionEqualSubsetSum: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([1, 5, 11, 5], True), + ([1, 2, 3, 5], False), + ([1, 1], True), + ([1], False), + ([2, 2, 1, 1], True), + ([100], False), + ([1, 2, 5], False), + ], + ) + def test_can_partition(self, nums: list[int], expected: bool): + result = run_can_partition(Solution, nums) + assert_can_partition(result, expected) From 24eeeced663a030f0d65bfc8eee0fd5ebaa57754 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 09:04:05 +0700 Subject: [PATCH 18/39] feat: add zero_one_matrix --- .templates/leetcode/json/permutations.json | 63 ++++++++++ .../json/product_of_array_except_self.json | 60 ++++++++++ .templates/leetcode/json/ransom_note.json | 61 ++++++++++ .../leetcode/json/reverse_linked_list.json | 65 +++++++++++ .../leetcode/json/reverse_linked_list_ii.json | 62 ++++++++++ .templates/leetcode/json/rotting_oranges.json | 67 +++++++++++ .../json/search_in_rotated_sorted_array.json | 61 ++++++++++ ...serialize_and_deserialize_binary_tree.json | 70 +++++++++++ .templates/leetcode/json/sort_colors.json | 60 ++++++++++ .templates/leetcode/json/spiral_matrix.json | 64 ++++++++++ .../leetcode/json/string_to_integer_atoi.json | 73 ++++++++++++ .templates/leetcode/json/task_scheduler.json | 67 +++++++++++ .templates/leetcode/json/three_sum.json | 67 +++++++++++ .../json/time_based_key_value_store.json | 69 +++++++++++ .../leetcode/json/trapping_rain_water.json | 62 ++++++++++ .../leetcode/json/valid_palindrome.json | 67 +++++++++++ .../leetcode/json/valid_parentheses.json | 63 ++++++++++ .../json/validate_binary_search_tree.json | 64 ++++++++++ .templates/leetcode/json/word_break.json | 67 +++++++++++ .templates/leetcode/json/word_ladder.json | 64 ++++++++++ .templates/leetcode/json/zero_one_matrix.json | 64 ++++++++++ leetcode/permutations/README.md | 40 +++++++ leetcode/permutations/__init__.py | 0 leetcode/permutations/helpers.py | 14 +++ leetcode/permutations/playground.ipynb | 68 +++++++++++ leetcode/permutations/solution.py | 19 +++ leetcode/permutations/test_solution.py | 25 ++++ .../product_of_array_except_self/README.md | 39 +++++++ .../product_of_array_except_self/__init__.py | 0 .../product_of_array_except_self/helpers.py | 8 ++ .../playground.ipynb | 68 +++++++++++ .../product_of_array_except_self/solution.py | 26 +++++ .../test_solution.py | 28 +++++ leetcode/ransom_note/README.md | 41 +++++++ leetcode/ransom_note/__init__.py | 0 leetcode/ransom_note/helpers.py | 8 ++ leetcode/ransom_note/playground.ipynb | 69 +++++++++++ leetcode/ransom_note/solution.py | 18 +++ leetcode/ransom_note/test_solution.py | 32 +++++ leetcode/reverse_linked_list/README.md | 45 ++++++++ leetcode/reverse_linked_list/__init__.py | 0 leetcode/reverse_linked_list/helpers.py | 13 +++ leetcode/reverse_linked_list/playground.ipynb | 70 +++++++++++ leetcode/reverse_linked_list/solution.py | 47 ++++++++ leetcode/reverse_linked_list/test_solution.py | 31 +++++ leetcode/reverse_linked_list_ii/README.md | 36 ++++++ leetcode/reverse_linked_list_ii/__init__.py | 0 leetcode/reverse_linked_list_ii/helpers.py | 13 +++ .../reverse_linked_list_ii/playground.ipynb | 71 ++++++++++++ leetcode/reverse_linked_list_ii/solution.py | 53 +++++++++ .../reverse_linked_list_ii/test_solution.py | 29 +++++ leetcode/rotting_oranges/README.md | 55 +++++++++ leetcode/rotting_oranges/__init__.py | 0 leetcode/rotting_oranges/helpers.py | 8 ++ leetcode/rotting_oranges/playground.ipynb | 68 +++++++++++ leetcode/rotting_oranges/solution.py | 43 +++++++ leetcode/rotting_oranges/test_solution.py | 32 +++++ .../search_in_rotated_sorted_array/README.md | 48 ++++++++ .../__init__.py | 0 .../search_in_rotated_sorted_array/helpers.py | 8 ++ .../playground.ipynb | 69 +++++++++++ .../solution.py | 27 +++++ .../test_solution.py | 30 +++++ .../README.md | 38 ++++++ .../__init__.py | 0 .../helpers.py | 19 +++ .../playground.ipynb | 69 +++++++++++ .../solution.py | 41 +++++++ .../test_solution.py | 26 +++++ leetcode/sort_colors/README.md | 39 +++++++ leetcode/sort_colors/__init__.py | 0 leetcode/sort_colors/helpers.py | 10 ++ leetcode/sort_colors/playground.ipynb | 68 +++++++++++ leetcode/sort_colors/solution.py | 20 ++++ leetcode/sort_colors/test_solution.py | 30 +++++ leetcode/spiral_matrix/README.md | 38 ++++++ leetcode/spiral_matrix/__init__.py | 0 leetcode/spiral_matrix/helpers.py | 8 ++ leetcode/spiral_matrix/playground.ipynb | 68 +++++++++++ leetcode/spiral_matrix/solution.py | 42 +++++++ leetcode/spiral_matrix/test_solution.py | 28 +++++ leetcode/string_to_integer_atoi/README.md | 109 ++++++++++++++++++ leetcode/string_to_integer_atoi/__init__.py | 0 leetcode/string_to_integer_atoi/helpers.py | 8 ++ .../string_to_integer_atoi/playground.ipynb | 68 +++++++++++ leetcode/string_to_integer_atoi/solution.py | 31 +++++ .../string_to_integer_atoi/test_solution.py | 34 ++++++ leetcode/task_scheduler/README.md | 54 +++++++++ leetcode/task_scheduler/__init__.py | 0 leetcode/task_scheduler/helpers.py | 8 ++ leetcode/task_scheduler/playground.ipynb | 69 +++++++++++ leetcode/task_scheduler/solution.py | 37 ++++++ leetcode/task_scheduler/test_solution.py | 27 +++++ leetcode/three_sum/README.md | 52 +++++++++ leetcode/three_sum/__init__.py | 0 leetcode/three_sum/helpers.py | 13 +++ leetcode/three_sum/playground.ipynb | 68 +++++++++++ leetcode/three_sum/solution.py | 24 ++++ leetcode/three_sum/test_solution.py | 27 +++++ leetcode/time_based_key_value_store/README.md | 49 ++++++++ .../time_based_key_value_store/__init__.py | 0 .../time_based_key_value_store/helpers.py | 19 +++ .../playground.ipynb | 69 +++++++++++ .../time_based_key_value_store/solution.py | 32 +++++ .../test_solution.py | 32 +++++ leetcode/trapping_rain_water/README.md | 37 ++++++ leetcode/trapping_rain_water/__init__.py | 0 leetcode/trapping_rain_water/helpers.py | 8 ++ leetcode/trapping_rain_water/playground.ipynb | 91 +++++++++++++++ leetcode/trapping_rain_water/solution.py | 26 +++++ leetcode/trapping_rain_water/test_solution.py | 28 +++++ leetcode/valid_palindrome/README.md | 47 ++++++++ leetcode/valid_palindrome/__init__.py | 0 leetcode/valid_palindrome/helpers.py | 8 ++ leetcode/valid_palindrome/playground.ipynb | 68 +++++++++++ leetcode/valid_palindrome/solution.py | 20 ++++ leetcode/valid_palindrome/test_solution.py | 29 +++++ leetcode/valid_parentheses/README.md | 59 ++++++++++ leetcode/valid_parentheses/__init__.py | 0 leetcode/valid_parentheses/helpers.py | 8 ++ leetcode/valid_parentheses/playground.ipynb | 68 +++++++++++ leetcode/valid_parentheses/solution.py | 15 +++ leetcode/valid_parentheses/test_solution.py | 31 +++++ .../validate_binary_search_tree/README.md | 44 +++++++ .../validate_binary_search_tree/__init__.py | 0 .../validate_binary_search_tree/helpers.py | 12 ++ .../playground.ipynb | 70 +++++++++++ .../validate_binary_search_tree/solution.py | 17 +++ .../test_solution.py | 27 +++++ leetcode/word_break/README.md | 49 ++++++++ leetcode/word_break/__init__.py | 0 leetcode/word_break/helpers.py | 8 ++ leetcode/word_break/playground.ipynb | 69 +++++++++++ leetcode/word_break/solution.py | 16 +++ leetcode/word_break/test_solution.py | 28 +++++ leetcode/word_ladder/README.md | 47 ++++++++ leetcode/word_ladder/__init__.py | 0 leetcode/word_ladder/helpers.py | 8 ++ leetcode/word_ladder/playground.ipynb | 70 +++++++++++ leetcode/word_ladder/solution.py | 37 ++++++ leetcode/word_ladder/test_solution.py | 26 +++++ leetcode/zero_one_matrix/README.md | 44 +++++++ leetcode/zero_one_matrix/__init__.py | 0 leetcode/zero_one_matrix/helpers.py | 8 ++ leetcode/zero_one_matrix/playground.ipynb | 68 +++++++++++ leetcode/zero_one_matrix/solution.py | 30 +++++ leetcode/zero_one_matrix/test_solution.py | 25 ++++ 147 files changed, 5279 insertions(+) create mode 100644 .templates/leetcode/json/permutations.json create mode 100644 .templates/leetcode/json/product_of_array_except_self.json create mode 100644 .templates/leetcode/json/ransom_note.json create mode 100644 .templates/leetcode/json/reverse_linked_list.json create mode 100644 .templates/leetcode/json/reverse_linked_list_ii.json create mode 100644 .templates/leetcode/json/rotting_oranges.json create mode 100644 .templates/leetcode/json/search_in_rotated_sorted_array.json create mode 100644 .templates/leetcode/json/serialize_and_deserialize_binary_tree.json create mode 100644 .templates/leetcode/json/sort_colors.json create mode 100644 .templates/leetcode/json/spiral_matrix.json create mode 100644 .templates/leetcode/json/string_to_integer_atoi.json create mode 100644 .templates/leetcode/json/task_scheduler.json create mode 100644 .templates/leetcode/json/three_sum.json create mode 100644 .templates/leetcode/json/time_based_key_value_store.json create mode 100644 .templates/leetcode/json/trapping_rain_water.json create mode 100644 .templates/leetcode/json/valid_palindrome.json create mode 100644 .templates/leetcode/json/valid_parentheses.json create mode 100644 .templates/leetcode/json/validate_binary_search_tree.json create mode 100644 .templates/leetcode/json/word_break.json create mode 100644 .templates/leetcode/json/word_ladder.json create mode 100644 .templates/leetcode/json/zero_one_matrix.json create mode 100644 leetcode/permutations/README.md create mode 100644 leetcode/permutations/__init__.py create mode 100644 leetcode/permutations/helpers.py create mode 100644 leetcode/permutations/playground.ipynb create mode 100644 leetcode/permutations/solution.py create mode 100644 leetcode/permutations/test_solution.py create mode 100644 leetcode/product_of_array_except_self/README.md create mode 100644 leetcode/product_of_array_except_self/__init__.py create mode 100644 leetcode/product_of_array_except_self/helpers.py create mode 100644 leetcode/product_of_array_except_self/playground.ipynb create mode 100644 leetcode/product_of_array_except_self/solution.py create mode 100644 leetcode/product_of_array_except_self/test_solution.py create mode 100644 leetcode/ransom_note/README.md create mode 100644 leetcode/ransom_note/__init__.py create mode 100644 leetcode/ransom_note/helpers.py create mode 100644 leetcode/ransom_note/playground.ipynb create mode 100644 leetcode/ransom_note/solution.py create mode 100644 leetcode/ransom_note/test_solution.py create mode 100644 leetcode/reverse_linked_list/README.md create mode 100644 leetcode/reverse_linked_list/__init__.py create mode 100644 leetcode/reverse_linked_list/helpers.py create mode 100644 leetcode/reverse_linked_list/playground.ipynb create mode 100644 leetcode/reverse_linked_list/solution.py create mode 100644 leetcode/reverse_linked_list/test_solution.py create mode 100644 leetcode/reverse_linked_list_ii/README.md create mode 100644 leetcode/reverse_linked_list_ii/__init__.py create mode 100644 leetcode/reverse_linked_list_ii/helpers.py create mode 100644 leetcode/reverse_linked_list_ii/playground.ipynb create mode 100644 leetcode/reverse_linked_list_ii/solution.py create mode 100644 leetcode/reverse_linked_list_ii/test_solution.py create mode 100644 leetcode/rotting_oranges/README.md create mode 100644 leetcode/rotting_oranges/__init__.py create mode 100644 leetcode/rotting_oranges/helpers.py create mode 100644 leetcode/rotting_oranges/playground.ipynb create mode 100644 leetcode/rotting_oranges/solution.py create mode 100644 leetcode/rotting_oranges/test_solution.py create mode 100644 leetcode/search_in_rotated_sorted_array/README.md create mode 100644 leetcode/search_in_rotated_sorted_array/__init__.py create mode 100644 leetcode/search_in_rotated_sorted_array/helpers.py create mode 100644 leetcode/search_in_rotated_sorted_array/playground.ipynb create mode 100644 leetcode/search_in_rotated_sorted_array/solution.py create mode 100644 leetcode/search_in_rotated_sorted_array/test_solution.py create mode 100644 leetcode/serialize_and_deserialize_binary_tree/README.md create mode 100644 leetcode/serialize_and_deserialize_binary_tree/__init__.py create mode 100644 leetcode/serialize_and_deserialize_binary_tree/helpers.py create mode 100644 leetcode/serialize_and_deserialize_binary_tree/playground.ipynb create mode 100644 leetcode/serialize_and_deserialize_binary_tree/solution.py create mode 100644 leetcode/serialize_and_deserialize_binary_tree/test_solution.py create mode 100644 leetcode/sort_colors/README.md create mode 100644 leetcode/sort_colors/__init__.py create mode 100644 leetcode/sort_colors/helpers.py create mode 100644 leetcode/sort_colors/playground.ipynb create mode 100644 leetcode/sort_colors/solution.py create mode 100644 leetcode/sort_colors/test_solution.py create mode 100644 leetcode/spiral_matrix/README.md create mode 100644 leetcode/spiral_matrix/__init__.py create mode 100644 leetcode/spiral_matrix/helpers.py create mode 100644 leetcode/spiral_matrix/playground.ipynb create mode 100644 leetcode/spiral_matrix/solution.py create mode 100644 leetcode/spiral_matrix/test_solution.py create mode 100644 leetcode/string_to_integer_atoi/README.md create mode 100644 leetcode/string_to_integer_atoi/__init__.py create mode 100644 leetcode/string_to_integer_atoi/helpers.py create mode 100644 leetcode/string_to_integer_atoi/playground.ipynb create mode 100644 leetcode/string_to_integer_atoi/solution.py create mode 100644 leetcode/string_to_integer_atoi/test_solution.py create mode 100644 leetcode/task_scheduler/README.md create mode 100644 leetcode/task_scheduler/__init__.py create mode 100644 leetcode/task_scheduler/helpers.py create mode 100644 leetcode/task_scheduler/playground.ipynb create mode 100644 leetcode/task_scheduler/solution.py create mode 100644 leetcode/task_scheduler/test_solution.py create mode 100644 leetcode/three_sum/README.md create mode 100644 leetcode/three_sum/__init__.py create mode 100644 leetcode/three_sum/helpers.py create mode 100644 leetcode/three_sum/playground.ipynb create mode 100644 leetcode/three_sum/solution.py create mode 100644 leetcode/three_sum/test_solution.py create mode 100644 leetcode/time_based_key_value_store/README.md create mode 100644 leetcode/time_based_key_value_store/__init__.py create mode 100644 leetcode/time_based_key_value_store/helpers.py create mode 100644 leetcode/time_based_key_value_store/playground.ipynb create mode 100644 leetcode/time_based_key_value_store/solution.py create mode 100644 leetcode/time_based_key_value_store/test_solution.py create mode 100644 leetcode/trapping_rain_water/README.md create mode 100644 leetcode/trapping_rain_water/__init__.py create mode 100644 leetcode/trapping_rain_water/helpers.py create mode 100644 leetcode/trapping_rain_water/playground.ipynb create mode 100644 leetcode/trapping_rain_water/solution.py create mode 100644 leetcode/trapping_rain_water/test_solution.py create mode 100644 leetcode/valid_palindrome/README.md create mode 100644 leetcode/valid_palindrome/__init__.py create mode 100644 leetcode/valid_palindrome/helpers.py create mode 100644 leetcode/valid_palindrome/playground.ipynb create mode 100644 leetcode/valid_palindrome/solution.py create mode 100644 leetcode/valid_palindrome/test_solution.py create mode 100644 leetcode/valid_parentheses/README.md create mode 100644 leetcode/valid_parentheses/__init__.py create mode 100644 leetcode/valid_parentheses/helpers.py create mode 100644 leetcode/valid_parentheses/playground.ipynb create mode 100644 leetcode/valid_parentheses/solution.py create mode 100644 leetcode/valid_parentheses/test_solution.py create mode 100644 leetcode/validate_binary_search_tree/README.md create mode 100644 leetcode/validate_binary_search_tree/__init__.py create mode 100644 leetcode/validate_binary_search_tree/helpers.py create mode 100644 leetcode/validate_binary_search_tree/playground.ipynb create mode 100644 leetcode/validate_binary_search_tree/solution.py create mode 100644 leetcode/validate_binary_search_tree/test_solution.py create mode 100644 leetcode/word_break/README.md create mode 100644 leetcode/word_break/__init__.py create mode 100644 leetcode/word_break/helpers.py create mode 100644 leetcode/word_break/playground.ipynb create mode 100644 leetcode/word_break/solution.py create mode 100644 leetcode/word_break/test_solution.py create mode 100644 leetcode/word_ladder/README.md create mode 100644 leetcode/word_ladder/__init__.py create mode 100644 leetcode/word_ladder/helpers.py create mode 100644 leetcode/word_ladder/playground.ipynb create mode 100644 leetcode/word_ladder/solution.py create mode 100644 leetcode/word_ladder/test_solution.py create mode 100644 leetcode/zero_one_matrix/README.md create mode 100644 leetcode/zero_one_matrix/__init__.py create mode 100644 leetcode/zero_one_matrix/helpers.py create mode 100644 leetcode/zero_one_matrix/playground.ipynb create mode 100644 leetcode/zero_one_matrix/solution.py create mode 100644 leetcode/zero_one_matrix/test_solution.py diff --git a/.templates/leetcode/json/permutations.json b/.templates/leetcode/json/permutations.json new file mode 100644 index 0000000..081332d --- /dev/null +++ b/.templates/leetcode/json/permutations.json @@ -0,0 +1,63 @@ +{ + "problem_name": "permutations", + "solution_class_name": "Solution", + "problem_number": "46", + "problem_title": "Permutations", + "difficulty": "Medium", + "topics": "Array, Backtracking", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an array `nums` of distinct integers, return all the possible permutations. You can return the answer in any order.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums = [1,2,3]\nOutput: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]\n```" + }, + { "content": "```\nInput: nums = [0,1]\nOutput: [[0,1],[1,0]]\n```" }, + { "content": "```\nInput: nums = [1]\nOutput: [[1]]\n```" } + ] + }, + "readme_constraints": "- 1 <= nums.length <= 6\n- -10 <= nums[i] <= 10\n- All the integers of nums are unique.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "permute", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.permute(nums)", + "helpers_assert_name": "permute", + "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool", + "helpers_assert_body": " # Sort both result and expected for comparison since order doesn't matter\n result_sorted = [sorted(perm) for perm in result]\n expected_sorted = [sorted(perm) for perm in expected]\n result_sorted.sort()\n expected_sorted.sort()\n assert len(result) == len(expected)\n assert result_sorted == expected_sorted\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_permute, run_permute\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "Permutations", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "permute", + "signature": "(self, nums: list[int]) -> list[list[int]]", + "body": " # TODO: Implement permute\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_permute", + "signature": "(self, nums: list[int], expected: list[list[int]])", + "parametrize": "nums, expected", + "test_cases": "[([1, 2, 3], [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]), ([0, 1], [[0, 1], [1, 0]]), ([1], [[1]]), ([2, 1], [[2, 1], [1, 2]])]", + "body": " result = run_permute(Solution, nums)\n assert_permute(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_permute, assert_permute\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [1, 2, 3]\nexpected = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]", + "playground_run": "result = run_permute(Solution, nums)\nresult", + "playground_assert": "assert_permute(result, expected)" +} diff --git a/.templates/leetcode/json/product_of_array_except_self.json b/.templates/leetcode/json/product_of_array_except_self.json new file mode 100644 index 0000000..d99aa69 --- /dev/null +++ b/.templates/leetcode/json/product_of_array_except_self.json @@ -0,0 +1,60 @@ +{ + "problem_name": "product_of_array_except_self", + "solution_class_name": "Solution", + "problem_number": "238", + "problem_title": "Product of Array Except Self", + "difficulty": "Medium", + "topics": "Array, Prefix Sum", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an integer array `nums`, return an array `answer` such that `answer[i]` is equal to the product of all the elements of `nums` except `nums[i]`.\n\nThe product of any prefix or suffix of `nums` is guaranteed to fit in a 32-bit integer.\n\nYou must write an algorithm that runs in O(n) time and without using the division operation.", + "_readme_examples": { + "list": [ + { "content": "```\nInput: nums = [1,2,3,4]\nOutput: [24,12,8,6]\n```" }, + { "content": "```\nInput: nums = [-1,1,0,-3,3]\nOutput: [0,0,9,0,0]\n```" } + ] + }, + "readme_constraints": "- 2 <= nums.length <= 10^5\n- -30 <= nums[i] <= 30\n- The input is generated such that answer[i] is guaranteed to fit in a 32-bit integer.", + "readme_additional": "**Follow up:** Can you solve the problem in O(1) extra space complexity? (The output array does not count as extra space for space complexity analysis.)", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "product_except_self", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.product_except_self(nums)", + "helpers_assert_name": "product_except_self", + "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_product_except_self, run_product_except_self\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ProductOfArrayExceptSelf", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "product_except_self", + "signature": "(self, nums: list[int]) -> list[int]", + "body": " # TODO: Implement product_except_self\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_product_except_self", + "signature": "(self, nums: list[int], expected: list[int])", + "parametrize": "nums, expected", + "test_cases": "[([1, 2, 3, 4], [24, 12, 8, 6]), ([-1, 1, 0, -3, 3], [0, 0, 9, 0, 0]), ([2, 3, 4, 5], [60, 40, 30, 24]), ([1, 1], [1, 1]), ([5, 2], [2, 5]), ([0, 1, 2, 3], [6, 0, 0, 0]), ([1, 0, 3, 4], [0, 12, 0, 0])]", + "body": " result = run_product_except_self(Solution, nums)\n assert_product_except_self(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_product_except_self, assert_product_except_self\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [1, 2, 3, 4]\nexpected = [24, 12, 8, 6]", + "playground_run": "result = run_product_except_self(Solution, nums)\nresult", + "playground_assert": "assert_product_except_self(result, expected)" +} diff --git a/.templates/leetcode/json/ransom_note.json b/.templates/leetcode/json/ransom_note.json new file mode 100644 index 0000000..6354724 --- /dev/null +++ b/.templates/leetcode/json/ransom_note.json @@ -0,0 +1,61 @@ +{ + "problem_name": "ransom_note", + "solution_class_name": "Solution", + "problem_number": "383", + "problem_title": "Ransom Note", + "difficulty": "Easy", + "topics": "Hash Table, String, Counting", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given two strings `ransomNote` and `magazine`, return `true` if `ransomNote` can be constructed by using the letters from `magazine` and `false` otherwise.\n\nEach letter in `magazine` can only be used once in `ransomNote`.", + "_readme_examples": { + "list": [ + { "content": "```\nInput: ransomNote = \"a\", magazine = \"b\"\nOutput: false\n```" }, + { "content": "```\nInput: ransomNote = \"aa\", magazine = \"ab\"\nOutput: false\n```" }, + { "content": "```\nInput: ransomNote = \"aa\", magazine = \"aab\"\nOutput: true\n```" } + ] + }, + "readme_constraints": "- 1 <= ransomNote.length, magazine.length <= 10^5\n- ransomNote and magazine consist of lowercase English letters.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "can_construct", + "helpers_run_signature": "(solution_class: type, ransom_note: str, magazine: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.can_construct(ransom_note, magazine)", + "helpers_assert_name": "can_construct", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_can_construct, run_can_construct\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "RansomNote", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "can_construct", + "signature": "(self, ransom_note: str, magazine: str) -> bool", + "body": " # TODO: Implement can_construct\n return False" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_can_construct", + "signature": "(self, ransom_note: str, magazine: str, expected: bool)", + "parametrize": "ransom_note, magazine, expected", + "test_cases": "[('a', 'b', False), ('aa', 'ab', False), ('aa', 'aab', True), ('aab', 'baa', True), ('', '', True), ('', 'abc', True), ('abc', '', False), ('abc', 'abc', True), ('abc', 'cba', True), ('aaa', 'aa', False), ('ab', 'ba', True)]", + "body": " result = run_can_construct(Solution, ransom_note, magazine)\n assert_can_construct(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_can_construct, assert_can_construct\nfrom solution import Solution", + "playground_setup": "# Example test case\nransom_note = 'aa'\nmagazine = 'aab'\nexpected = True", + "playground_run": "result = run_can_construct(Solution, ransom_note, magazine)\nresult", + "playground_assert": "assert_can_construct(result, expected)" +} diff --git a/.templates/leetcode/json/reverse_linked_list.json b/.templates/leetcode/json/reverse_linked_list.json new file mode 100644 index 0000000..2166957 --- /dev/null +++ b/.templates/leetcode/json/reverse_linked_list.json @@ -0,0 +1,65 @@ +{ + "problem_name": "reverse_linked_list", + "solution_class_name": "Solution", + "problem_number": "206", + "problem_title": "Reverse Linked List", + "difficulty": "Easy", + "topics": "Linked List, Recursion", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given the `head` of a singly linked list, reverse the list, and return the reversed list.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2021/02/19/rev1ex1.jpg)\n\n```\nInput: head = [1,2,3,4,5]\nOutput: [5,4,3,2,1]\n```" + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2021/02/19/rev1ex2.jpg)\n\n```\nInput: head = [1,2]\nOutput: [2,1]\n```" + }, + { "content": "```\nInput: head = []\nOutput: []\n```" } + ] + }, + "readme_constraints": "- The number of nodes in the list is the range `[0, 5000]`.\n- `-5000 <= Node.val <= 5000`", + "readme_additional": "**Follow up:** A linked list can be reversed either iteratively or recursively. Could you implement both?", + "helpers_imports": "from leetcode_py import ListNode", + "helpers_content": "", + "helpers_run_name": "reverse_list", + "helpers_run_signature": "(solution_class: type, head_list: list[int])", + "helpers_run_body": " head = ListNode[int].from_list(head_list)\n implementation = solution_class()\n return implementation.reverse_list(head)", + "helpers_assert_name": "reverse_list", + "helpers_assert_signature": "(result: ListNode[int] | None, expected_list: list[int]) -> bool", + "helpers_assert_body": " expected = ListNode[int].from_list(expected_list)\n assert result == expected\n return True", + "solution_imports": "from leetcode_py import ListNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_reverse_list, run_reverse_list\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ReverseLinkedList", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "reverse_list", + "signature": "(self, head: ListNode[int] | None) -> ListNode[int] | None", + "body": " # TODO: Implement reverse_list\n return None" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_reverse_list", + "signature": "(self, head_list: list[int], expected_list: list[int])", + "parametrize": "head_list, expected_list", + "test_cases": "[([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]), ([1, 2], [2, 1]), ([1], [1]), ([], []), ([1, 2, 3], [3, 2, 1]), ([1, 2, 3, 4], [4, 3, 2, 1]), ([-1, -2, -3], [-3, -2, -1]), ([0], [0]), ([5000, -5000], [-5000, 5000]), ([1, 1, 1], [1, 1, 1])]", + "body": " result = run_reverse_list(Solution, head_list)\n assert_reverse_list(result, expected_list)" + } + ] + }, + "playground_imports": "from helpers import run_reverse_list, assert_reverse_list\nfrom solution import Solution\nfrom leetcode_py import ListNode", + "playground_setup": "# Example test case\nhead_list = [1, 2, 3, 4, 5]\nexpected_list = [5, 4, 3, 2, 1]", + "playground_run": "result = run_reverse_list(Solution, head_list)\nresult", + "playground_assert": "assert_reverse_list(result, expected_list)" +} diff --git a/.templates/leetcode/json/reverse_linked_list_ii.json b/.templates/leetcode/json/reverse_linked_list_ii.json new file mode 100644 index 0000000..2efc947 --- /dev/null +++ b/.templates/leetcode/json/reverse_linked_list_ii.json @@ -0,0 +1,62 @@ +{ + "problem_name": "reverse_linked_list_ii", + "solution_class_name": "Solution", + "problem_number": "92", + "problem_title": "Reverse Linked List II", + "difficulty": "Medium", + "topics": "Linked List", + "_tags": { "list": [] }, + "readme_description": "Given the `head` of a singly linked list and two integers `left` and `right` where `left <= right`, reverse the nodes of the list from position `left` to position `right`, and return the reversed list.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: head = [1,2,3,4,5], left = 2, right = 4\nOutput: [1,4,3,2,5]\n```" + }, + { "content": "```\nInput: head = [5], left = 1, right = 1\nOutput: [5]\n```" } + ] + }, + "readme_constraints": "- The number of nodes in the list is n\n- 1 <= n <= 500\n- -500 <= Node.val <= 500\n- 1 <= left <= right <= n", + "readme_additional": "**Follow up:** Could you do it in one pass?", + "helpers_imports": "from leetcode_py import ListNode", + "helpers_content": "", + "helpers_run_name": "reverse_between", + "helpers_run_signature": "(solution_class: type, head_list: list[int], left: int, right: int)", + "helpers_run_body": " head = ListNode[int].from_list(head_list)\n implementation = solution_class()\n return implementation.reverse_between(head, left, right)", + "helpers_assert_name": "reverse_between", + "helpers_assert_signature": "(result: ListNode[int] | None, expected_list: list[int]) -> bool", + "helpers_assert_body": " expected = ListNode[int].from_list(expected_list)\n assert result == expected\n return True", + "solution_imports": "from leetcode_py import ListNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_reverse_between, run_reverse_between\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ReverseLinkedListII", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "reverse_between", + "signature": "(self, head: ListNode[int] | None, left: int, right: int) -> ListNode[int] | None", + "body": " # TODO: Implement reverse_between\n return None" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_reverse_between", + "signature": "(self, head_list: list[int], left: int, right: int, expected_list: list[int])", + "parametrize": "head_list, left, right, expected_list", + "test_cases": "[([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]), ([5], 1, 1, [5]), ([1, 2], 1, 2, [2, 1]), ([1, 2, 3], 1, 3, [3, 2, 1]), ([1, 2, 3, 4, 5], 1, 5, [5, 4, 3, 2, 1]), ([1, 2, 3, 4, 5], 3, 3, [1, 2, 3, 4, 5])]", + "body": " result = run_reverse_between(Solution, head_list, left, right)\n assert_reverse_between(result, expected_list)" + } + ] + }, + "playground_imports": "from helpers import run_reverse_between, assert_reverse_between\nfrom solution import Solution\nfrom leetcode_py import ListNode", + "playground_setup": "# Example test case\nhead_list = [1, 2, 3, 4, 5]\nleft, right = 2, 4\nexpected_list = [1, 4, 3, 2, 5]", + "playground_run": "result = run_reverse_between(Solution, head_list, left, right)\nresult", + "playground_assert": "assert_reverse_between(result, expected_list)" +} diff --git a/.templates/leetcode/json/rotting_oranges.json b/.templates/leetcode/json/rotting_oranges.json new file mode 100644 index 0000000..6081ed4 --- /dev/null +++ b/.templates/leetcode/json/rotting_oranges.json @@ -0,0 +1,67 @@ +{ + "problem_name": "rotting_oranges", + "solution_class_name": "Solution", + "problem_number": "994", + "problem_title": "Rotting Oranges", + "difficulty": "Medium", + "topics": "Array, Breadth-First Search, Matrix", + "_tags": { "list": ["grind-75"] }, + "readme_description": "You are given an `m x n` `grid` where each cell can have one of three values:\n\n- `0` representing an empty cell,\n- `1` representing a fresh orange, or\n- `2` representing a rotten orange.\n\nEvery minute, any fresh orange that is **4-directionally adjacent** to a rotten orange becomes rotten.\n\nReturn *the minimum number of minutes that must elapse until no cell has a fresh orange*. If *this is impossible, return* `-1`.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2019/02/16/oranges.png)\n\n```\nInput: grid = [[2,1,1],[1,1,0],[0,1,1]]\nOutput: 4\n```" + }, + { + "content": "```\nInput: grid = [[2,1,1],[0,1,1],[1,0,1]]\nOutput: -1\n```\n**Explanation:** The orange in the bottom left corner (row 2, column 0) is never rotten, because rotting only happens 4-directionally." + }, + { + "content": "```\nInput: grid = [[0,2]]\nOutput: 0\n```\n**Explanation:** Since there are already no fresh oranges at minute 0, the answer is just 0." + } + ] + }, + "readme_constraints": "- `m == grid.length`\n- `n == grid[i].length`\n- `1 <= m, n <= 10`\n- `grid[i][j]` is `0`, `1`, or `2`.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "oranges_rotting", + "helpers_run_signature": "(solution_class: type, grid: list[list[int]])", + "helpers_run_body": " implementation = solution_class()\n return implementation.oranges_rotting(grid)", + "helpers_assert_name": "oranges_rotting", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_oranges_rotting, run_oranges_rotting\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "RottingOranges", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "oranges_rotting", + "signature": "(self, grid: list[list[int]]) -> int", + "body": " # TODO: Implement oranges_rotting\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_oranges_rotting", + "signature": "(self, grid: list[list[int]], expected: int)", + "parametrize": "grid, expected", + "test_cases": "[([[2, 1, 1], [1, 1, 0], [0, 1, 1]], 4), ([[2, 1, 1], [0, 1, 1], [1, 0, 1]], -1), ([[0, 2]], 0), ([[0]], 0), ([[1]], -1), ([[2]], 0), ([[1, 2]], 1), ([[2, 1]], 1), ([[0, 1, 2]], 1), ([[2, 2], [1, 1], [0, 0]], 1), ([[2, 1, 1], [1, 1, 1], [0, 1, 2]], 2)]", + "body": " result = run_oranges_rotting(Solution, grid)\n assert_oranges_rotting(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_oranges_rotting, assert_oranges_rotting\nfrom solution import Solution", + "playground_setup": "# Example test case\ngrid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]\nexpected = 4", + "playground_run": "result = run_oranges_rotting(Solution, grid)\nresult", + "playground_assert": "assert_oranges_rotting(result, expected)" +} diff --git a/.templates/leetcode/json/search_in_rotated_sorted_array.json b/.templates/leetcode/json/search_in_rotated_sorted_array.json new file mode 100644 index 0000000..9f2165b --- /dev/null +++ b/.templates/leetcode/json/search_in_rotated_sorted_array.json @@ -0,0 +1,61 @@ +{ + "problem_name": "search_in_rotated_sorted_array", + "solution_class_name": "Solution", + "problem_number": "33", + "problem_title": "Search in Rotated Sorted Array", + "difficulty": "Medium", + "topics": "Array, Binary Search", + "_tags": { "list": ["grind-75"] }, + "readme_description": "There is an integer array `nums` sorted in ascending order (with **distinct** values).\n\nPrior to being passed to your function, `nums` is **possibly left rotated** at an unknown index `k` (`1 <= k < nums.length`) such that the resulting array is `[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]` (**0-indexed**). For example, `[0,1,2,4,5,6,7]` might be left rotated by 3 indices and become `[4,5,6,7,0,1,2]`.\n\nGiven the array `nums` **after** the possible rotation and an integer `target`, return *the index of* `target` *if it is in* `nums`*, or* `-1` *if it is not in* `nums`.\n\nYou must write an algorithm with `O(log n)` runtime complexity.", + "_readme_examples": { + "list": [ + { "content": "```\nInput: nums = [4,5,6,7,0,1,2], target = 0\nOutput: 4\n```" }, + { "content": "```\nInput: nums = [4,5,6,7,0,1,2], target = 3\nOutput: -1\n```" }, + { "content": "```\nInput: nums = [1], target = 0\nOutput: -1\n```" } + ] + }, + "readme_constraints": "- `1 <= nums.length <= 5000`\n- `-10^4 <= nums[i] <= 10^4`\n- All values of `nums` are **unique**.\n- `nums` is an ascending array that is possibly rotated.\n- `-10^4 <= target <= 10^4`", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "search", + "helpers_run_signature": "(solution_class: type, nums: list[int], target: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.search(nums, target)", + "helpers_assert_name": "search", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_search, run_search\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "SearchInRotatedSortedArray", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "search", + "signature": "(self, nums: list[int], target: int) -> int", + "body": " # TODO: Implement search\n return -1" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_search", + "signature": "(self, nums: list[int], target: int, expected: int)", + "parametrize": "nums, target, expected", + "test_cases": "[([4, 5, 6, 7, 0, 1, 2], 0, 4), ([4, 5, 6, 7, 0, 1, 2], 3, -1), ([1], 0, -1), ([1], 1, 0), ([3, 1], 1, 1), ([1, 3], 3, 1), ([2, 1], 2, 0), ([5, 1, 3], 3, 2), ([4, 5, 6, 7, 8, 1, 2, 3], 8, 4)]", + "body": " result = run_search(Solution, nums, target)\n assert_search(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_search, assert_search\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [4, 5, 6, 7, 0, 1, 2]\ntarget = 0\nexpected = 4", + "playground_run": "result = run_search(Solution, nums, target)\nresult", + "playground_assert": "assert_search(result, expected)" +} diff --git a/.templates/leetcode/json/serialize_and_deserialize_binary_tree.json b/.templates/leetcode/json/serialize_and_deserialize_binary_tree.json new file mode 100644 index 0000000..552c8b7 --- /dev/null +++ b/.templates/leetcode/json/serialize_and_deserialize_binary_tree.json @@ -0,0 +1,70 @@ +{ + "problem_name": "serialize_and_deserialize_binary_tree", + "solution_class_name": "Codec", + "problem_number": "297", + "problem_title": "Serialize and Deserialize Binary Tree", + "difficulty": "Hard", + "topics": "String, Tree, Depth-First Search, Breadth-First Search, Design, Binary Tree", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.\n\nDesign an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.\n\n**Clarification:** The input/output format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2020/09/15/serdeser.jpg)\n\n```\nInput: root = [1,2,3,null,null,4,5]\nOutput: [1,2,3,null,null,4,5]\n```" + }, + { "content": "```\nInput: root = []\nOutput: []\n```" } + ] + }, + "readme_constraints": "- The number of nodes in the tree is in the range [0, 10^4].\n- -1000 <= Node.val <= 1000", + "readme_additional": "", + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "", + "helpers_run_name": "serialize_deserialize", + "helpers_run_signature": "(solution_class: type, root_list: list[int | None])", + "helpers_run_body": " root = TreeNode[int].from_list(root_list) if root_list else None\n codec = solution_class()\n serialized = codec.serialize(root)\n deserialized = codec.deserialize(serialized)\n return deserialized", + "helpers_assert_name": "serialize_deserialize", + "helpers_assert_signature": "(result: TreeNode[int] | None, expected_list: list[int | None]) -> bool", + "helpers_assert_body": " expected = TreeNode[int].from_list(expected_list) if expected_list else None\n if expected is None:\n assert result is None\n else:\n assert result is not None\n assert result.to_list() == expected.to_list()\n return True", + "solution_imports": "from leetcode_py import TreeNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_serialize_deserialize, run_serialize_deserialize\nfrom .solution import Codec", + "test_content": "", + "test_class_name": "SerializeAndDeserializeBinaryTree", + "test_class_content": "", + "_solution_methods": { + "list": [ + { + "name": "__init__", + "signature": "(self) -> None", + "body": " # TODO: Initialize\n pass" + }, + { + "name": "serialize", + "signature": "(self, root: TreeNode[int] | None) -> str", + "body": " # TODO: Implement serialize\n return ''" + }, + { + "name": "deserialize", + "signature": "(self, data: str) -> TreeNode[int] | None", + "body": " # TODO: Implement deserialize\n return None" + } + ] + }, + "_test_helper_methods": { "list": [] }, + "_test_methods": { + "list": [ + { + "name": "test_serialize_deserialize", + "signature": "(self, root_list: list[int | None])", + "parametrize": "root_list", + "test_cases": "[([1, 2, 3, None, None, 4, 5]), ([]), ([1]), ([1, 2]), ([1, None, 2]), ([1, 2, 3, 4, 5, 6, 7]), ([5, 2, 3, None, None, 2, 4, 3, 1])]", + "body": " result = run_serialize_deserialize(Codec, root_list)\n assert_serialize_deserialize(result, root_list)" + } + ] + }, + "playground_imports": "from helpers import run_serialize_deserialize, assert_serialize_deserialize\nfrom solution import Codec\nfrom leetcode_py import TreeNode", + "playground_setup": "# Example test case\nroot_list = [1, 2, 3, None, None, 4, 5]", + "playground_run": "result = run_serialize_deserialize(Codec, root_list)\nresult", + "playground_assert": "assert_serialize_deserialize(result, root_list)" +} diff --git a/.templates/leetcode/json/sort_colors.json b/.templates/leetcode/json/sort_colors.json new file mode 100644 index 0000000..3525a54 --- /dev/null +++ b/.templates/leetcode/json/sort_colors.json @@ -0,0 +1,60 @@ +{ + "problem_name": "sort_colors", + "solution_class_name": "Solution", + "problem_number": "75", + "problem_title": "Sort Colors", + "difficulty": "Medium", + "topics": "Array, Two Pointers, Sorting", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an array `nums` with `n` objects colored red, white, or blue, sort them **in-place** so that objects of the same color are adjacent, with the colors in the order red, white, and blue.\n\nWe will use the integers `0`, `1`, and `2` to represent the color red, white, and blue, respectively.\n\nYou must solve this problem without using the library's sort function.", + "_readme_examples": { + "list": [ + { "content": "```\nInput: nums = [2,0,2,1,1,0]\nOutput: [0,0,1,1,2,2]\n```" }, + { "content": "```\nInput: nums = [2,0,1]\nOutput: [0,1,2]\n```" } + ] + }, + "readme_constraints": "- `n == nums.length`\n- `1 <= n <= 300`\n- `nums[i]` is either `0`, `1`, or `2`.", + "readme_additional": "**Follow up:** Could you come up with a one-pass algorithm using only constant extra space?", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "sort_colors", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " nums_copy = nums.copy()\n implementation = solution_class()\n implementation.sort_colors(nums_copy)\n return nums_copy", + "helpers_assert_name": "sort_colors", + "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_sort_colors, run_sort_colors\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "SortColors", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "sort_colors", + "signature": "(self, nums: list[int]) -> None", + "body": " # TODO: Implement sort_colors\n pass" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_sort_colors", + "signature": "(self, nums: list[int], expected: list[int])", + "parametrize": "nums, expected", + "test_cases": "[([2, 0, 2, 1, 1, 0], [0, 0, 1, 1, 2, 2]), ([2, 0, 1], [0, 1, 2]), ([0], [0]), ([1], [1]), ([2], [2]), ([0, 1, 2], [0, 1, 2]), ([2, 2, 2], [2, 2, 2]), ([0, 0, 0], [0, 0, 0]), ([1, 1, 1], [1, 1, 1])]", + "body": " result = run_sort_colors(Solution, nums)\n assert_sort_colors(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_sort_colors, assert_sort_colors\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [2, 0, 2, 1, 1, 0]\nexpected = [0, 0, 1, 1, 2, 2]", + "playground_run": "result = run_sort_colors(Solution, nums)\nresult", + "playground_assert": "assert_sort_colors(result, expected)" +} diff --git a/.templates/leetcode/json/spiral_matrix.json b/.templates/leetcode/json/spiral_matrix.json new file mode 100644 index 0000000..db80ac1 --- /dev/null +++ b/.templates/leetcode/json/spiral_matrix.json @@ -0,0 +1,64 @@ +{ + "problem_name": "spiral_matrix", + "solution_class_name": "Solution", + "problem_number": "54", + "problem_title": "Spiral Matrix", + "difficulty": "Medium", + "topics": "Array, Matrix, Simulation", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an `m x n` matrix, return all elements of the matrix in spiral order.", + "_readme_examples": { + "list": [ + { + "content": "\"\"\n\n```\nInput: matrix = [[1,2,3],[4,5,6],[7,8,9]]\nOutput: [1,2,3,6,9,8,7,4,5]\n```" + }, + { + "content": "\"\"\n\n```\nInput: matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]\nOutput: [1,2,3,4,8,12,11,10,9,5,6,7]\n```" + } + ] + }, + "readme_constraints": "- m == matrix.length\n- n == matrix[i].length\n- 1 <= m, n <= 10\n- -100 <= matrix[i][j] <= 100", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "spiral_order", + "helpers_run_signature": "(solution_class: type, matrix: list[list[int]])", + "helpers_run_body": " implementation = solution_class()\n return implementation.spiral_order(matrix)", + "helpers_assert_name": "spiral_order", + "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_spiral_order, run_spiral_order\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "SpiralMatrix", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "spiral_order", + "signature": "(self, matrix: list[list[int]]) -> list[int]", + "body": " # TODO: Implement spiral_order\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_spiral_order", + "signature": "(self, matrix: list[list[int]], expected: list[int])", + "parametrize": "matrix, expected", + "test_cases": "[([[1,2,3],[4,5,6],[7,8,9]], [1,2,3,6,9,8,7,4,5]), ([[1,2,3,4],[5,6,7,8],[9,10,11,12]], [1,2,3,4,8,12,11,10,9,5,6,7]), ([[1]], [1]), ([[1,2]], [1,2]), ([[1],[2]], [1,2]), ([[1,2,3]], [1,2,3]), ([[1],[2],[3]], [1,2,3])]", + "body": " result = run_spiral_order(Solution, matrix)\n assert_spiral_order(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_spiral_order, assert_spiral_order\nfrom solution import Solution", + "playground_setup": "# Example test case\nmatrix = [[1,2,3],[4,5,6],[7,8,9]]\nexpected = [1,2,3,6,9,8,7,4,5]", + "playground_run": "result = run_spiral_order(Solution, matrix)\nresult", + "playground_assert": "assert_spiral_order(result, expected)" +} diff --git a/.templates/leetcode/json/string_to_integer_atoi.json b/.templates/leetcode/json/string_to_integer_atoi.json new file mode 100644 index 0000000..781b2c8 --- /dev/null +++ b/.templates/leetcode/json/string_to_integer_atoi.json @@ -0,0 +1,73 @@ +{ + "problem_name": "string_to_integer_atoi", + "solution_class_name": "Solution", + "problem_number": "8", + "problem_title": "String to Integer (atoi)", + "difficulty": "Medium", + "topics": "String", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Implement the `my_atoi(string s)` function, which converts a string to a 32-bit signed integer.\n\nThe algorithm for `my_atoi(string s)` is as follows:\n\n1. **Whitespace**: Ignore any leading whitespace (` `).\n2. **Signedness**: Determine the sign by checking if the next character is `-` or `+`, assuming positivity if neither present.\n3. **Conversion**: Read the integer by skipping leading zeros until a non-digit character is encountered or the end of the string is reached. If no digits were read, then the result is 0.\n4. **Rounding**: If the integer is out of the 32-bit signed integer range `[-2^31, 2^31 - 1]`, then round the integer to remain in the range. Specifically, integers less than `-2^31` should be rounded to `-2^31`, and integers greater than `2^31 - 1` should be rounded to `2^31 - 1`.\n\nReturn the integer as the final result.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: s = \"42\"\nOutput: 42\n```\n**Explanation:**\n```\nThe underlined characters are what is read in and the caret is the current reader position.\nStep 1: \"42\" (no characters read because there is no leading whitespace)\n ^\nStep 2: \"42\" (no characters read because there is neither a '-' nor '+')\n ^\nStep 3: \"42\" (\"42\" is read in)\n ^\n```" + }, + { + "content": "```\nInput: s = \" -042\"\nOutput: -42\n```\n**Explanation:**\n```\nStep 1: \" -042\" (leading whitespace is read and ignored)\n ^\nStep 2: \" -042\" ('-' is read, so the result should be negative)\n ^\nStep 3: \" -042\" (\"042\" is read in, leading zeros ignored in the result)\n ^\n```" + }, + { + "content": "```\nInput: s = \"1337c0d3\"\nOutput: 1337\n```\n**Explanation:**\n```\nStep 1: \"1337c0d3\" (no characters read because there is no leading whitespace)\n ^\nStep 2: \"1337c0d3\" (no characters read because there is neither a '-' nor '+')\n ^\nStep 3: \"1337c0d3\" (\"1337\" is read in; reading stops because the next character is a non-digit)\n ^\n```" + }, + { + "content": "```\nInput: s = \"0-1\"\nOutput: 0\n```\n**Explanation:**\n```\nStep 1: \"0-1\" (no characters read because there is no leading whitespace)\n ^\nStep 2: \"0-1\" (no characters read because there is neither a '-' nor '+')\n ^\nStep 3: \"0-1\" (\"0\" is read in; reading stops because the next character is a non-digit)\n ^\n```" + }, + { + "content": "```\nInput: s = \"words and 987\"\nOutput: 0\n```\n**Explanation:** Reading stops at the first non-digit character 'w'." + } + ] + }, + "readme_constraints": "- `0 <= s.length <= 200`\n- `s` consists of English letters (lower-case and upper-case), digits (0-9), ` `, `+`, `-`, and `.`.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "my_atoi", + "helpers_run_signature": "(solution_class: type, s: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.my_atoi(s)", + "helpers_assert_name": "my_atoi", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_my_atoi, run_my_atoi\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "StringToIntegerAtoi", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "my_atoi", + "signature": "(self, s: str) -> int", + "body": " # TODO: Implement my_atoi\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_my_atoi", + "signature": "(self, s: str, expected: int)", + "parametrize": "s, expected", + "test_cases": "[('42', 42), (' -042', -42), ('1337c0d3', 1337), ('0-1', 0), ('words and 987', 0), ('', 0), (' ', 0), ('+1', 1), ('-1', -1), ('2147483647', 2147483647), ('-2147483648', -2147483648), ('2147483648', 2147483647), ('-2147483649', -2147483648)]", + "body": " result = run_my_atoi(Solution, s)\n assert_my_atoi(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_my_atoi, assert_my_atoi\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = '42'\nexpected = 42", + "playground_run": "result = run_my_atoi(Solution, s)\nresult", + "playground_assert": "assert_my_atoi(result, expected)" +} diff --git a/.templates/leetcode/json/task_scheduler.json b/.templates/leetcode/json/task_scheduler.json new file mode 100644 index 0000000..908512f --- /dev/null +++ b/.templates/leetcode/json/task_scheduler.json @@ -0,0 +1,67 @@ +{ + "problem_name": "task_scheduler", + "solution_class_name": "Solution", + "problem_number": "621", + "problem_title": "Task Scheduler", + "difficulty": "Medium", + "topics": "Array, Hash Table, Greedy, Sorting, Heap (Priority Queue), Counting", + "_tags": { "list": ["grind-75"] }, + "readme_description": "You are given an array of CPU `tasks`, each labeled with a letter from A to Z, and a number `n`. Each CPU interval can be idle or allow the completion of one task. Tasks can be completed in any order, but there's a constraint: there has to be a gap of **at least** `n` intervals between two tasks with the same label.\n\nReturn the **minimum** number of CPU intervals required to complete all tasks.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: tasks = [\"A\",\"A\",\"A\",\"B\",\"B\",\"B\"], n = 2\nOutput: 8\n```\n**Explanation:** A possible sequence is: A -> B -> idle -> A -> B -> idle -> A -> B.\n\nAfter completing task A, you must wait two intervals before doing A again. The same applies to task B. In the 3rd interval, neither A nor B can be done, so you idle. By the 4th interval, you can do A again as 2 intervals have passed." + }, + { + "content": "```\nInput: tasks = [\"A\",\"C\",\"A\",\"B\",\"D\",\"B\"], n = 1\nOutput: 6\n```\n**Explanation:** A possible sequence is: A -> B -> C -> D -> A -> B.\n\nWith a cooling interval of 1, you can repeat a task after just one other task." + }, + { + "content": "```\nInput: tasks = [\"A\",\"A\",\"A\", \"B\",\"B\",\"B\"], n = 3\nOutput: 10\n```\n**Explanation:** A possible sequence is: A -> B -> idle -> idle -> A -> B -> idle -> idle -> A -> B.\n\nThere are only two types of tasks, A and B, which need to be separated by 3 intervals. This leads to idling twice between repetitions of these tasks." + } + ] + }, + "readme_constraints": "- `1 <= tasks.length <= 10^4`\n- `tasks[i]` is an uppercase English letter.\n- `0 <= n <= 100`", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "least_interval", + "helpers_run_signature": "(solution_class: type, tasks: list[str], n: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.least_interval(tasks, n)", + "helpers_assert_name": "least_interval", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_least_interval, run_least_interval\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "TaskScheduler", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "least_interval", + "signature": "(self, tasks: list[str], n: int) -> int", + "body": " # TODO: Implement least_interval\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_least_interval", + "signature": "(self, tasks: list[str], n: int, expected: int)", + "parametrize": "tasks, n, expected", + "test_cases": "[(['A', 'A', 'A', 'B', 'B', 'B'], 2, 8), (['A', 'C', 'A', 'B', 'D', 'B'], 1, 6), (['A', 'A', 'A', 'B', 'B', 'B'], 3, 10), (['A'], 0, 1), (['A', 'A'], 1, 3), (['A', 'B'], 0, 2)]", + "body": " result = run_least_interval(Solution, tasks, n)\n assert_least_interval(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_least_interval, assert_least_interval\nfrom solution import Solution", + "playground_setup": "# Example test case\ntasks = ['A', 'A', 'A', 'B', 'B', 'B']\nn = 2\nexpected = 8", + "playground_run": "result = run_least_interval(Solution, tasks, n)\nresult", + "playground_assert": "assert_least_interval(result, expected)" +} diff --git a/.templates/leetcode/json/three_sum.json b/.templates/leetcode/json/three_sum.json new file mode 100644 index 0000000..e80a8d6 --- /dev/null +++ b/.templates/leetcode/json/three_sum.json @@ -0,0 +1,67 @@ +{ + "problem_name": "three_sum", + "solution_class_name": "Solution", + "problem_number": "15", + "problem_title": "3Sum", + "difficulty": "Medium", + "topics": "Array, Two Pointers, Sorting", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an integer array `nums`, return all the triplets `[nums[i], nums[j], nums[k]]` such that `i != j`, `i != k`, and `j != k`, and `nums[i] + nums[j] + nums[k] == 0`.\n\nNotice that the solution set must not contain duplicate triplets.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums = [-1,0,1,2,-1,-4]\nOutput: [[-1,-1,2],[-1,0,1]]\n```\n**Explanation:** \nnums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0.\nnums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0.\nnums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0.\nThe distinct triplets are [-1,0,1] and [-1,-1,2].\nNotice that the order of the output and the order of the triplets does not matter." + }, + { + "content": "```\nInput: nums = [0,1,1]\nOutput: []\n```\n**Explanation:** The only possible triplet does not sum up to 0." + }, + { + "content": "```\nInput: nums = [0,0,0]\nOutput: [[0,0,0]]\n```\n**Explanation:** The only possible triplet sums up to 0." + } + ] + }, + "readme_constraints": "- 3 <= nums.length <= 3000\n- -10^5 <= nums[i] <= 10^5", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "three_sum", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.three_sum(nums)", + "helpers_assert_name": "three_sum", + "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool", + "helpers_assert_body": " # Sort both result and expected for comparison since order doesn't matter\n result_sorted = [sorted(triplet) for triplet in result]\n expected_sorted = [sorted(triplet) for triplet in expected]\n result_sorted.sort()\n expected_sorted.sort()\n assert result_sorted == expected_sorted\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_three_sum, run_three_sum\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ThreeSum", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "three_sum", + "signature": "(self, nums: list[int]) -> list[list[int]]", + "body": " # TODO: Implement three_sum\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_three_sum", + "signature": "(self, nums: list[int], expected: list[list[int]])", + "parametrize": "nums, expected", + "test_cases": "[([-1, 0, 1, 2, -1, -4], [[-1, -1, 2], [-1, 0, 1]]), ([0, 1, 1], []), ([0, 0, 0], [[0, 0, 0]]), ([-1, 0, 1], [[-1, 0, 1]]), ([1, 2, -2, -1], []), ([-2, 0, 1, 1, 2], [[-2, 0, 2], [-2, 1, 1]])]", + "body": " result = run_three_sum(Solution, nums)\n assert_three_sum(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_three_sum, assert_three_sum\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [-1, 0, 1, 2, -1, -4]\nexpected = [[-1, -1, 2], [-1, 0, 1]]", + "playground_run": "result = run_three_sum(Solution, nums)\nresult", + "playground_assert": "assert_three_sum(result, expected)" +} diff --git a/.templates/leetcode/json/time_based_key_value_store.json b/.templates/leetcode/json/time_based_key_value_store.json new file mode 100644 index 0000000..3bdbbe2 --- /dev/null +++ b/.templates/leetcode/json/time_based_key_value_store.json @@ -0,0 +1,69 @@ +{ + "problem_name": "time_based_key_value_store", + "solution_class_name": "TimeMap", + "problem_number": "981", + "problem_title": "Time Based Key-Value Store", + "difficulty": "Medium", + "topics": "Hash Table, String, Binary Search, Design", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Design a time-based key-value data structure that can store multiple values for the same key at different time stamps and retrieve the key's value at a certain timestamp.\n\nImplement the `TimeMap` class:\n\n- `TimeMap()` Initializes the object of the data structure.\n- `void set(String key, String value, int timestamp)` Stores the key `key` with the value `value` at the given time `timestamp`.\n- `String get(String key, int timestamp)` Returns a value such that `set` was called previously, with `timestamp_prev <= timestamp`. If there are multiple such values, it returns the value associated with the largest `timestamp_prev`. If there are no values, it returns `\"\"`.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput\n[\"TimeMap\", \"set\", \"get\", \"get\", \"set\", \"get\", \"get\"]\n[[], [\"foo\", \"bar\", 1], [\"foo\", 1], [\"foo\", 3], [\"foo\", \"bar2\", 4], [\"foo\", 4], [\"foo\", 5]]\nOutput\n[null, null, \"bar\", \"bar\", null, \"bar2\", \"bar2\"]\n```\n\n**Explanation:**\n```\nTimeMap timeMap = new TimeMap();\ntimeMap.set(\"foo\", \"bar\", 1); // store the key \"foo\" and value \"bar\" along with timestamp = 1.\ntimeMap.get(\"foo\", 1); // return \"bar\"\ntimeMap.get(\"foo\", 3); // return \"bar\", since there is no value corresponding to foo at timestamp 3 and timestamp 2, then the only value is at timestamp 1 is \"bar\".\ntimeMap.set(\"foo\", \"bar2\", 4); // store the key \"foo\" and value \"bar2\" along with timestamp = 4.\ntimeMap.get(\"foo\", 4); // return \"bar2\"\ntimeMap.get(\"foo\", 5); // return \"bar2\"\n```" + } + ] + }, + "readme_constraints": "- `1 <= key.length, value.length <= 100`\n- `key` and `value` consist of lowercase English letters and digits.\n- `1 <= timestamp <= 10^7`\n- All the timestamps `timestamp` of `set` are strictly increasing.\n- At most `2 * 10^5` calls will be made to `set` and `get`.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "time_map_operations", + "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list])", + "helpers_run_body": " time_map = None\n results = []\n for i, op in enumerate(operations):\n if op == 'TimeMap':\n time_map = solution_class()\n results.append(None)\n elif op == 'set' and time_map is not None:\n time_map.set(*inputs[i])\n results.append(None)\n elif op == 'get' and time_map is not None:\n results.append(time_map.get(*inputs[i]))\n return results, time_map", + "helpers_assert_name": "time_map_operations", + "helpers_assert_signature": "(result: list, expected: list) -> bool", + "helpers_assert_body": " results, _ = result\n assert results == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_time_map_operations, run_time_map_operations\nfrom .solution import TimeMap", + "test_content": "", + "test_class_name": "TimeBasedKeyValueStore", + "test_class_content": "", + "_solution_methods": { + "list": [ + { + "name": "__init__", + "signature": "(self) -> None", + "body": " # TODO: Initialize\n pass" + }, + { + "name": "set", + "signature": "(self, key: str, value: str, timestamp: int) -> None", + "body": " # TODO: Implement set\n pass" + }, + { + "name": "get", + "signature": "(self, key: str, timestamp: int) -> str", + "body": " # TODO: Implement get\n return \"\"" + } + ] + }, + "_test_helper_methods": { "list": [] }, + "_test_methods": { + "list": [ + { + "name": "test_time_map_operations", + "signature": "(self, operations: list[str], inputs: list[list], expected: list)", + "parametrize": "operations, inputs, expected", + "test_cases": "[(['TimeMap', 'set', 'get', 'get', 'set', 'get', 'get'], [[], ['foo', 'bar', 1], ['foo', 1], ['foo', 3], ['foo', 'bar2', 4], ['foo', 4], ['foo', 5]], [None, None, 'bar', 'bar', None, 'bar2', 'bar2'])]", + "body": " result = run_time_map_operations(TimeMap, operations, inputs)\n assert_time_map_operations(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_time_map_operations, assert_time_map_operations\nfrom solution import TimeMap", + "playground_setup": "# Example test case\noperations = ['TimeMap', 'set', 'get', 'get', 'set', 'get', 'get']\ninputs = [[], ['foo', 'bar', 1], ['foo', 1], ['foo', 3], ['foo', 'bar2', 4], ['foo', 4], ['foo', 5]]\nexpected = [None, None, 'bar', 'bar', None, 'bar2', 'bar2']", + "playground_run": "result = run_time_map_operations(TimeMap, operations, inputs)\nresult", + "playground_assert": "assert_time_map_operations(result, expected)" +} diff --git a/.templates/leetcode/json/trapping_rain_water.json b/.templates/leetcode/json/trapping_rain_water.json new file mode 100644 index 0000000..5b44338 --- /dev/null +++ b/.templates/leetcode/json/trapping_rain_water.json @@ -0,0 +1,62 @@ +{ + "problem_name": "trapping_rain_water", + "solution_class_name": "Solution", + "problem_number": "42", + "problem_title": "Trapping Rain Water", + "difficulty": "Hard", + "topics": "Array, Two Pointers, Dynamic Programming, Stack, Monotonic Stack", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given `n` non-negative integers representing an elevation map where the width of each bar is `1`, compute how much water it can trap after raining.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2018/10/22/rainwatertrap.png)\n\n```\nInput: height = [0,1,0,2,1,0,1,3,2,1,2,1]\nOutput: 6\n```\n**Explanation:** The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped." + }, + { "content": "```\nInput: height = [4,2,0,3,2,5]\nOutput: 9\n```" } + ] + }, + "readme_constraints": "- `n == height.length`\n- `1 <= n <= 2 * 10^4`\n- `0 <= height[i] <= 10^5`", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "trap", + "helpers_run_signature": "(solution_class: type, height: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.trap(height)", + "helpers_assert_name": "trap", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_trap, run_trap\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "TrappingRainWater", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "trap", + "signature": "(self, height: list[int]) -> int", + "body": " # TODO: Implement trap\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_trap", + "signature": "(self, height: list[int], expected: int)", + "parametrize": "height, expected", + "test_cases": "[([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6), ([4, 2, 0, 3, 2, 5], 9), ([3, 0, 2, 0, 4], 7), ([0], 0), ([1], 0), ([1, 2], 0), ([2, 1], 0)]", + "body": " result = run_trap(Solution, height)\n assert_trap(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_trap, assert_trap\nfrom solution import Solution", + "playground_setup": "# Example test case\nheight = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]\nexpected = 6", + "playground_run": "result = run_trap(Solution, height)\nresult", + "playground_assert": "assert_trap(result, expected)" +} diff --git a/.templates/leetcode/json/valid_palindrome.json b/.templates/leetcode/json/valid_palindrome.json new file mode 100644 index 0000000..3e264eb --- /dev/null +++ b/.templates/leetcode/json/valid_palindrome.json @@ -0,0 +1,67 @@ +{ + "problem_name": "valid_palindrome", + "solution_class_name": "Solution", + "problem_number": "125", + "problem_title": "Valid Palindrome", + "difficulty": "Easy", + "topics": "Two Pointers, String", + "_tags": { "list": ["grind-75"] }, + "readme_description": "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.\n\nGiven a string `s`, return `true` if it is a **palindrome**, or `false` otherwise.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: s = \"A man, a plan, a canal: Panama\"\nOutput: true\n```\n**Explanation:** \"amanaplanacanalpanama\" is a palindrome." + }, + { + "content": "```\nInput: s = \"race a car\"\nOutput: false\n```\n**Explanation:** \"raceacar\" is not a palindrome." + }, + { + "content": "```\nInput: s = \" \"\nOutput: true\n```\n**Explanation:** s is an empty string \"\" after removing non-alphanumeric characters. Since an empty string reads the same forward and backward, it is a palindrome." + } + ] + }, + "readme_constraints": "- `1 <= s.length <= 2 * 10^5`\n- `s` consists only of printable ASCII characters.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "is_palindrome", + "helpers_run_signature": "(solution_class: type, s: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.is_palindrome(s)", + "helpers_assert_name": "is_palindrome", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_is_palindrome, run_is_palindrome\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ValidPalindrome", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "is_palindrome", + "signature": "(self, s: str) -> bool", + "body": " # TODO: Implement is_palindrome\n return False" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_is_palindrome", + "signature": "(self, s: str, expected: bool)", + "parametrize": "s, expected", + "test_cases": "[(\"A man, a plan, a canal: Panama\", True), (\"race a car\", False), (\" \", True), (\"\", True), (\"a\", True), (\"Madam\", True), (\"No 'x' in Nixon\", True), (\"Mr. Owl ate my metal worm\", True)]", + "body": " result = run_is_palindrome(Solution, s)\n assert_is_palindrome(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_is_palindrome, assert_is_palindrome\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = \"A man, a plan, a canal: Panama\"\nexpected = True", + "playground_run": "result = run_is_palindrome(Solution, s)\nresult", + "playground_assert": "assert_is_palindrome(result, expected)" +} diff --git a/.templates/leetcode/json/valid_parentheses.json b/.templates/leetcode/json/valid_parentheses.json new file mode 100644 index 0000000..cc3c986 --- /dev/null +++ b/.templates/leetcode/json/valid_parentheses.json @@ -0,0 +1,63 @@ +{ + "problem_name": "valid_parentheses", + "solution_class_name": "Solution", + "problem_number": "20", + "problem_title": "Valid Parentheses", + "difficulty": "Easy", + "topics": "String, Stack", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given a string `s` containing just the characters `'('`, `')'`, `'{'`, `'}'`, `'['` and `']'`, determine if the input string is valid.\n\nAn input string is valid if:\n\n1. Open brackets must be closed by the same type of brackets.\n2. Open brackets must be closed in the correct order.\n3. Every close bracket has a corresponding open bracket of the same type.", + "_readme_examples": { + "list": [ + { "content": "```\nInput: s = \"()\"\nOutput: true\n```" }, + { "content": "```\nInput: s = \"()[]{}\"\nOutput: true\n```" }, + { "content": "```\nInput: s = \"(]\"\nOutput: false\n```" }, + { "content": "```\nInput: s = \"([])\"\nOutput: true\n```" }, + { "content": "```\nInput: s = \"([)]\"\nOutput: false\n```" } + ] + }, + "readme_constraints": "- `1 <= s.length <= 10^4`\n- `s` consists of parentheses only `'()[]{}'`.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "is_valid", + "helpers_run_signature": "(solution_class: type, s: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.is_valid(s)", + "helpers_assert_name": "is_valid", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_is_valid, run_is_valid\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ValidParentheses", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "is_valid", + "signature": "(self, s: str) -> bool", + "body": " # TODO: Implement is_valid\n return False" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_is_valid", + "signature": "(self, s: str, expected: bool)", + "parametrize": "s, expected", + "test_cases": "[('()', True), ('()[]{}', True), ('(]', False), ('([])', True), ('([)]', False), ('', True), ('(', False), (')', False), ('{[()]}', True), ('{[(])}', False)]", + "body": " result = run_is_valid(Solution, s)\n assert_is_valid(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_is_valid, assert_is_valid\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = '()'\nexpected = True", + "playground_run": "result = run_is_valid(Solution, s)\nresult", + "playground_assert": "assert_is_valid(result, expected)" +} diff --git a/.templates/leetcode/json/validate_binary_search_tree.json b/.templates/leetcode/json/validate_binary_search_tree.json new file mode 100644 index 0000000..ef141e2 --- /dev/null +++ b/.templates/leetcode/json/validate_binary_search_tree.json @@ -0,0 +1,64 @@ +{ + "problem_name": "validate_binary_search_tree", + "solution_class_name": "Solution", + "problem_number": "98", + "problem_title": "Validate Binary Search Tree", + "difficulty": "Medium", + "topics": "Tree, Depth-First Search, Binary Search Tree, Binary Tree", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given the `root` of a binary tree, determine if it is a valid binary search tree (BST).\n\nA **valid BST** is defined as follows:\n\n- The left subtree of a node contains only nodes with keys **strictly less than** the node's key.\n- The right subtree of a node contains only nodes with keys **strictly greater than** the node's key.\n- Both the left and right subtrees must also be binary search trees.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2020/12/01/tree1.jpg)\n\n```\nInput: root = [2,1,3]\nOutput: true\n```" + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2020/12/01/tree2.jpg)\n\n```\nInput: root = [5,1,4,null,null,3,6]\nOutput: false\n```\n**Explanation:** The root node's value is 5 but its right child's value is 4." + } + ] + }, + "readme_constraints": "- The number of nodes in the tree is in the range `[1, 10^4]`.\n- `-2^31 <= Node.val <= 2^31 - 1`", + "readme_additional": "", + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "", + "helpers_run_name": "is_valid_bst", + "helpers_run_signature": "(solution_class: type, root_list: list[int | None])", + "helpers_run_body": " root = TreeNode[int].from_list(root_list) if root_list else None\n implementation = solution_class()\n return implementation.is_valid_bst(root)", + "helpers_assert_name": "is_valid_bst", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "from leetcode_py import TreeNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_is_valid_bst, run_is_valid_bst\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ValidateBinarySearchTree", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "is_valid_bst", + "signature": "(self, root: TreeNode[int] | None) -> bool", + "body": " # TODO: Implement is_valid_bst\n return False" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_is_valid_bst", + "signature": "(self, root_list: list[int | None], expected: bool)", + "parametrize": "root_list, expected", + "test_cases": "[([2, 1, 3], True), ([5, 1, 4, None, None, 3, 6], False), ([1], True), ([1, 1], False), ([10, 5, 15, None, None, 6, 20], False), ([2, 1, 3, None, None, None, 4], True)]", + "body": " result = run_is_valid_bst(Solution, root_list)\n assert_is_valid_bst(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_is_valid_bst, assert_is_valid_bst\nfrom solution import Solution\nfrom leetcode_py import TreeNode", + "playground_setup": "# Example test case\nroot_list = [2, 1, 3]\nexpected = True", + "playground_run": "result = run_is_valid_bst(Solution, root_list)\nresult", + "playground_assert": "assert_is_valid_bst(result, expected)" +} diff --git a/.templates/leetcode/json/word_break.json b/.templates/leetcode/json/word_break.json new file mode 100644 index 0000000..1a0f5c0 --- /dev/null +++ b/.templates/leetcode/json/word_break.json @@ -0,0 +1,67 @@ +{ + "problem_name": "word_break", + "solution_class_name": "Solution", + "problem_number": "139", + "problem_title": "Word Break", + "difficulty": "Medium", + "topics": "Array, Hash Table, String, Dynamic Programming, Trie, Memoization", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given a string `s` and a dictionary of strings `wordDict`, return `true` if `s` can be segmented into a space-separated sequence of one or more dictionary words.\n\n**Note** that the same word in the dictionary may be reused multiple times in the segmentation.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: s = \"leetcode\", wordDict = [\"leet\",\"code\"]\nOutput: true\n```\n**Explanation:** Return true because \"leetcode\" can be segmented as \"leet code\"." + }, + { + "content": "```\nInput: s = \"applepenapple\", wordDict = [\"apple\",\"pen\"]\nOutput: true\n```\n**Explanation:** Return true because \"applepenapple\" can be segmented as \"apple pen apple\".\nNote that you are allowed to reuse a dictionary word." + }, + { + "content": "```\nInput: s = \"catsandog\", wordDict = [\"cats\",\"dog\",\"sand\",\"and\",\"cat\"]\nOutput: false\n```" + } + ] + }, + "readme_constraints": "- `1 <= s.length <= 300`\n- `1 <= wordDict.length <= 1000`\n- `1 <= wordDict[i].length <= 20`\n- `s` and `wordDict[i]` consist of only lowercase English letters.\n- All the strings of `wordDict` are **unique**.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "word_break", + "helpers_run_signature": "(solution_class: type, s: str, word_dict: list[str])", + "helpers_run_body": " implementation = solution_class()\n return implementation.word_break(s, word_dict)", + "helpers_assert_name": "word_break", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_word_break, run_word_break\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "WordBreak", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "word_break", + "signature": "(self, s: str, word_dict: list[str]) -> bool", + "body": " # TODO: Implement word_break\n return False" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_word_break", + "signature": "(self, s: str, word_dict: list[str], expected: bool)", + "parametrize": "s, word_dict, expected", + "test_cases": "[('leetcode', ['leet', 'code'], True), ('applepenapple', ['apple', 'pen'], True), ('catsandog', ['cats', 'dog', 'sand', 'and', 'cat'], False), ('', [], True), ('a', ['a'], True), ('ab', ['a', 'b'], True), ('abcd', ['a', 'abc', 'd'], True)]", + "body": " result = run_word_break(Solution, s, word_dict)\n assert_word_break(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_word_break, assert_word_break\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = 'leetcode'\nword_dict = ['leet', 'code']\nexpected = True", + "playground_run": "result = run_word_break(Solution, s, word_dict)\nresult", + "playground_assert": "assert_word_break(result, expected)" +} diff --git a/.templates/leetcode/json/word_ladder.json b/.templates/leetcode/json/word_ladder.json new file mode 100644 index 0000000..78cf3f5 --- /dev/null +++ b/.templates/leetcode/json/word_ladder.json @@ -0,0 +1,64 @@ +{ + "problem_name": "word_ladder", + "solution_class_name": "Solution", + "problem_number": "127", + "problem_title": "Word Ladder", + "difficulty": "Hard", + "topics": "Hash Table, String, Breadth-First Search", + "_tags": { "list": ["grind-75"] }, + "readme_description": "A **transformation sequence** from word `beginWord` to word `endWord` using a dictionary `wordList` is a sequence of words `beginWord -> s1 -> s2 -> ... -> sk` such that:\n\n- Every adjacent pair of words differs by a single letter.\n- Every `si` for `1 <= i <= k` is in `wordList`. Note that `beginWord` does not need to be in `wordList`.\n- `sk == endWord`\n\nGiven two words, `beginWord` and `endWord`, and a dictionary `wordList`, return the **number of words** in the **shortest transformation sequence** from `beginWord` to `endWord`, or `0` if no such sequence exists.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: beginWord = \"hit\", endWord = \"cog\", wordList = [\"hot\",\"dot\",\"dog\",\"lot\",\"log\",\"cog\"]\nOutput: 5\n```\n**Explanation:** One shortest transformation sequence is \"hit\" -> \"hot\" -> \"dot\" -> \"dog\" -> \"cog\", which is 5 words long." + }, + { + "content": "```\nInput: beginWord = \"hit\", endWord = \"cog\", wordList = [\"hot\",\"dot\",\"dog\",\"lot\",\"log\"]\nOutput: 0\n```\n**Explanation:** The endWord \"cog\" is not in wordList, therefore there is no valid transformation sequence." + } + ] + }, + "readme_constraints": "- 1 <= beginWord.length <= 10\n- endWord.length == beginWord.length\n- 1 <= wordList.length <= 5000\n- wordList[i].length == beginWord.length\n- beginWord, endWord, and wordList[i] consist of lowercase English letters.\n- beginWord != endWord\n- All the words in wordList are unique.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "ladder_length", + "helpers_run_signature": "(solution_class: type, begin_word: str, end_word: str, word_list: list[str])", + "helpers_run_body": " implementation = solution_class()\n return implementation.ladder_length(begin_word, end_word, word_list)", + "helpers_assert_name": "ladder_length", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_ladder_length, run_ladder_length\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "WordLadder", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "ladder_length", + "signature": "(self, begin_word: str, end_word: str, word_list: list[str]) -> int", + "body": " # TODO: Implement ladder_length\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_ladder_length", + "signature": "(self, begin_word: str, end_word: str, word_list: list[str], expected: int)", + "parametrize": "begin_word, end_word, word_list, expected", + "test_cases": "[('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log', 'cog'], 5), ('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log'], 0), ('a', 'c', ['a', 'b', 'c'], 2), ('hot', 'dog', ['hot', 'dog'], 0), ('hot', 'dog', ['hot', 'hog', 'dog'], 3)]", + "body": " result = run_ladder_length(Solution, begin_word, end_word, word_list)\n assert_ladder_length(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_ladder_length, assert_ladder_length\nfrom solution import Solution", + "playground_setup": "# Example test case\nbegin_word = 'hit'\nend_word = 'cog'\nword_list = ['hot', 'dot', 'dog', 'lot', 'log', 'cog']\nexpected = 5", + "playground_run": "result = run_ladder_length(Solution, begin_word, end_word, word_list)\nresult", + "playground_assert": "assert_ladder_length(result, expected)" +} diff --git a/.templates/leetcode/json/zero_one_matrix.json b/.templates/leetcode/json/zero_one_matrix.json new file mode 100644 index 0000000..81ab126 --- /dev/null +++ b/.templates/leetcode/json/zero_one_matrix.json @@ -0,0 +1,64 @@ +{ + "problem_name": "zero_one_matrix", + "solution_class_name": "Solution", + "problem_number": "542", + "problem_title": "01 Matrix", + "difficulty": "Medium", + "topics": "Array, Dynamic Programming, Breadth-First Search, Matrix", + "_tags": { "list": ["grind-75"] }, + "readme_description": "Given an `m x n` binary matrix `mat`, return the distance of the nearest `0` for each cell.\n\nThe distance between two cells sharing a common edge is `1`.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2021/04/24/01-1-grid.jpg)\n\n```\nInput: mat = [[0,0,0],[0,1,0],[0,0,0]]\nOutput: [[0,0,0],[0,1,0],[0,0,0]]\n```" + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2021/04/24/01-2-grid.jpg)\n\n```\nInput: mat = [[0,0,0],[0,1,0],[1,1,1]]\nOutput: [[0,0,0],[0,1,0],[1,2,1]]\n```" + } + ] + }, + "readme_constraints": "- `m == mat.length`\n- `n == mat[i].length`\n- `1 <= m, n <= 10^4`\n- `1 <= m * n <= 10^4`\n- `mat[i][j]` is either `0` or `1`\n- There is at least one `0` in `mat`", + "readme_additional": "**Note:** This question is the same as 1765: [Map of Highest Peak](https://leetcode.com/problems/map-of-highest-peak/)", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "update_matrix", + "helpers_run_signature": "(solution_class: type, mat: list[list[int]])", + "helpers_run_body": " implementation = solution_class()\n return implementation.update_matrix(mat)", + "helpers_assert_name": "update_matrix", + "helpers_assert_signature": "(result: list[list[int]], expected: list[list[int]]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_update_matrix, run_update_matrix\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ZeroOneMatrix", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "update_matrix", + "signature": "(self, mat: list[list[int]]) -> list[list[int]]", + "body": " # TODO: Implement update_matrix\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_update_matrix", + "signature": "(self, mat: list[list[int]], expected: list[list[int]])", + "parametrize": "mat, expected", + "test_cases": "[([[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]]), ([[0, 0, 0], [0, 1, 0], [1, 1, 1]], [[0, 0, 0], [0, 1, 0], [1, 2, 1]]), ([[0]], [[0]]), ([[1, 0]], [[1, 0]])]", + "body": " result = run_update_matrix(Solution, mat)\n assert_update_matrix(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_update_matrix, assert_update_matrix\nfrom solution import Solution", + "playground_setup": "# Example test case\nmat = [[0, 0, 0], [0, 1, 0], [1, 1, 1]]\nexpected = [[0, 0, 0], [0, 1, 0], [1, 2, 1]]", + "playground_run": "result = run_update_matrix(Solution, mat)\nresult", + "playground_assert": "assert_update_matrix(result, expected)" +} diff --git a/leetcode/permutations/README.md b/leetcode/permutations/README.md new file mode 100644 index 0000000..69459b5 --- /dev/null +++ b/leetcode/permutations/README.md @@ -0,0 +1,40 @@ +# Permutations + +**Difficulty:** Medium +**Topics:** Array, Backtracking +**Tags:** grind-75 + +**LeetCode:** [Problem 46](https://leetcode.com/problems/permutations/description/) + +## Problem Description + +Given an array `nums` of distinct integers, return all the possible permutations. You can return the answer in any order. + +## Examples + +### Example 1: + +``` +Input: nums = [1,2,3] +Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] +``` + +### Example 2: + +``` +Input: nums = [0,1] +Output: [[0,1],[1,0]] +``` + +### Example 3: + +``` +Input: nums = [1] +Output: [[1]] +``` + +## Constraints + +- 1 <= nums.length <= 6 +- -10 <= nums[i] <= 10 +- All the integers of nums are unique. diff --git a/leetcode/permutations/__init__.py b/leetcode/permutations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/permutations/helpers.py b/leetcode/permutations/helpers.py new file mode 100644 index 0000000..6114fa1 --- /dev/null +++ b/leetcode/permutations/helpers.py @@ -0,0 +1,14 @@ +def run_permute(solution_class: type, nums: list[int]): + implementation = solution_class() + return implementation.permute(nums) + + +def assert_permute(result: list[list[int]], expected: list[list[int]]) -> bool: + # Sort both result and expected for comparison since order doesn't matter + result_sorted = [sorted(perm) for perm in result] + expected_sorted = [sorted(perm) for perm in expected] + result_sorted.sort() + expected_sorted.sort() + assert len(result) == len(expected) + assert result_sorted == expected_sorted + return True diff --git a/leetcode/permutations/playground.ipynb b/leetcode/permutations/playground.ipynb new file mode 100644 index 0000000..3df2f29 --- /dev/null +++ b/leetcode/permutations/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_permute, run_permute\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [1, 2, 3]\n", + "expected = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_permute(Solution, nums)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_permute(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/permutations/solution.py b/leetcode/permutations/solution.py new file mode 100644 index 0000000..c71dcd0 --- /dev/null +++ b/leetcode/permutations/solution.py @@ -0,0 +1,19 @@ +class Solution: + + # Time: O(n! * n) + # Space: O(n! * n) output + O(n) recursion + def permute(self, nums: list[int]) -> list[list[int]]: + result = [] + + def backtrack(start: int) -> None: + if start == len(nums): + result.append(nums[:]) + return + + for i in range(start, len(nums)): + nums[start], nums[i] = nums[i], nums[start] + backtrack(start + 1) + nums[start], nums[i] = nums[i], nums[start] + + backtrack(0) + return result diff --git a/leetcode/permutations/test_solution.py b/leetcode/permutations/test_solution.py new file mode 100644 index 0000000..da684e9 --- /dev/null +++ b/leetcode/permutations/test_solution.py @@ -0,0 +1,25 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_permute, run_permute +from .solution import Solution + + +class TestPermutations: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([1, 2, 3], [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]), + ([0, 1], [[0, 1], [1, 0]]), + ([1], [[1]]), + ([2, 1], [[2, 1], [1, 2]]), + ], + ) + def test_permute(self, nums: list[int], expected: list[list[int]]): + result = run_permute(Solution, nums) + assert_permute(result, expected) diff --git a/leetcode/product_of_array_except_self/README.md b/leetcode/product_of_array_except_self/README.md new file mode 100644 index 0000000..3f75f03 --- /dev/null +++ b/leetcode/product_of_array_except_self/README.md @@ -0,0 +1,39 @@ +# Product of Array Except Self + +**Difficulty:** Medium +**Topics:** Array, Prefix Sum +**Tags:** grind-75 + +**LeetCode:** [Problem 238](https://leetcode.com/problems/product-of-array-except-self/description/) + +## Problem Description + +Given an integer array `nums`, return an array `answer` such that `answer[i]` is equal to the product of all the elements of `nums` except `nums[i]`. + +The product of any prefix or suffix of `nums` is guaranteed to fit in a 32-bit integer. + +You must write an algorithm that runs in O(n) time and without using the division operation. + +## Examples + +### Example 1: + +``` +Input: nums = [1,2,3,4] +Output: [24,12,8,6] +``` + +### Example 2: + +``` +Input: nums = [-1,1,0,-3,3] +Output: [0,0,9,0,0] +``` + +## Constraints + +- 2 <= nums.length <= 10^5 +- -30 <= nums[i] <= 30 +- The input is generated such that answer[i] is guaranteed to fit in a 32-bit integer. + +**Follow up:** Can you solve the problem in O(1) extra space complexity? (The output array does not count as extra space for space complexity analysis.) diff --git a/leetcode/product_of_array_except_self/__init__.py b/leetcode/product_of_array_except_self/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/product_of_array_except_self/helpers.py b/leetcode/product_of_array_except_self/helpers.py new file mode 100644 index 0000000..c43546a --- /dev/null +++ b/leetcode/product_of_array_except_self/helpers.py @@ -0,0 +1,8 @@ +def run_product_except_self(solution_class: type, nums: list[int]): + implementation = solution_class() + return implementation.product_except_self(nums) + + +def assert_product_except_self(result: list[int], expected: list[int]) -> bool: + assert result == expected + return True diff --git a/leetcode/product_of_array_except_self/playground.ipynb b/leetcode/product_of_array_except_self/playground.ipynb new file mode 100644 index 0000000..c96f1f3 --- /dev/null +++ b/leetcode/product_of_array_except_self/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_product_except_self, run_product_except_self\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [1, 2, 3, 4]\n", + "expected = [24, 12, 8, 6]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_product_except_self(Solution, nums)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_product_except_self(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/product_of_array_except_self/solution.py b/leetcode/product_of_array_except_self/solution.py new file mode 100644 index 0000000..0265649 --- /dev/null +++ b/leetcode/product_of_array_except_self/solution.py @@ -0,0 +1,26 @@ +class Solution: + + # Time: O(n) + # Space: O(1) + def product_except_self(self, nums: list[int]) -> list[int]: + # Example: nums = [1, 2, 3, 4] + # Expected output: [24, 12, 8, 6] + + n = len(nums) + result = [1] * n # [1, 1, 1, 1] + + # Left pass: result[i] = product of all elements to the left of i + # nums: [1, 2, 3, 4] + # result: [1, 1, 2, 6] (left products) + for i in range(1, n): + result[i] = result[i - 1] * nums[i - 1] + + # Right pass: multiply by product of all elements to the right of i + # right products: [24, 12, 4, 1] + # result: [1*24, 1*12, 2*4, 6*1] = [24, 12, 8, 6] + right = 1 + for i in range(n - 1, -1, -1): + result[i] *= right + right *= nums[i] + + return result diff --git a/leetcode/product_of_array_except_self/test_solution.py b/leetcode/product_of_array_except_self/test_solution.py new file mode 100644 index 0000000..2645aff --- /dev/null +++ b/leetcode/product_of_array_except_self/test_solution.py @@ -0,0 +1,28 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_product_except_self, run_product_except_self +from .solution import Solution + + +class TestProductOfArrayExceptSelf: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([1, 2, 3, 4], [24, 12, 8, 6]), + ([-1, 1, 0, -3, 3], [0, 0, 9, 0, 0]), + ([2, 3, 4, 5], [60, 40, 30, 24]), + ([1, 1], [1, 1]), + ([5, 2], [2, 5]), + ([0, 1, 2, 3], [6, 0, 0, 0]), + ([1, 0, 3, 4], [0, 12, 0, 0]), + ], + ) + def test_product_except_self(self, nums: list[int], expected: list[int]): + result = run_product_except_self(Solution, nums) + assert_product_except_self(result, expected) diff --git a/leetcode/ransom_note/README.md b/leetcode/ransom_note/README.md new file mode 100644 index 0000000..59c1c12 --- /dev/null +++ b/leetcode/ransom_note/README.md @@ -0,0 +1,41 @@ +# Ransom Note + +**Difficulty:** Easy +**Topics:** Hash Table, String, Counting +**Tags:** grind-75 + +**LeetCode:** [Problem 383](https://leetcode.com/problems/ransom-note/description/) + +## Problem Description + +Given two strings `ransomNote` and `magazine`, return `true` if `ransomNote` can be constructed by using the letters from `magazine` and `false` otherwise. + +Each letter in `magazine` can only be used once in `ransomNote`. + +## Examples + +### Example 1: + +``` +Input: ransomNote = "a", magazine = "b" +Output: false +``` + +### Example 2: + +``` +Input: ransomNote = "aa", magazine = "ab" +Output: false +``` + +### Example 3: + +``` +Input: ransomNote = "aa", magazine = "aab" +Output: true +``` + +## Constraints + +- 1 <= ransomNote.length, magazine.length <= 10^5 +- ransomNote and magazine consist of lowercase English letters. diff --git a/leetcode/ransom_note/__init__.py b/leetcode/ransom_note/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/ransom_note/helpers.py b/leetcode/ransom_note/helpers.py new file mode 100644 index 0000000..b150ae5 --- /dev/null +++ b/leetcode/ransom_note/helpers.py @@ -0,0 +1,8 @@ +def run_can_construct(solution_class: type, ransom_note: str, magazine: str): + implementation = solution_class() + return implementation.can_construct(ransom_note, magazine) + + +def assert_can_construct(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/ransom_note/playground.ipynb b/leetcode/ransom_note/playground.ipynb new file mode 100644 index 0000000..5982636 --- /dev/null +++ b/leetcode/ransom_note/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_can_construct, run_can_construct\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "ransom_note = \"aa\"\n", + "magazine = \"aab\"\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_can_construct(Solution, ransom_note, magazine)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_can_construct(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/ransom_note/solution.py b/leetcode/ransom_note/solution.py new file mode 100644 index 0000000..f99deef --- /dev/null +++ b/leetcode/ransom_note/solution.py @@ -0,0 +1,18 @@ +from collections import Counter + + +class Solution: + # Time: O(m + n) where m = magazine length, n = ransom_note length + # Space: O(1) - at most 26 lowercase letters + def can_construct(self, ransom_note: str, magazine: str) -> bool: + if len(ransom_note) > len(magazine): + return False + + magazine_count = Counter(magazine) + + for char in ransom_note: + if magazine_count[char] == 0: + return False + magazine_count[char] -= 1 + + return True diff --git a/leetcode/ransom_note/test_solution.py b/leetcode/ransom_note/test_solution.py new file mode 100644 index 0000000..429913a --- /dev/null +++ b/leetcode/ransom_note/test_solution.py @@ -0,0 +1,32 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_can_construct, run_can_construct +from .solution import Solution + + +class TestRansomNote: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "ransom_note, magazine, expected", + [ + ("a", "b", False), + ("aa", "ab", False), + ("aa", "aab", True), + ("aab", "baa", True), + ("", "", True), + ("", "abc", True), + ("abc", "", False), + ("abc", "abc", True), + ("abc", "cba", True), + ("aaa", "aa", False), + ("ab", "ba", True), + ], + ) + def test_can_construct(self, ransom_note: str, magazine: str, expected: bool): + result = run_can_construct(Solution, ransom_note, magazine) + assert_can_construct(result, expected) diff --git a/leetcode/reverse_linked_list/README.md b/leetcode/reverse_linked_list/README.md new file mode 100644 index 0000000..ae96ddd --- /dev/null +++ b/leetcode/reverse_linked_list/README.md @@ -0,0 +1,45 @@ +# Reverse Linked List + +**Difficulty:** Easy +**Topics:** Linked List, Recursion +**Tags:** grind-75 + +**LeetCode:** [Problem 206](https://leetcode.com/problems/reverse-linked-list/description/) + +## Problem Description + +Given the `head` of a singly linked list, reverse the list, and return the reversed list. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2021/02/19/rev1ex1.jpg) + +``` +Input: head = [1,2,3,4,5] +Output: [5,4,3,2,1] +``` + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2021/02/19/rev1ex2.jpg) + +``` +Input: head = [1,2] +Output: [2,1] +``` + +### Example 3: + +``` +Input: head = [] +Output: [] +``` + +## Constraints + +- The number of nodes in the list is the range `[0, 5000]`. +- `-5000 <= Node.val <= 5000` + +**Follow up:** A linked list can be reversed either iteratively or recursively. Could you implement both? diff --git a/leetcode/reverse_linked_list/__init__.py b/leetcode/reverse_linked_list/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/reverse_linked_list/helpers.py b/leetcode/reverse_linked_list/helpers.py new file mode 100644 index 0000000..a78b0ed --- /dev/null +++ b/leetcode/reverse_linked_list/helpers.py @@ -0,0 +1,13 @@ +from leetcode_py import ListNode + + +def run_reverse_list(solution_class: type, head_list: list[int]): + head = ListNode[int].from_list(head_list) + implementation = solution_class() + return implementation.reverse_list(head) + + +def assert_reverse_list(result: ListNode[int] | None, expected_list: list[int]) -> bool: + expected = ListNode[int].from_list(expected_list) + assert result == expected + return True diff --git a/leetcode/reverse_linked_list/playground.ipynb b/leetcode/reverse_linked_list/playground.ipynb new file mode 100644 index 0000000..b6a4171 --- /dev/null +++ b/leetcode/reverse_linked_list/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_reverse_list, run_reverse_list\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import ListNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "head_list = [1, 2, 3, 4, 5]\n", + "expected_list = [5, 4, 3, 2, 1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_reverse_list(Solution, head_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_reverse_list(result, expected_list)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/reverse_linked_list/solution.py b/leetcode/reverse_linked_list/solution.py new file mode 100644 index 0000000..ed4d433 --- /dev/null +++ b/leetcode/reverse_linked_list/solution.py @@ -0,0 +1,47 @@ +from leetcode_py import ListNode + + +class Solution: + + # Time: O(n) + # Space: O(1) + def reverse_list(self, head: ListNode[int] | None) -> ListNode[int] | None: + if not head: + return None + + # Iterative approach using three pointers + # Example: [1,2,3] -> [3,2,1] + # + # Initial: prev curr + # None ↓ + # 1 -> 2 -> 3 -> None + # + prev: ListNode[int] | None = None + curr: ListNode[int] | None = head + + while curr: + # Store next node before breaking the link + next_node = curr.next + # + # prev curr next_node + # None ↓ ↓ + # 1 -> 2 -> 3 -> None + # + + # Reverse the current link + curr.next = prev + # None <- 1 2 -> 3 -> None + # prev curr next_node + # + + # Move pointers forward + prev = curr + curr = next_node + # 1 <- 2 3 -> None + # prev curr + # + + # 1 <- 2 <- 3 None + # prev curr + # prev now points to new head of reversed list + return prev diff --git a/leetcode/reverse_linked_list/test_solution.py b/leetcode/reverse_linked_list/test_solution.py new file mode 100644 index 0000000..38404d5 --- /dev/null +++ b/leetcode/reverse_linked_list/test_solution.py @@ -0,0 +1,31 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_reverse_list, run_reverse_list +from .solution import Solution + + +class TestReverseLinkedList: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "head_list, expected_list", + [ + ([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]), + ([1, 2], [2, 1]), + ([1], [1]), + ([], []), + ([1, 2, 3], [3, 2, 1]), + ([1, 2, 3, 4], [4, 3, 2, 1]), + ([-1, -2, -3], [-3, -2, -1]), + ([0], [0]), + ([5000, -5000], [-5000, 5000]), + ([1, 1, 1], [1, 1, 1]), + ], + ) + def test_reverse_list(self, head_list: list[int], expected_list: list[int]): + result = run_reverse_list(Solution, head_list) + assert_reverse_list(result, expected_list) diff --git a/leetcode/reverse_linked_list_ii/README.md b/leetcode/reverse_linked_list_ii/README.md new file mode 100644 index 0000000..1f7a098 --- /dev/null +++ b/leetcode/reverse_linked_list_ii/README.md @@ -0,0 +1,36 @@ +# Reverse Linked List II + +**Difficulty:** Medium +**Topics:** Linked List +**Tags:** + +**LeetCode:** [Problem 92](https://leetcode.com/problems/reverse-linked-list-ii/description/) + +## Problem Description + +Given the `head` of a singly linked list and two integers `left` and `right` where `left <= right`, reverse the nodes of the list from position `left` to position `right`, and return the reversed list. + +## Examples + +### Example 1: + +``` +Input: head = [1,2,3,4,5], left = 2, right = 4 +Output: [1,4,3,2,5] +``` + +### Example 2: + +``` +Input: head = [5], left = 1, right = 1 +Output: [5] +``` + +## Constraints + +- The number of nodes in the list is n +- 1 <= n <= 500 +- -500 <= Node.val <= 500 +- 1 <= left <= right <= n + +**Follow up:** Could you do it in one pass? diff --git a/leetcode/reverse_linked_list_ii/__init__.py b/leetcode/reverse_linked_list_ii/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/reverse_linked_list_ii/helpers.py b/leetcode/reverse_linked_list_ii/helpers.py new file mode 100644 index 0000000..c518e6d --- /dev/null +++ b/leetcode/reverse_linked_list_ii/helpers.py @@ -0,0 +1,13 @@ +from leetcode_py import ListNode + + +def run_reverse_between(solution_class: type, head_list: list[int], left: int, right: int): + head = ListNode[int].from_list(head_list) + implementation = solution_class() + return implementation.reverse_between(head, left, right) + + +def assert_reverse_between(result: ListNode[int] | None, expected_list: list[int]) -> bool: + expected = ListNode[int].from_list(expected_list) + assert result == expected + return True diff --git a/leetcode/reverse_linked_list_ii/playground.ipynb b/leetcode/reverse_linked_list_ii/playground.ipynb new file mode 100644 index 0000000..0cd88a4 --- /dev/null +++ b/leetcode/reverse_linked_list_ii/playground.ipynb @@ -0,0 +1,71 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_reverse_between, run_reverse_between\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import ListNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "head_list = [1, 2, 3, 4, 5]\n", + "left, right = 2, 4\n", + "expected_list = [1, 4, 3, 2, 5]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_reverse_between(Solution, head_list, left, right)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_reverse_between(result, expected_list)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/reverse_linked_list_ii/solution.py b/leetcode/reverse_linked_list_ii/solution.py new file mode 100644 index 0000000..59280dd --- /dev/null +++ b/leetcode/reverse_linked_list_ii/solution.py @@ -0,0 +1,53 @@ +from leetcode_py import ListNode + + +class Solution: + + # Time: O(n) + # Space: O(1) + def reverse_between(self, head: ListNode[int] | None, left: int, right: int) -> ListNode[int] | None: + if not head or left == right: + return head + + dummy = ListNode[int](0) + dummy.next = head + prev = dummy + + # Move to position before left + for _ in range(left - 1): + assert prev.next + prev = prev.next + + # Reverse from left to right using iterative approach + # Example: [1,2,3,4,5] left=2, right=4 -> [1,4,3,2,5] + # + # Initial: prev curr + # ↓ ↓ + # 1 -> 2 -> 3 -> 4 -> 5 + # + assert prev.next + curr = prev.next # First node to be reversed (will become last after reversal) + + # Reverse by moving nodes one by one to the front of the section + for _ in range(right - left): + assert curr.next + next_node = curr.next # Node to move to front + # + # prev curr next_node + # ↓ ↓ ↓ + # 1 -> 2 -> 3 -> 4 -> 5 + # + curr.next = next_node.next + # 1 -> 2 -----> 4 -> 5 + # 3 ↗ + # + next_node.next = prev.next + # 1 -> 2 -----> 4 -> 5 + # 3 ↗ + # + prev.next = next_node + # 1 -> 3 -> 2 -> 4 -> 5 + # prev ↑ curr + # next_node + + return dummy.next diff --git a/leetcode/reverse_linked_list_ii/test_solution.py b/leetcode/reverse_linked_list_ii/test_solution.py new file mode 100644 index 0000000..daecb86 --- /dev/null +++ b/leetcode/reverse_linked_list_ii/test_solution.py @@ -0,0 +1,29 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_reverse_between, run_reverse_between +from .solution import Solution + + +class TestReverseLinkedListII: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "head_list, left, right, expected_list", + [ + ([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]), + ([5], 1, 1, [5]), + ([1, 2], 1, 2, [2, 1]), + ([1, 2, 3], 1, 3, [3, 2, 1]), + ([1, 2, 3, 4, 5], 1, 5, [5, 4, 3, 2, 1]), + ([1, 2, 3, 4, 5], 3, 3, [1, 2, 3, 4, 5]), + ], + ) + def test_reverse_between( + self, head_list: list[int], left: int, right: int, expected_list: list[int] + ): + result = run_reverse_between(Solution, head_list, left, right) + assert_reverse_between(result, expected_list) diff --git a/leetcode/rotting_oranges/README.md b/leetcode/rotting_oranges/README.md new file mode 100644 index 0000000..36d2c82 --- /dev/null +++ b/leetcode/rotting_oranges/README.md @@ -0,0 +1,55 @@ +# Rotting Oranges + +**Difficulty:** Medium +**Topics:** Array, Breadth-First Search, Matrix +**Tags:** grind-75 + +**LeetCode:** [Problem 994](https://leetcode.com/problems/rotting-oranges/description/) + +## Problem Description + +You are given an `m x n` `grid` where each cell can have one of three values: + +- `0` representing an empty cell, +- `1` representing a fresh orange, or +- `2` representing a rotten orange. + +Every minute, any fresh orange that is **4-directionally adjacent** to a rotten orange becomes rotten. + +Return _the minimum number of minutes that must elapse until no cell has a fresh orange_. If _this is impossible, return_ `-1`. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2019/02/16/oranges.png) + +``` +Input: grid = [[2,1,1],[1,1,0],[0,1,1]] +Output: 4 +``` + +### Example 2: + +``` +Input: grid = [[2,1,1],[0,1,1],[1,0,1]] +Output: -1 +``` + +**Explanation:** The orange in the bottom left corner (row 2, column 0) is never rotten, because rotting only happens 4-directionally. + +### Example 3: + +``` +Input: grid = [[0,2]] +Output: 0 +``` + +**Explanation:** Since there are already no fresh oranges at minute 0, the answer is just 0. + +## Constraints + +- `m == grid.length` +- `n == grid[i].length` +- `1 <= m, n <= 10` +- `grid[i][j]` is `0`, `1`, or `2`. diff --git a/leetcode/rotting_oranges/__init__.py b/leetcode/rotting_oranges/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/rotting_oranges/helpers.py b/leetcode/rotting_oranges/helpers.py new file mode 100644 index 0000000..0395657 --- /dev/null +++ b/leetcode/rotting_oranges/helpers.py @@ -0,0 +1,8 @@ +def run_oranges_rotting(solution_class: type, grid: list[list[int]]): + implementation = solution_class() + return implementation.oranges_rotting(grid) + + +def assert_oranges_rotting(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/rotting_oranges/playground.ipynb b/leetcode/rotting_oranges/playground.ipynb new file mode 100644 index 0000000..fde0cf1 --- /dev/null +++ b/leetcode/rotting_oranges/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_oranges_rotting, run_oranges_rotting\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "grid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]\n", + "expected = 4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_oranges_rotting(Solution, grid)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_oranges_rotting(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/rotting_oranges/solution.py b/leetcode/rotting_oranges/solution.py new file mode 100644 index 0000000..8cdcd5e --- /dev/null +++ b/leetcode/rotting_oranges/solution.py @@ -0,0 +1,43 @@ +class Solution: + + # Time: O(m*n) + # Space: O(m*n) + def oranges_rotting(self, grid: list[list[int]]) -> int: + from collections import deque + + EMPTY, FRESH, ROTTEN = 0, 1, 2 + _ = EMPTY + + m, n = len(grid), len(grid[0]) + queue: deque[tuple[int, int]] = deque() + fresh = 0 + + # Find all rotten oranges and count fresh ones + for i in range(m): + for j in range(n): + if grid[i][j] == ROTTEN: + queue.append((i, j)) + elif grid[i][j] == FRESH: + fresh += 1 + + if fresh == 0: + return 0 + + minutes = 0 + directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] + + while queue: + size = len(queue) + for _ in range(size): + x, y = queue.popleft() + for dx, dy in directions: + nx, ny = x + dx, y + dy + if 0 <= nx < m and 0 <= ny < n and grid[nx][ny] == FRESH: + grid[nx][ny] = ROTTEN + fresh -= 1 + queue.append((nx, ny)) + + if queue: + minutes += 1 + + return minutes if fresh == 0 else -1 diff --git a/leetcode/rotting_oranges/test_solution.py b/leetcode/rotting_oranges/test_solution.py new file mode 100644 index 0000000..9710fb8 --- /dev/null +++ b/leetcode/rotting_oranges/test_solution.py @@ -0,0 +1,32 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_oranges_rotting, run_oranges_rotting +from .solution import Solution + + +class TestRottingOranges: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "grid, expected", + [ + ([[2, 1, 1], [1, 1, 0], [0, 1, 1]], 4), + ([[2, 1, 1], [0, 1, 1], [1, 0, 1]], -1), + ([[0, 2]], 0), + ([[0]], 0), + ([[1]], -1), + ([[2]], 0), + ([[1, 2]], 1), + ([[2, 1]], 1), + ([[0, 1, 2]], 1), + ([[2, 2], [1, 1], [0, 0]], 1), + ([[2, 1, 1], [1, 1, 1], [0, 1, 2]], 2), + ], + ) + def test_oranges_rotting(self, grid: list[list[int]], expected: int): + result = run_oranges_rotting(Solution, grid) + assert_oranges_rotting(result, expected) diff --git a/leetcode/search_in_rotated_sorted_array/README.md b/leetcode/search_in_rotated_sorted_array/README.md new file mode 100644 index 0000000..f74397c --- /dev/null +++ b/leetcode/search_in_rotated_sorted_array/README.md @@ -0,0 +1,48 @@ +# Search in Rotated Sorted Array + +**Difficulty:** Medium +**Topics:** Array, Binary Search +**Tags:** grind-75 + +**LeetCode:** [Problem 33](https://leetcode.com/problems/search-in-rotated-sorted-array/description/) + +## Problem Description + +There is an integer array `nums` sorted in ascending order (with **distinct** values). + +Prior to being passed to your function, `nums` is **possibly left rotated** at an unknown index `k` (`1 <= k < nums.length`) such that the resulting array is `[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]` (**0-indexed**). For example, `[0,1,2,4,5,6,7]` might be left rotated by 3 indices and become `[4,5,6,7,0,1,2]`. + +Given the array `nums` **after** the possible rotation and an integer `target`, return _the index of_ `target` _if it is in_ `nums`_, or_ `-1` _if it is not in_ `nums`. + +You must write an algorithm with `O(log n)` runtime complexity. + +## Examples + +### Example 1: + +``` +Input: nums = [4,5,6,7,0,1,2], target = 0 +Output: 4 +``` + +### Example 2: + +``` +Input: nums = [4,5,6,7,0,1,2], target = 3 +Output: -1 +``` + +### Example 3: + +``` +Input: nums = [1], target = 0 +Output: -1 +``` + +## Constraints + +- `1 <= nums.length <= 5000` +- `-10^4 <= nums[i] <= 10^4` +- All values of `nums` are **unique**. +- `nums` is an ascending array that is possibly rotated. +- `-10^4 <= target <= 10^4` diff --git a/leetcode/search_in_rotated_sorted_array/__init__.py b/leetcode/search_in_rotated_sorted_array/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/search_in_rotated_sorted_array/helpers.py b/leetcode/search_in_rotated_sorted_array/helpers.py new file mode 100644 index 0000000..70f8aee --- /dev/null +++ b/leetcode/search_in_rotated_sorted_array/helpers.py @@ -0,0 +1,8 @@ +def run_search(solution_class: type, nums: list[int], target: int): + implementation = solution_class() + return implementation.search(nums, target) + + +def assert_search(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/search_in_rotated_sorted_array/playground.ipynb b/leetcode/search_in_rotated_sorted_array/playground.ipynb new file mode 100644 index 0000000..6dadf29 --- /dev/null +++ b/leetcode/search_in_rotated_sorted_array/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_search, run_search\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [4, 5, 6, 7, 0, 1, 2]\n", + "target = 0\n", + "expected = 4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_search(Solution, nums, target)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_search(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/search_in_rotated_sorted_array/solution.py b/leetcode/search_in_rotated_sorted_array/solution.py new file mode 100644 index 0000000..8de19ca --- /dev/null +++ b/leetcode/search_in_rotated_sorted_array/solution.py @@ -0,0 +1,27 @@ +class Solution: + + # Time: O(log n) + # Space: O(1) + def search(self, nums: list[int], target: int) -> int: + left, right = 0, len(nums) - 1 + + while left <= right: + mid = (left + right) // 2 + + if nums[mid] == target: + return mid + + # Left half is sorted + if nums[left] <= nums[mid]: + if nums[left] <= target < nums[mid]: + right = mid - 1 + else: + left = mid + 1 + # Right half is sorted + else: + if nums[mid] < target <= nums[right]: + left = mid + 1 + else: + right = mid - 1 + + return -1 diff --git a/leetcode/search_in_rotated_sorted_array/test_solution.py b/leetcode/search_in_rotated_sorted_array/test_solution.py new file mode 100644 index 0000000..c63c101 --- /dev/null +++ b/leetcode/search_in_rotated_sorted_array/test_solution.py @@ -0,0 +1,30 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_search, run_search +from .solution import Solution + + +class TestSearchInRotatedSortedArray: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, target, expected", + [ + ([4, 5, 6, 7, 0, 1, 2], 0, 4), + ([4, 5, 6, 7, 0, 1, 2], 3, -1), + ([1], 0, -1), + ([1], 1, 0), + ([3, 1], 1, 1), + ([1, 3], 3, 1), + ([2, 1], 2, 0), + ([5, 1, 3], 3, 2), + ([4, 5, 6, 7, 8, 1, 2, 3], 8, 4), + ], + ) + def test_search(self, nums: list[int], target: int, expected: int): + result = run_search(Solution, nums, target) + assert_search(result, expected) diff --git a/leetcode/serialize_and_deserialize_binary_tree/README.md b/leetcode/serialize_and_deserialize_binary_tree/README.md new file mode 100644 index 0000000..8226e6c --- /dev/null +++ b/leetcode/serialize_and_deserialize_binary_tree/README.md @@ -0,0 +1,38 @@ +# Serialize and Deserialize Binary Tree + +**Difficulty:** Hard +**Topics:** String, Tree, Depth-First Search, Breadth-First Search, Design, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 297](https://leetcode.com/problems/serialize-and-deserialize-binary-tree/description/) + +## Problem Description + +Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment. + +Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure. + +**Clarification:** The input/output format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2020/09/15/serdeser.jpg) + +``` +Input: root = [1,2,3,null,null,4,5] +Output: [1,2,3,null,null,4,5] +``` + +### Example 2: + +``` +Input: root = [] +Output: [] +``` + +## Constraints + +- The number of nodes in the tree is in the range [0, 10^4]. +- -1000 <= Node.val <= 1000 diff --git a/leetcode/serialize_and_deserialize_binary_tree/__init__.py b/leetcode/serialize_and_deserialize_binary_tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/serialize_and_deserialize_binary_tree/helpers.py b/leetcode/serialize_and_deserialize_binary_tree/helpers.py new file mode 100644 index 0000000..62809b6 --- /dev/null +++ b/leetcode/serialize_and_deserialize_binary_tree/helpers.py @@ -0,0 +1,19 @@ +from leetcode_py import TreeNode + + +def run_serialize_deserialize(solution_class: type, root_list: list[int | None]): + root = TreeNode[int].from_list(root_list) if root_list else None + codec = solution_class() + serialized = codec.serialize(root) + deserialized = codec.deserialize(serialized) + return deserialized + + +def assert_serialize_deserialize(result: TreeNode[int] | None, expected_list: list[int | None]) -> bool: + expected = TreeNode[int].from_list(expected_list) if expected_list else None + if expected is None: + assert result is None + else: + assert result is not None + assert result.to_list() == expected.to_list() + return True diff --git a/leetcode/serialize_and_deserialize_binary_tree/playground.ipynb b/leetcode/serialize_and_deserialize_binary_tree/playground.ipynb new file mode 100644 index 0000000..a1b0bf6 --- /dev/null +++ b/leetcode/serialize_and_deserialize_binary_tree/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_serialize_deserialize, run_serialize_deserialize\n", + "from solution import Codec\n", + "\n", + "from leetcode_py import TreeNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root_list = [1, 2, 3, None, None, 4, 5]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_serialize_deserialize(Codec, root_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_serialize_deserialize(result, root_list)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/serialize_and_deserialize_binary_tree/solution.py b/leetcode/serialize_and_deserialize_binary_tree/solution.py new file mode 100644 index 0000000..680e164 --- /dev/null +++ b/leetcode/serialize_and_deserialize_binary_tree/solution.py @@ -0,0 +1,41 @@ +from leetcode_py import TreeNode + + +class Codec: + # Preorder with Null Markers + # Time: O(n) + # Space: O(n) + def __init__(self) -> None: + pass + + # Time: O(n) + # Space: O(n) + def serialize(self, root: TreeNode[int] | None) -> str: + vals = [] + + def dfs(node: TreeNode[int] | None): + if not node: + vals.append("#") + return + vals.append(str(node.val)) + dfs(node.left) + dfs(node.right) + + dfs(root) + return ",".join(vals) + + # Time: O(n) + # Space: O(n) + def deserialize(self, data: str) -> TreeNode[int] | None: + vals = iter(data.split(",")) + + def dfs(): + val = next(vals) + if val == "#": + return None + node = TreeNode[int](int(val)) + node.left = dfs() + node.right = dfs() + return node + + return dfs() diff --git a/leetcode/serialize_and_deserialize_binary_tree/test_solution.py b/leetcode/serialize_and_deserialize_binary_tree/test_solution.py new file mode 100644 index 0000000..4e523e9 --- /dev/null +++ b/leetcode/serialize_and_deserialize_binary_tree/test_solution.py @@ -0,0 +1,26 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_serialize_deserialize, run_serialize_deserialize +from .solution import Codec + + +class TestSerializeAndDeserializeBinaryTree: + + @logged_test + @pytest.mark.parametrize( + "root_list", + [ + ([1, 2, 3, None, None, 4, 5]), + ([]), + ([1]), + ([1, 2]), + ([1, None, 2]), + ([1, 2, 3, 4, 5, 6, 7]), + ([5, 2, 3, None, None, 2, 4, 3, 1]), + ], + ) + def test_serialize_deserialize(self, root_list: list[int | None]): + result = run_serialize_deserialize(Codec, root_list) + assert_serialize_deserialize(result, root_list) diff --git a/leetcode/sort_colors/README.md b/leetcode/sort_colors/README.md new file mode 100644 index 0000000..ada8cf0 --- /dev/null +++ b/leetcode/sort_colors/README.md @@ -0,0 +1,39 @@ +# Sort Colors + +**Difficulty:** Medium +**Topics:** Array, Two Pointers, Sorting +**Tags:** grind-75 + +**LeetCode:** [Problem 75](https://leetcode.com/problems/sort-colors/description/) + +## Problem Description + +Given an array `nums` with `n` objects colored red, white, or blue, sort them **in-place** so that objects of the same color are adjacent, with the colors in the order red, white, and blue. + +We will use the integers `0`, `1`, and `2` to represent the color red, white, and blue, respectively. + +You must solve this problem without using the library's sort function. + +## Examples + +### Example 1: + +``` +Input: nums = [2,0,2,1,1,0] +Output: [0,0,1,1,2,2] +``` + +### Example 2: + +``` +Input: nums = [2,0,1] +Output: [0,1,2] +``` + +## Constraints + +- `n == nums.length` +- `1 <= n <= 300` +- `nums[i]` is either `0`, `1`, or `2`. + +**Follow up:** Could you come up with a one-pass algorithm using only constant extra space? diff --git a/leetcode/sort_colors/__init__.py b/leetcode/sort_colors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/sort_colors/helpers.py b/leetcode/sort_colors/helpers.py new file mode 100644 index 0000000..5dcd8e9 --- /dev/null +++ b/leetcode/sort_colors/helpers.py @@ -0,0 +1,10 @@ +def run_sort_colors(solution_class: type, nums: list[int]): + nums_copy = nums.copy() + implementation = solution_class() + implementation.sort_colors(nums_copy) + return nums_copy + + +def assert_sort_colors(result: list[int], expected: list[int]) -> bool: + assert result == expected + return True diff --git a/leetcode/sort_colors/playground.ipynb b/leetcode/sort_colors/playground.ipynb new file mode 100644 index 0000000..b2f099f --- /dev/null +++ b/leetcode/sort_colors/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_sort_colors, run_sort_colors\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [2, 0, 2, 1, 1, 0]\n", + "expected = [0, 0, 1, 1, 2, 2]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_sort_colors(Solution, nums)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_sort_colors(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/sort_colors/solution.py b/leetcode/sort_colors/solution.py new file mode 100644 index 0000000..9e21708 --- /dev/null +++ b/leetcode/sort_colors/solution.py @@ -0,0 +1,20 @@ +class Solution: + + # Dutch National Flag Algorithm - partitions array into 3 regions using 2 pointers + # Creates: [0s][1s][2s] with left/right boundaries, mid processes unvisited elements + # Time: O(n) + # Space: O(1) + def sort_colors(self, nums: list[int]) -> None: + left = mid = 0 + right = len(nums) - 1 + + while mid <= right: + if nums[mid] == 0: + nums[left], nums[mid] = nums[mid], nums[left] + left += 1 + mid += 1 + elif nums[mid] == 1: + mid += 1 + else: + nums[mid], nums[right] = nums[right], nums[mid] + right -= 1 diff --git a/leetcode/sort_colors/test_solution.py b/leetcode/sort_colors/test_solution.py new file mode 100644 index 0000000..df38daf --- /dev/null +++ b/leetcode/sort_colors/test_solution.py @@ -0,0 +1,30 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_sort_colors, run_sort_colors +from .solution import Solution + + +class TestSortColors: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([2, 0, 2, 1, 1, 0], [0, 0, 1, 1, 2, 2]), + ([2, 0, 1], [0, 1, 2]), + ([0], [0]), + ([1], [1]), + ([2], [2]), + ([0, 1, 2], [0, 1, 2]), + ([2, 2, 2], [2, 2, 2]), + ([0, 0, 0], [0, 0, 0]), + ([1, 1, 1], [1, 1, 1]), + ], + ) + def test_sort_colors(self, nums: list[int], expected: list[int]): + result = run_sort_colors(Solution, nums) + assert_sort_colors(result, expected) diff --git a/leetcode/spiral_matrix/README.md b/leetcode/spiral_matrix/README.md new file mode 100644 index 0000000..e2aab0f --- /dev/null +++ b/leetcode/spiral_matrix/README.md @@ -0,0 +1,38 @@ +# Spiral Matrix + +**Difficulty:** Medium +**Topics:** Array, Matrix, Simulation +**Tags:** grind-75 + +**LeetCode:** [Problem 54](https://leetcode.com/problems/spiral-matrix/description/) + +## Problem Description + +Given an `m x n` matrix, return all elements of the matrix in spiral order. + +## Examples + +### Example 1: + + + +``` +Input: matrix = [[1,2,3],[4,5,6],[7,8,9]] +Output: [1,2,3,6,9,8,7,4,5] +``` + +### Example 2: + + + +``` +Input: matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] +Output: [1,2,3,4,8,12,11,10,9,5,6,7] +``` + +## Constraints + +- m == matrix.length +- n == matrix[i].length +- 1 <= m, n <= 10 +- -100 <= matrix[i][j] <= 100 diff --git a/leetcode/spiral_matrix/__init__.py b/leetcode/spiral_matrix/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/spiral_matrix/helpers.py b/leetcode/spiral_matrix/helpers.py new file mode 100644 index 0000000..5b6456f --- /dev/null +++ b/leetcode/spiral_matrix/helpers.py @@ -0,0 +1,8 @@ +def run_spiral_order(solution_class: type, matrix: list[list[int]]): + implementation = solution_class() + return implementation.spiral_order(matrix) + + +def assert_spiral_order(result: list[int], expected: list[int]) -> bool: + assert result == expected + return True diff --git a/leetcode/spiral_matrix/playground.ipynb b/leetcode/spiral_matrix/playground.ipynb new file mode 100644 index 0000000..0668a7e --- /dev/null +++ b/leetcode/spiral_matrix/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_spiral_order, run_spiral_order\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]\n", + "expected = [1, 2, 3, 6, 9, 8, 7, 4, 5]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_spiral_order(Solution, matrix)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_spiral_order(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/spiral_matrix/solution.py b/leetcode/spiral_matrix/solution.py new file mode 100644 index 0000000..276d25e --- /dev/null +++ b/leetcode/spiral_matrix/solution.py @@ -0,0 +1,42 @@ +class Solution: + + # Time: O(m*n) + # Space: O(1) + def spiral_order(self, matrix: list[list[int]]) -> list[int]: + if not matrix or not matrix[0]: + return [] + + # Check if all rows have same length + cols = len(matrix[0]) + for row in matrix: + if len(row) != cols: + raise ValueError("Invalid matrix: all rows must have same length") + + result = [] + top, bottom = 0, len(matrix) - 1 + left, right = 0, cols - 1 + + while top <= bottom and left <= right: + # Right + for c in range(left, right + 1): + result.append(matrix[top][c]) + top += 1 + + # Down + for r in range(top, bottom + 1): + result.append(matrix[r][right]) + right -= 1 + + # Left (if still valid row) + if top <= bottom: + for c in range(right, left - 1, -1): + result.append(matrix[bottom][c]) + bottom -= 1 + + # Up (if still valid column) + if left <= right: + for r in range(bottom, top - 1, -1): + result.append(matrix[r][left]) + left += 1 + + return result diff --git a/leetcode/spiral_matrix/test_solution.py b/leetcode/spiral_matrix/test_solution.py new file mode 100644 index 0000000..6d36a02 --- /dev/null +++ b/leetcode/spiral_matrix/test_solution.py @@ -0,0 +1,28 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_spiral_order, run_spiral_order +from .solution import Solution + + +class TestSpiralMatrix: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "matrix, expected", + [ + ([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [1, 2, 3, 6, 9, 8, 7, 4, 5]), + ([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]], [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]), + ([[1]], [1]), + ([[1, 2]], [1, 2]), + ([[1], [2]], [1, 2]), + ([[1, 2, 3]], [1, 2, 3]), + ([[1], [2], [3]], [1, 2, 3]), + ], + ) + def test_spiral_order(self, matrix: list[list[int]], expected: list[int]): + result = run_spiral_order(Solution, matrix) + assert_spiral_order(result, expected) diff --git a/leetcode/string_to_integer_atoi/README.md b/leetcode/string_to_integer_atoi/README.md new file mode 100644 index 0000000..513a46d --- /dev/null +++ b/leetcode/string_to_integer_atoi/README.md @@ -0,0 +1,109 @@ +# String to Integer (atoi) + +**Difficulty:** Medium +**Topics:** String +**Tags:** grind-75 + +**LeetCode:** [Problem 8](https://leetcode.com/problems/string-to-integer-atoi/description/) + +## Problem Description + +Implement the `my_atoi(string s)` function, which converts a string to a 32-bit signed integer. + +The algorithm for `my_atoi(string s)` is as follows: + +1. **Whitespace**: Ignore any leading whitespace (` `). +2. **Signedness**: Determine the sign by checking if the next character is `-` or `+`, assuming positivity if neither present. +3. **Conversion**: Read the integer by skipping leading zeros until a non-digit character is encountered or the end of the string is reached. If no digits were read, then the result is 0. +4. **Rounding**: If the integer is out of the 32-bit signed integer range `[-2^31, 2^31 - 1]`, then round the integer to remain in the range. Specifically, integers less than `-2^31` should be rounded to `-2^31`, and integers greater than `2^31 - 1` should be rounded to `2^31 - 1`. + +Return the integer as the final result. + +## Examples + +### Example 1: + +``` +Input: s = "42" +Output: 42 +``` + +**Explanation:** + +``` +The underlined characters are what is read in and the caret is the current reader position. +Step 1: "42" (no characters read because there is no leading whitespace) + ^ +Step 2: "42" (no characters read because there is neither a '-' nor '+') + ^ +Step 3: "42" ("42" is read in) + ^ +``` + +### Example 2: + +``` +Input: s = " -042" +Output: -42 +``` + +**Explanation:** + +``` +Step 1: " -042" (leading whitespace is read and ignored) + ^ +Step 2: " -042" ('-' is read, so the result should be negative) + ^ +Step 3: " -042" ("042" is read in, leading zeros ignored in the result) + ^ +``` + +### Example 3: + +``` +Input: s = "1337c0d3" +Output: 1337 +``` + +**Explanation:** + +``` +Step 1: "1337c0d3" (no characters read because there is no leading whitespace) + ^ +Step 2: "1337c0d3" (no characters read because there is neither a '-' nor '+') + ^ +Step 3: "1337c0d3" ("1337" is read in; reading stops because the next character is a non-digit) + ^ +``` + +### Example 4: + +``` +Input: s = "0-1" +Output: 0 +``` + +**Explanation:** + +``` +Step 1: "0-1" (no characters read because there is no leading whitespace) + ^ +Step 2: "0-1" (no characters read because there is neither a '-' nor '+') + ^ +Step 3: "0-1" ("0" is read in; reading stops because the next character is a non-digit) + ^ +``` + +### Example 5: + +``` +Input: s = "words and 987" +Output: 0 +``` + +**Explanation:** Reading stops at the first non-digit character 'w'. + +## Constraints + +- `0 <= s.length <= 200` +- `s` consists of English letters (lower-case and upper-case), digits (0-9), ` `, `+`, `-`, and `.`. diff --git a/leetcode/string_to_integer_atoi/__init__.py b/leetcode/string_to_integer_atoi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/string_to_integer_atoi/helpers.py b/leetcode/string_to_integer_atoi/helpers.py new file mode 100644 index 0000000..623972c --- /dev/null +++ b/leetcode/string_to_integer_atoi/helpers.py @@ -0,0 +1,8 @@ +def run_my_atoi(solution_class: type, s: str): + implementation = solution_class() + return implementation.my_atoi(s) + + +def assert_my_atoi(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/string_to_integer_atoi/playground.ipynb b/leetcode/string_to_integer_atoi/playground.ipynb new file mode 100644 index 0000000..82ecfb7 --- /dev/null +++ b/leetcode/string_to_integer_atoi/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_my_atoi, run_my_atoi\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "s = \"42\"\n", + "expected = 42" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_my_atoi(Solution, s)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_my_atoi(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/string_to_integer_atoi/solution.py b/leetcode/string_to_integer_atoi/solution.py new file mode 100644 index 0000000..7f69674 --- /dev/null +++ b/leetcode/string_to_integer_atoi/solution.py @@ -0,0 +1,31 @@ +class Solution: + + # Time: O(n) + # Space: O(1) + def my_atoi(self, s: str) -> int: + i = 0 + n = len(s) + + # Skip whitespace + while i < n and s[i] == " ": + i += 1 + + if i == n: + return 0 + + # Check sign + sign = 1 + if s[i] in {"+", "-"}: + sign = -1 if s[i] == "-" else 1 + i += 1 + + # Convert digits + result = 0 + while i < n and s[i].isdigit(): + result = result * 10 + int(s[i]) + i += 1 + + result *= sign + + # Clamp to 32-bit range + return max(-(2**31), min(2**31 - 1, result)) diff --git a/leetcode/string_to_integer_atoi/test_solution.py b/leetcode/string_to_integer_atoi/test_solution.py new file mode 100644 index 0000000..d09898c --- /dev/null +++ b/leetcode/string_to_integer_atoi/test_solution.py @@ -0,0 +1,34 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_my_atoi, run_my_atoi +from .solution import Solution + + +class TestStringToIntegerAtoi: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, expected", + [ + ("42", 42), + (" -042", -42), + ("1337c0d3", 1337), + ("0-1", 0), + ("words and 987", 0), + ("", 0), + (" ", 0), + ("+1", 1), + ("-1", -1), + ("2147483647", 2147483647), + ("-2147483648", -2147483648), + ("2147483648", 2147483647), + ("-2147483649", -2147483648), + ], + ) + def test_my_atoi(self, s: str, expected: int): + result = run_my_atoi(Solution, s) + assert_my_atoi(result, expected) diff --git a/leetcode/task_scheduler/README.md b/leetcode/task_scheduler/README.md new file mode 100644 index 0000000..be7b1aa --- /dev/null +++ b/leetcode/task_scheduler/README.md @@ -0,0 +1,54 @@ +# Task Scheduler + +**Difficulty:** Medium +**Topics:** Array, Hash Table, Greedy, Sorting, Heap (Priority Queue), Counting +**Tags:** grind-75 + +**LeetCode:** [Problem 621](https://leetcode.com/problems/task-scheduler/description/) + +## Problem Description + +You are given an array of CPU `tasks`, each labeled with a letter from A to Z, and a number `n`. Each CPU interval can be idle or allow the completion of one task. Tasks can be completed in any order, but there's a constraint: there has to be a gap of **at least** `n` intervals between two tasks with the same label. + +Return the **minimum** number of CPU intervals required to complete all tasks. + +## Examples + +### Example 1: + +``` +Input: tasks = ["A","A","A","B","B","B"], n = 2 +Output: 8 +``` + +**Explanation:** A possible sequence is: A -> B -> idle -> A -> B -> idle -> A -> B. + +After completing task A, you must wait two intervals before doing A again. The same applies to task B. In the 3rd interval, neither A nor B can be done, so you idle. By the 4th interval, you can do A again as 2 intervals have passed. + +### Example 2: + +``` +Input: tasks = ["A","C","A","B","D","B"], n = 1 +Output: 6 +``` + +**Explanation:** A possible sequence is: A -> B -> C -> D -> A -> B. + +With a cooling interval of 1, you can repeat a task after just one other task. + +### Example 3: + +``` +Input: tasks = ["A","A","A", "B","B","B"], n = 3 +Output: 10 +``` + +**Explanation:** A possible sequence is: A -> B -> idle -> idle -> A -> B -> idle -> idle -> A -> B. + +There are only two types of tasks, A and B, which need to be separated by 3 intervals. This leads to idling twice between repetitions of these tasks. + +## Constraints + +- `1 <= tasks.length <= 10^4` +- `tasks[i]` is an uppercase English letter. +- `0 <= n <= 100` diff --git a/leetcode/task_scheduler/__init__.py b/leetcode/task_scheduler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/task_scheduler/helpers.py b/leetcode/task_scheduler/helpers.py new file mode 100644 index 0000000..78a421a --- /dev/null +++ b/leetcode/task_scheduler/helpers.py @@ -0,0 +1,8 @@ +def run_least_interval(solution_class: type, tasks: list[str], n: int): + implementation = solution_class() + return implementation.least_interval(tasks, n) + + +def assert_least_interval(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/task_scheduler/playground.ipynb b/leetcode/task_scheduler/playground.ipynb new file mode 100644 index 0000000..3ad7d83 --- /dev/null +++ b/leetcode/task_scheduler/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_least_interval, run_least_interval\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "tasks = [\"A\", \"A\", \"A\", \"B\", \"B\", \"B\"]\n", + "n = 2\n", + "expected = 8" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_least_interval(Solution, tasks, n)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_least_interval(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/task_scheduler/solution.py b/leetcode/task_scheduler/solution.py new file mode 100644 index 0000000..438bc66 --- /dev/null +++ b/leetcode/task_scheduler/solution.py @@ -0,0 +1,37 @@ +class Solution: + + # Time: O(T + m) where T = len(tasks), m = unique tasks ≤ 26, so O(T) + # Space: O(m) where m ≤ 26, so O(1) + def least_interval(self, tasks: list[str], n: int) -> int: + from collections import Counter + + """ + Mathematical approach: + + Key insight: The most frequent task determines the minimum time. + + Example: tasks=["A","A","A","B","B","B"], n=2 + + 1. Find max frequency: max_freq = 3 (A and B both appear 3 times) + 2. Count tasks with max frequency: max_count = 2 (A and B) + 3. Create frame structure: + Frame: A B _ | A B _ | A B + - (max_freq - 1) complete frames of size (n + 1) + - Last frame contains only max frequency tasks + + 4. Calculate minimum intervals: + - Frame intervals: (max_freq - 1) * (n + 1) = 2 * 3 = 6 + - Plus max frequency tasks: 6 + 2 = 8 + + 5. Return max(total_tasks, calculated_min) to handle cases where + we have enough variety to fill all gaps without idle time. + """ + counts = Counter(tasks) + max_freq = max(counts.values()) + max_count = sum(1 for freq in counts.values() if freq == max_freq) + + # Minimum intervals needed based on most frequent tasks + min_intervals = (max_freq - 1) * (n + 1) + max_count + + # Return max to handle cases with sufficient task variety + return max(len(tasks), min_intervals) diff --git a/leetcode/task_scheduler/test_solution.py b/leetcode/task_scheduler/test_solution.py new file mode 100644 index 0000000..58bdcd1 --- /dev/null +++ b/leetcode/task_scheduler/test_solution.py @@ -0,0 +1,27 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_least_interval, run_least_interval +from .solution import Solution + + +class TestTaskScheduler: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "tasks, n, expected", + [ + (["A", "A", "A", "B", "B", "B"], 2, 8), + (["A", "C", "A", "B", "D", "B"], 1, 6), + (["A", "A", "A", "B", "B", "B"], 3, 10), + (["A"], 0, 1), + (["A", "A"], 1, 3), + (["A", "B"], 0, 2), + ], + ) + def test_least_interval(self, tasks: list[str], n: int, expected: int): + result = run_least_interval(Solution, tasks, n) + assert_least_interval(result, expected) diff --git a/leetcode/three_sum/README.md b/leetcode/three_sum/README.md new file mode 100644 index 0000000..c031c0e --- /dev/null +++ b/leetcode/three_sum/README.md @@ -0,0 +1,52 @@ +# 3Sum + +**Difficulty:** Medium +**Topics:** Array, Two Pointers, Sorting +**Tags:** grind-75 + +**LeetCode:** [Problem 15](https://leetcode.com/problems/three-sum/description/) + +## Problem Description + +Given an integer array `nums`, return all the triplets `[nums[i], nums[j], nums[k]]` such that `i != j`, `i != k`, and `j != k`, and `nums[i] + nums[j] + nums[k] == 0`. + +Notice that the solution set must not contain duplicate triplets. + +## Examples + +### Example 1: + +``` +Input: nums = [-1,0,1,2,-1,-4] +Output: [[-1,-1,2],[-1,0,1]] +``` + +**Explanation:** +nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0. +nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0. +nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0. +The distinct triplets are [-1,0,1] and [-1,-1,2]. +Notice that the order of the output and the order of the triplets does not matter. + +### Example 2: + +``` +Input: nums = [0,1,1] +Output: [] +``` + +**Explanation:** The only possible triplet does not sum up to 0. + +### Example 3: + +``` +Input: nums = [0,0,0] +Output: [[0,0,0]] +``` + +**Explanation:** The only possible triplet sums up to 0. + +## Constraints + +- 3 <= nums.length <= 3000 +- -10^5 <= nums[i] <= 10^5 diff --git a/leetcode/three_sum/__init__.py b/leetcode/three_sum/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/three_sum/helpers.py b/leetcode/three_sum/helpers.py new file mode 100644 index 0000000..e85c19f --- /dev/null +++ b/leetcode/three_sum/helpers.py @@ -0,0 +1,13 @@ +def run_three_sum(solution_class: type, nums: list[int]): + implementation = solution_class() + return implementation.three_sum(nums) + + +def assert_three_sum(result: list[list[int]], expected: list[list[int]]) -> bool: + # Sort both result and expected for comparison since order doesn't matter + result_sorted = [sorted(triplet) for triplet in result] + expected_sorted = [sorted(triplet) for triplet in expected] + result_sorted.sort() + expected_sorted.sort() + assert result_sorted == expected_sorted + return True diff --git a/leetcode/three_sum/playground.ipynb b/leetcode/three_sum/playground.ipynb new file mode 100644 index 0000000..ddbc41b --- /dev/null +++ b/leetcode/three_sum/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_three_sum, run_three_sum\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "nums = [-1, 0, 1, 2, -1, -4]\n", + "expected = [[-1, -1, 2], [-1, 0, 1]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_three_sum(Solution, nums)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_three_sum(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/three_sum/solution.py b/leetcode/three_sum/solution.py new file mode 100644 index 0000000..7e44a03 --- /dev/null +++ b/leetcode/three_sum/solution.py @@ -0,0 +1,24 @@ +class Solution: + + # Time: O(n^2) + # Space: O(k) where k is number of unique triplets + def three_sum(self, nums: list[int]) -> list[list[int]]: + nums.sort() + result = set() + + for i in range(len(nums) - 2): + left, right = i + 1, len(nums) - 1 + + while left < right: + total = nums[i] + nums[left] + nums[right] + + if total < 0: + left += 1 + elif total > 0: + right -= 1 + else: + result.add((nums[i], nums[left], nums[right])) + left += 1 + right -= 1 + + return [list(triplet) for triplet in result] diff --git a/leetcode/three_sum/test_solution.py b/leetcode/three_sum/test_solution.py new file mode 100644 index 0000000..9ec9f76 --- /dev/null +++ b/leetcode/three_sum/test_solution.py @@ -0,0 +1,27 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_three_sum, run_three_sum +from .solution import Solution + + +class TestThreeSum: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([-1, 0, 1, 2, -1, -4], [[-1, -1, 2], [-1, 0, 1]]), + ([0, 1, 1], []), + ([0, 0, 0], [[0, 0, 0]]), + ([-1, 0, 1], [[-1, 0, 1]]), + ([1, 2, -2, -1], []), + ([-2, 0, 1, 1, 2], [[-2, 0, 2], [-2, 1, 1]]), + ], + ) + def test_three_sum(self, nums: list[int], expected: list[list[int]]): + result = run_three_sum(Solution, nums) + assert_three_sum(result, expected) diff --git a/leetcode/time_based_key_value_store/README.md b/leetcode/time_based_key_value_store/README.md new file mode 100644 index 0000000..fd46220 --- /dev/null +++ b/leetcode/time_based_key_value_store/README.md @@ -0,0 +1,49 @@ +# Time Based Key-Value Store + +**Difficulty:** Medium +**Topics:** Hash Table, String, Binary Search, Design +**Tags:** grind-75 + +**LeetCode:** [Problem 981](https://leetcode.com/problems/time-based-key-value-store/description/) + +## Problem Description + +Design a time-based key-value data structure that can store multiple values for the same key at different time stamps and retrieve the key's value at a certain timestamp. + +Implement the `TimeMap` class: + +- `TimeMap()` Initializes the object of the data structure. +- `void set(String key, String value, int timestamp)` Stores the key `key` with the value `value` at the given time `timestamp`. +- `String get(String key, int timestamp)` Returns a value such that `set` was called previously, with `timestamp_prev <= timestamp`. If there are multiple such values, it returns the value associated with the largest `timestamp_prev`. If there are no values, it returns `""`. + +## Examples + +### Example 1: + +``` +Input +["TimeMap", "set", "get", "get", "set", "get", "get"] +[[], ["foo", "bar", 1], ["foo", 1], ["foo", 3], ["foo", "bar2", 4], ["foo", 4], ["foo", 5]] +Output +[null, null, "bar", "bar", null, "bar2", "bar2"] +``` + +**Explanation:** + +``` +TimeMap timeMap = new TimeMap(); +timeMap.set("foo", "bar", 1); // store the key "foo" and value "bar" along with timestamp = 1. +timeMap.get("foo", 1); // return "bar" +timeMap.get("foo", 3); // return "bar", since there is no value corresponding to foo at timestamp 3 and timestamp 2, then the only value is at timestamp 1 is "bar". +timeMap.set("foo", "bar2", 4); // store the key "foo" and value "bar2" along with timestamp = 4. +timeMap.get("foo", 4); // return "bar2" +timeMap.get("foo", 5); // return "bar2" +``` + +## Constraints + +- `1 <= key.length, value.length <= 100` +- `key` and `value` consist of lowercase English letters and digits. +- `1 <= timestamp <= 10^7` +- All the timestamps `timestamp` of `set` are strictly increasing. +- At most `2 * 10^5` calls will be made to `set` and `get`. diff --git a/leetcode/time_based_key_value_store/__init__.py b/leetcode/time_based_key_value_store/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/time_based_key_value_store/helpers.py b/leetcode/time_based_key_value_store/helpers.py new file mode 100644 index 0000000..05b7e89 --- /dev/null +++ b/leetcode/time_based_key_value_store/helpers.py @@ -0,0 +1,19 @@ +def run_time_map_operations(solution_class: type, operations: list[str], inputs: list[list]): + time_map = None + results: list[str | None] = [] + for i, op in enumerate(operations): + if op == "TimeMap": + time_map = solution_class() + results.append(None) + elif op == "set" and time_map is not None: + time_map.set(*inputs[i]) + results.append(None) + elif op == "get" and time_map is not None: + results.append(time_map.get(*inputs[i])) + return results, time_map + + +def assert_time_map_operations(result: list, expected: list) -> bool: + results, _ = result + assert results == expected + return True diff --git a/leetcode/time_based_key_value_store/playground.ipynb b/leetcode/time_based_key_value_store/playground.ipynb new file mode 100644 index 0000000..28f407a --- /dev/null +++ b/leetcode/time_based_key_value_store/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_time_map_operations, run_time_map_operations\n", + "from solution import TimeMap" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "operations = [\"TimeMap\", \"set\", \"get\", \"get\", \"set\", \"get\", \"get\"]\n", + "inputs = [[], [\"foo\", \"bar\", 1], [\"foo\", 1], [\"foo\", 3], [\"foo\", \"bar2\", 4], [\"foo\", 4], [\"foo\", 5]]\n", + "expected = [None, None, \"bar\", \"bar\", None, \"bar2\", \"bar2\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_time_map_operations(TimeMap, operations, inputs)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_time_map_operations(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/time_based_key_value_store/solution.py b/leetcode/time_based_key_value_store/solution.py new file mode 100644 index 0000000..cb51d7a --- /dev/null +++ b/leetcode/time_based_key_value_store/solution.py @@ -0,0 +1,32 @@ +class TimeMap: + # Time: O(1) + # Space: O(n) + def __init__(self) -> None: + self.store: dict[str, list[tuple[int, str]]] = {} + + # Time: O(1) + # Space: O(1) + def set(self, key: str, value: str, timestamp: int) -> None: + if key not in self.store: + self.store[key] = [] + self.store[key].append((timestamp, value)) + + # Time: O(log n) + # Space: O(1) + def get(self, key: str, timestamp: int) -> str: + if key not in self.store: + return "" + + values = self.store[key] + left, right = 0, len(values) - 1 + result = "" + + while left <= right: + mid = (left + right) // 2 + if values[mid][0] <= timestamp: + result = values[mid][1] + left = mid + 1 + else: + right = mid - 1 + + return result diff --git a/leetcode/time_based_key_value_store/test_solution.py b/leetcode/time_based_key_value_store/test_solution.py new file mode 100644 index 0000000..b72dd3d --- /dev/null +++ b/leetcode/time_based_key_value_store/test_solution.py @@ -0,0 +1,32 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_time_map_operations, run_time_map_operations +from .solution import TimeMap + + +class TestTimeBasedKeyValueStore: + + @logged_test + @pytest.mark.parametrize( + "operations, inputs, expected", + [ + ( + ["TimeMap", "set", "get", "get", "set", "get", "get"], + [ + [], + ["foo", "bar", 1], + ["foo", 1], + ["foo", 3], + ["foo", "bar2", 4], + ["foo", 4], + ["foo", 5], + ], + [None, None, "bar", "bar", None, "bar2", "bar2"], + ) + ], + ) + def test_time_map_operations(self, operations: list[str], inputs: list[list], expected: list): + result = run_time_map_operations(TimeMap, operations, inputs) + assert_time_map_operations(result, expected) diff --git a/leetcode/trapping_rain_water/README.md b/leetcode/trapping_rain_water/README.md new file mode 100644 index 0000000..fc3a317 --- /dev/null +++ b/leetcode/trapping_rain_water/README.md @@ -0,0 +1,37 @@ +# Trapping Rain Water + +**Difficulty:** Hard +**Topics:** Array, Two Pointers, Dynamic Programming, Stack, Monotonic Stack +**Tags:** grind-75 + +**LeetCode:** [Problem 42](https://leetcode.com/problems/trapping-rain-water/description/) + +## Problem Description + +Given `n` non-negative integers representing an elevation map where the width of each bar is `1`, compute how much water it can trap after raining. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2018/10/22/rainwatertrap.png) + +``` +Input: height = [0,1,0,2,1,0,1,3,2,1,2,1] +Output: 6 +``` + +**Explanation:** The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. + +### Example 2: + +``` +Input: height = [4,2,0,3,2,5] +Output: 9 +``` + +## Constraints + +- `n == height.length` +- `1 <= n <= 2 * 10^4` +- `0 <= height[i] <= 10^5` diff --git a/leetcode/trapping_rain_water/__init__.py b/leetcode/trapping_rain_water/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/trapping_rain_water/helpers.py b/leetcode/trapping_rain_water/helpers.py new file mode 100644 index 0000000..0fc4b5e --- /dev/null +++ b/leetcode/trapping_rain_water/helpers.py @@ -0,0 +1,8 @@ +def run_trap(solution_class: type, height: list[int]): + implementation = solution_class() + return implementation.trap(height) + + +def assert_trap(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/trapping_rain_water/playground.ipynb b/leetcode/trapping_rain_water/playground.ipynb new file mode 100644 index 0000000..b4db37d --- /dev/null +++ b/leetcode/trapping_rain_water/playground.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_trap, run_trap\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]\n", + "expected = 6" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "run", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = run_trap(Solution, height)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "assert", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "assert_trap(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/trapping_rain_water/solution.py b/leetcode/trapping_rain_water/solution.py new file mode 100644 index 0000000..2596f30 --- /dev/null +++ b/leetcode/trapping_rain_water/solution.py @@ -0,0 +1,26 @@ +class Solution: + + # Time: O(n) + # Space: O(1) + def trap(self, height: list[int]) -> int: + if not height: + return 0 + + left, right = 0, len(height) - 1 + left_max = right_max = water = 0 + + while left < right: + if height[left] < height[right]: + if height[left] >= left_max: + left_max = height[left] + else: + water += left_max - height[left] + left += 1 + else: + if height[right] >= right_max: + right_max = height[right] + else: + water += right_max - height[right] + right -= 1 + + return water diff --git a/leetcode/trapping_rain_water/test_solution.py b/leetcode/trapping_rain_water/test_solution.py new file mode 100644 index 0000000..fb1fc4c --- /dev/null +++ b/leetcode/trapping_rain_water/test_solution.py @@ -0,0 +1,28 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_trap, run_trap +from .solution import Solution + + +class TestTrappingRainWater: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "height, expected", + [ + ([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6), + ([4, 2, 0, 3, 2, 5], 9), + ([3, 0, 2, 0, 4], 7), + ([0], 0), + ([1], 0), + ([1, 2], 0), + ([2, 1], 0), + ], + ) + def test_trap(self, height: list[int], expected: int): + result = run_trap(Solution, height) + assert_trap(result, expected) diff --git a/leetcode/valid_palindrome/README.md b/leetcode/valid_palindrome/README.md new file mode 100644 index 0000000..e2444ef --- /dev/null +++ b/leetcode/valid_palindrome/README.md @@ -0,0 +1,47 @@ +# Valid Palindrome + +**Difficulty:** Easy +**Topics:** Two Pointers, String +**Tags:** grind-75 + +**LeetCode:** [Problem 125](https://leetcode.com/problems/valid-palindrome/description/) + +## Problem Description + +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. + +## Examples + +### Example 1: + +``` +Input: s = "A man, a plan, a canal: Panama" +Output: true +``` + +**Explanation:** "amanaplanacanalpanama" is a palindrome. + +### Example 2: + +``` +Input: s = "race a car" +Output: false +``` + +**Explanation:** "raceacar" is not a palindrome. + +### Example 3: + +``` +Input: s = " " +Output: true +``` + +**Explanation:** s is an empty string "" after removing non-alphanumeric characters. Since an empty string reads the same forward and backward, it is a palindrome. + +## Constraints + +- `1 <= s.length <= 2 * 10^5` +- `s` consists only of printable ASCII characters. diff --git a/leetcode/valid_palindrome/__init__.py b/leetcode/valid_palindrome/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/valid_palindrome/helpers.py b/leetcode/valid_palindrome/helpers.py new file mode 100644 index 0000000..7f7713b --- /dev/null +++ b/leetcode/valid_palindrome/helpers.py @@ -0,0 +1,8 @@ +def run_is_palindrome(solution_class: type, s: str): + implementation = solution_class() + return implementation.is_palindrome(s) + + +def assert_is_palindrome(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/valid_palindrome/playground.ipynb b/leetcode/valid_palindrome/playground.ipynb new file mode 100644 index 0000000..c4c6bbc --- /dev/null +++ b/leetcode/valid_palindrome/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_is_palindrome, run_is_palindrome\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "s = \"A man, a plan, a canal: Panama\"\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_is_palindrome(Solution, s)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_is_palindrome(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/valid_palindrome/solution.py b/leetcode/valid_palindrome/solution.py new file mode 100644 index 0000000..daefb2e --- /dev/null +++ b/leetcode/valid_palindrome/solution.py @@ -0,0 +1,20 @@ +class Solution: + + # Time: O(n) + # Space: O(1) + def is_palindrome(self, s: str) -> bool: + left, right = 0, len(s) - 1 + + while left < right: + while left < right and not s[left].isalnum(): + left += 1 + while left < right and not s[right].isalnum(): + right -= 1 + + if s[left].lower() != s[right].lower(): + return False + + left += 1 + right -= 1 + + return True diff --git a/leetcode/valid_palindrome/test_solution.py b/leetcode/valid_palindrome/test_solution.py new file mode 100644 index 0000000..f1fedb9 --- /dev/null +++ b/leetcode/valid_palindrome/test_solution.py @@ -0,0 +1,29 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_is_palindrome, run_is_palindrome +from .solution import Solution + + +class TestValidPalindrome: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, expected", + [ + ("A man, a plan, a canal: Panama", True), + ("race a car", False), + (" ", True), + ("", True), + ("a", True), + ("Madam", True), + ("No 'x' in Nixon", True), + ("Mr. Owl ate my metal worm", True), + ], + ) + def test_is_palindrome(self, s: str, expected: bool): + result = run_is_palindrome(Solution, s) + assert_is_palindrome(result, expected) diff --git a/leetcode/valid_parentheses/README.md b/leetcode/valid_parentheses/README.md new file mode 100644 index 0000000..05d24f0 --- /dev/null +++ b/leetcode/valid_parentheses/README.md @@ -0,0 +1,59 @@ +# Valid Parentheses + +**Difficulty:** Easy +**Topics:** String, Stack +**Tags:** grind-75 + +**LeetCode:** [Problem 20](https://leetcode.com/problems/valid-parentheses/description/) + +## Problem Description + +Given a string `s` containing just the characters `'('`, `')'`, `'{'`, `'}'`, `'['` and `']'`, determine if the input string is valid. + +An input string is valid if: + +1. Open brackets must be closed by the same type of brackets. +2. Open brackets must be closed in the correct order. +3. Every close bracket has a corresponding open bracket of the same type. + +## Examples + +### Example 1: + +``` +Input: s = "()" +Output: true +``` + +### Example 2: + +``` +Input: s = "()[]{}" +Output: true +``` + +### Example 3: + +``` +Input: s = "(]" +Output: false +``` + +### Example 4: + +``` +Input: s = "([])" +Output: true +``` + +### Example 5: + +``` +Input: s = "([)]" +Output: false +``` + +## Constraints + +- `1 <= s.length <= 10^4` +- `s` consists of parentheses only `'()[]{}'`. diff --git a/leetcode/valid_parentheses/__init__.py b/leetcode/valid_parentheses/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/valid_parentheses/helpers.py b/leetcode/valid_parentheses/helpers.py new file mode 100644 index 0000000..e1fec52 --- /dev/null +++ b/leetcode/valid_parentheses/helpers.py @@ -0,0 +1,8 @@ +def run_is_valid(solution_class: type, s: str): + implementation = solution_class() + return implementation.is_valid(s) + + +def assert_is_valid(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/valid_parentheses/playground.ipynb b/leetcode/valid_parentheses/playground.ipynb new file mode 100644 index 0000000..e5324f0 --- /dev/null +++ b/leetcode/valid_parentheses/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_is_valid, run_is_valid\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "s = \"()\"\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_is_valid(Solution, s)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_is_valid(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/valid_parentheses/solution.py b/leetcode/valid_parentheses/solution.py new file mode 100644 index 0000000..a776a9d --- /dev/null +++ b/leetcode/valid_parentheses/solution.py @@ -0,0 +1,15 @@ +class Solution: + + # Time: O(n) + # Space: O(n) + def is_valid(self, s: str) -> bool: + stack = [] + pairs = {"(": ")", "[": "]", "{": "}"} + + for char in s: + if char in pairs: + stack.append(char) + elif not stack or pairs[stack.pop()] != char: + return False + + return not stack diff --git a/leetcode/valid_parentheses/test_solution.py b/leetcode/valid_parentheses/test_solution.py new file mode 100644 index 0000000..88b32d6 --- /dev/null +++ b/leetcode/valid_parentheses/test_solution.py @@ -0,0 +1,31 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_is_valid, run_is_valid +from .solution import Solution + + +class TestValidParentheses: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, expected", + [ + ("()", True), + ("()[]{}", True), + ("(]", False), + ("([])", True), + ("([)]", False), + ("", True), + ("(", False), + (")", False), + ("{[()]}", True), + ("{[(])}", False), + ], + ) + def test_is_valid(self, s: str, expected: bool): + result = run_is_valid(Solution, s) + assert_is_valid(result, expected) diff --git a/leetcode/validate_binary_search_tree/README.md b/leetcode/validate_binary_search_tree/README.md new file mode 100644 index 0000000..4b8b99f --- /dev/null +++ b/leetcode/validate_binary_search_tree/README.md @@ -0,0 +1,44 @@ +# Validate Binary Search Tree + +**Difficulty:** Medium +**Topics:** Tree, Depth-First Search, Binary Search Tree, Binary Tree +**Tags:** grind-75 + +**LeetCode:** [Problem 98](https://leetcode.com/problems/validate-binary-search-tree/description/) + +## Problem Description + +Given the `root` of a binary tree, determine if it is a valid binary search tree (BST). + +A **valid BST** is defined as follows: + +- The left subtree of a node contains only nodes with keys **strictly less than** the node's key. +- The right subtree of a node contains only nodes with keys **strictly greater than** the node's key. +- Both the left and right subtrees must also be binary search trees. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2020/12/01/tree1.jpg) + +``` +Input: root = [2,1,3] +Output: true +``` + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2020/12/01/tree2.jpg) + +``` +Input: root = [5,1,4,null,null,3,6] +Output: false +``` + +**Explanation:** The root node's value is 5 but its right child's value is 4. + +## Constraints + +- The number of nodes in the tree is in the range `[1, 10^4]`. +- `-2^31 <= Node.val <= 2^31 - 1` diff --git a/leetcode/validate_binary_search_tree/__init__.py b/leetcode/validate_binary_search_tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/validate_binary_search_tree/helpers.py b/leetcode/validate_binary_search_tree/helpers.py new file mode 100644 index 0000000..b476545 --- /dev/null +++ b/leetcode/validate_binary_search_tree/helpers.py @@ -0,0 +1,12 @@ +from leetcode_py import TreeNode + + +def run_is_valid_bst(solution_class: type, root_list: list[int | None]): + root = TreeNode[int].from_list(root_list) if root_list else None + implementation = solution_class() + return implementation.is_valid_bst(root) + + +def assert_is_valid_bst(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/validate_binary_search_tree/playground.ipynb b/leetcode/validate_binary_search_tree/playground.ipynb new file mode 100644 index 0000000..a53e0f9 --- /dev/null +++ b/leetcode/validate_binary_search_tree/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_is_valid_bst, run_is_valid_bst\n", + "from solution import Solution\n", + "\n", + "from leetcode_py import TreeNode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "root_list = [2, 1, 3]\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_is_valid_bst(Solution, root_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_is_valid_bst(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/validate_binary_search_tree/solution.py b/leetcode/validate_binary_search_tree/solution.py new file mode 100644 index 0000000..b87123f --- /dev/null +++ b/leetcode/validate_binary_search_tree/solution.py @@ -0,0 +1,17 @@ +from leetcode_py import TreeNode + + +class Solution: + + @classmethod + def validate(cls, node: TreeNode[int] | None, min_val: float, max_val: float) -> bool: + if not node: + return True + if node.val <= min_val or node.val >= max_val: + return False + return cls.validate(node.left, min_val, node.val) and cls.validate(node.right, node.val, max_val) + + # Time: O(n) + # Space: O(h) + def is_valid_bst(self, root: TreeNode[int] | None) -> bool: + return self.validate(root, float("-inf"), float("inf")) diff --git a/leetcode/validate_binary_search_tree/test_solution.py b/leetcode/validate_binary_search_tree/test_solution.py new file mode 100644 index 0000000..825fa25 --- /dev/null +++ b/leetcode/validate_binary_search_tree/test_solution.py @@ -0,0 +1,27 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_is_valid_bst, run_is_valid_bst +from .solution import Solution + + +class TestValidateBinarySearchTree: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "root_list, expected", + [ + ([2, 1, 3], True), + ([5, 1, 4, None, None, 3, 6], False), + ([1], True), + ([1, 1], False), + ([10, 5, 15, None, None, 6, 20], False), + ([2, 1, 3, None, None, None, 4], True), + ], + ) + def test_is_valid_bst(self, root_list: list[int | None], expected: bool): + result = run_is_valid_bst(Solution, root_list) + assert_is_valid_bst(result, expected) diff --git a/leetcode/word_break/README.md b/leetcode/word_break/README.md new file mode 100644 index 0000000..7bb5206 --- /dev/null +++ b/leetcode/word_break/README.md @@ -0,0 +1,49 @@ +# Word Break + +**Difficulty:** Medium +**Topics:** Array, Hash Table, String, Dynamic Programming, Trie, Memoization +**Tags:** grind-75 + +**LeetCode:** [Problem 139](https://leetcode.com/problems/word-break/description/) + +## Problem Description + +Given a string `s` and a dictionary of strings `wordDict`, return `true` if `s` can be segmented into a space-separated sequence of one or more dictionary words. + +**Note** that the same word in the dictionary may be reused multiple times in the segmentation. + +## Examples + +### Example 1: + +``` +Input: s = "leetcode", wordDict = ["leet","code"] +Output: true +``` + +**Explanation:** Return true because "leetcode" can be segmented as "leet code". + +### Example 2: + +``` +Input: s = "applepenapple", wordDict = ["apple","pen"] +Output: true +``` + +**Explanation:** Return true because "applepenapple" can be segmented as "apple pen apple". +Note that you are allowed to reuse a dictionary word. + +### Example 3: + +``` +Input: s = "catsandog", wordDict = ["cats","dog","sand","and","cat"] +Output: false +``` + +## Constraints + +- `1 <= s.length <= 300` +- `1 <= wordDict.length <= 1000` +- `1 <= wordDict[i].length <= 20` +- `s` and `wordDict[i]` consist of only lowercase English letters. +- All the strings of `wordDict` are **unique**. diff --git a/leetcode/word_break/__init__.py b/leetcode/word_break/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/word_break/helpers.py b/leetcode/word_break/helpers.py new file mode 100644 index 0000000..45c39e9 --- /dev/null +++ b/leetcode/word_break/helpers.py @@ -0,0 +1,8 @@ +def run_word_break(solution_class: type, s: str, word_dict: list[str]): + implementation = solution_class() + return implementation.word_break(s, word_dict) + + +def assert_word_break(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/word_break/playground.ipynb b/leetcode/word_break/playground.ipynb new file mode 100644 index 0000000..b606647 --- /dev/null +++ b/leetcode/word_break/playground.ipynb @@ -0,0 +1,69 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_word_break, run_word_break\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "s = \"leetcode\"\n", + "word_dict = [\"leet\", \"code\"]\n", + "expected = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_word_break(Solution, s, word_dict)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_word_break(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/word_break/solution.py b/leetcode/word_break/solution.py new file mode 100644 index 0000000..24efa49 --- /dev/null +++ b/leetcode/word_break/solution.py @@ -0,0 +1,16 @@ +class Solution: + + # Time: O(n^2) + # Space: O(n) + def word_break(self, s: str, word_dict: list[str]) -> bool: + word_set = set(word_dict) + dp = [False] * (len(s) + 1) + dp[0] = True + + for i in range(1, len(s) + 1): + for j in range(i): + if dp[j] and s[j:i] in word_set: + dp[i] = True + break + + return dp[-1] diff --git a/leetcode/word_break/test_solution.py b/leetcode/word_break/test_solution.py new file mode 100644 index 0000000..fe2f52e --- /dev/null +++ b/leetcode/word_break/test_solution.py @@ -0,0 +1,28 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_word_break, run_word_break +from .solution import Solution + + +class TestWordBreak: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, word_dict, expected", + [ + ("leetcode", ["leet", "code"], True), + ("applepenapple", ["apple", "pen"], True), + ("catsandog", ["cats", "dog", "sand", "and", "cat"], False), + ("", [], True), + ("a", ["a"], True), + ("ab", ["a", "b"], True), + ("abcd", ["a", "abc", "d"], True), + ], + ) + def test_word_break(self, s: str, word_dict: list[str], expected: bool): + result = run_word_break(Solution, s, word_dict) + assert_word_break(result, expected) diff --git a/leetcode/word_ladder/README.md b/leetcode/word_ladder/README.md new file mode 100644 index 0000000..32820d3 --- /dev/null +++ b/leetcode/word_ladder/README.md @@ -0,0 +1,47 @@ +# Word Ladder + +**Difficulty:** Hard +**Topics:** Hash Table, String, Breadth-First Search +**Tags:** grind-75 + +**LeetCode:** [Problem 127](https://leetcode.com/problems/word-ladder/description/) + +## Problem Description + +A **transformation sequence** from word `beginWord` to word `endWord` using a dictionary `wordList` is a sequence of words `beginWord -> s1 -> s2 -> ... -> sk` such that: + +- Every adjacent pair of words differs by a single letter. +- Every `si` for `1 <= i <= k` is in `wordList`. Note that `beginWord` does not need to be in `wordList`. +- `sk == endWord` + +Given two words, `beginWord` and `endWord`, and a dictionary `wordList`, return the **number of words** in the **shortest transformation sequence** from `beginWord` to `endWord`, or `0` if no such sequence exists. + +## Examples + +### Example 1: + +``` +Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] +Output: 5 +``` + +**Explanation:** One shortest transformation sequence is "hit" -> "hot" -> "dot" -> "dog" -> "cog", which is 5 words long. + +### Example 2: + +``` +Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"] +Output: 0 +``` + +**Explanation:** The endWord "cog" is not in wordList, therefore there is no valid transformation sequence. + +## Constraints + +- 1 <= beginWord.length <= 10 +- endWord.length == beginWord.length +- 1 <= wordList.length <= 5000 +- wordList[i].length == beginWord.length +- beginWord, endWord, and wordList[i] consist of lowercase English letters. +- beginWord != endWord +- All the words in wordList are unique. diff --git a/leetcode/word_ladder/__init__.py b/leetcode/word_ladder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/word_ladder/helpers.py b/leetcode/word_ladder/helpers.py new file mode 100644 index 0000000..615221a --- /dev/null +++ b/leetcode/word_ladder/helpers.py @@ -0,0 +1,8 @@ +def run_ladder_length(solution_class: type, begin_word: str, end_word: str, word_list: list[str]): + implementation = solution_class() + return implementation.ladder_length(begin_word, end_word, word_list) + + +def assert_ladder_length(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/word_ladder/playground.ipynb b/leetcode/word_ladder/playground.ipynb new file mode 100644 index 0000000..863eedf --- /dev/null +++ b/leetcode/word_ladder/playground.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_ladder_length, run_ladder_length\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "begin_word = \"hit\"\n", + "end_word = \"cog\"\n", + "word_list = [\"hot\", \"dot\", \"dog\", \"lot\", \"log\", \"cog\"]\n", + "expected = 5" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_ladder_length(Solution, begin_word, end_word, word_list)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_ladder_length(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/word_ladder/solution.py b/leetcode/word_ladder/solution.py new file mode 100644 index 0000000..2356300 --- /dev/null +++ b/leetcode/word_ladder/solution.py @@ -0,0 +1,37 @@ +class Solution: + + # Time: O(M^2 * N) where M is length of each word, N is total number of words + # Space: O(M * N) for the visited sets + def ladder_length(self, begin_word: str, end_word: str, word_list: list[str]) -> int: + if end_word not in word_list: + return 0 + + if begin_word == end_word: + return 1 + + word_set = set(word_list) + begin_set = {begin_word} + end_set = {end_word} + length = 1 + + while begin_set and end_set: + if len(begin_set) > len(end_set): + begin_set, end_set = end_set, begin_set + + next_set = set() + for word in begin_set: + for i in range(len(word)): + for c in "abcdefghijklmnopqrstuvwxyz": + new_word = word[:i] + c + word[i + 1 :] + + if new_word in end_set: + return length + 1 + + if new_word in word_set: + next_set.add(new_word) + word_set.remove(new_word) + + begin_set = next_set + length += 1 + + return 0 diff --git a/leetcode/word_ladder/test_solution.py b/leetcode/word_ladder/test_solution.py new file mode 100644 index 0000000..ed83464 --- /dev/null +++ b/leetcode/word_ladder/test_solution.py @@ -0,0 +1,26 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_ladder_length, run_ladder_length +from .solution import Solution + + +class TestWordLadder: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "begin_word, end_word, word_list, expected", + [ + ("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"], 5), + ("hit", "cog", ["hot", "dot", "dog", "lot", "log"], 0), + ("a", "c", ["a", "b", "c"], 2), + ("hot", "dog", ["hot", "dog"], 0), + ("hot", "dog", ["hot", "hog", "dog"], 3), + ], + ) + def test_ladder_length(self, begin_word: str, end_word: str, word_list: list[str], expected: int): + result = run_ladder_length(Solution, begin_word, end_word, word_list) + assert_ladder_length(result, expected) diff --git a/leetcode/zero_one_matrix/README.md b/leetcode/zero_one_matrix/README.md new file mode 100644 index 0000000..29b3127 --- /dev/null +++ b/leetcode/zero_one_matrix/README.md @@ -0,0 +1,44 @@ +# 01 Matrix + +**Difficulty:** Medium +**Topics:** Array, Dynamic Programming, Breadth-First Search, Matrix +**Tags:** grind-75 + +**LeetCode:** [Problem 542](https://leetcode.com/problems/zero-one-matrix/description/) + +## Problem Description + +Given an `m x n` binary matrix `mat`, return the distance of the nearest `0` for each cell. + +The distance between two cells sharing a common edge is `1`. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2021/04/24/01-1-grid.jpg) + +``` +Input: mat = [[0,0,0],[0,1,0],[0,0,0]] +Output: [[0,0,0],[0,1,0],[0,0,0]] +``` + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2021/04/24/01-2-grid.jpg) + +``` +Input: mat = [[0,0,0],[0,1,0],[1,1,1]] +Output: [[0,0,0],[0,1,0],[1,2,1]] +``` + +## Constraints + +- `m == mat.length` +- `n == mat[i].length` +- `1 <= m, n <= 10^4` +- `1 <= m * n <= 10^4` +- `mat[i][j]` is either `0` or `1` +- There is at least one `0` in `mat` + +**Note:** This question is the same as 1765: [Map of Highest Peak](https://leetcode.com/problems/map-of-highest-peak/) diff --git a/leetcode/zero_one_matrix/__init__.py b/leetcode/zero_one_matrix/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/zero_one_matrix/helpers.py b/leetcode/zero_one_matrix/helpers.py new file mode 100644 index 0000000..46b900c --- /dev/null +++ b/leetcode/zero_one_matrix/helpers.py @@ -0,0 +1,8 @@ +def run_update_matrix(solution_class: type, mat: list[list[int]]): + implementation = solution_class() + return implementation.update_matrix(mat) + + +def assert_update_matrix(result: list[list[int]], expected: list[list[int]]) -> bool: + assert result == expected + return True diff --git a/leetcode/zero_one_matrix/playground.ipynb b/leetcode/zero_one_matrix/playground.ipynb new file mode 100644 index 0000000..5802a61 --- /dev/null +++ b/leetcode/zero_one_matrix/playground.ipynb @@ -0,0 +1,68 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "imports", + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import assert_update_matrix, run_update_matrix\n", + "from solution import Solution" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "setup", + "metadata": {}, + "outputs": [], + "source": [ + "# Example test case\n", + "mat = [[0, 0, 0], [0, 1, 0], [1, 1, 1]]\n", + "expected = [[0, 0, 0], [0, 1, 0], [1, 2, 1]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "run", + "metadata": {}, + "outputs": [], + "source": [ + "result = run_update_matrix(Solution, mat)\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "assert", + "metadata": {}, + "outputs": [], + "source": [ + "assert_update_matrix(result, expected)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "leetcode-py-py3.13", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/leetcode/zero_one_matrix/solution.py b/leetcode/zero_one_matrix/solution.py new file mode 100644 index 0000000..ba59c37 --- /dev/null +++ b/leetcode/zero_one_matrix/solution.py @@ -0,0 +1,30 @@ +class Solution: + + # Time: O(m * n) + # Space: O(m * n) + def update_matrix(self, mat: list[list[int]]) -> list[list[int]]: + from collections import deque + + UNSEEN = -1 + m, n = len(mat), len(mat[0]) + queue: deque[tuple[int, int]] = deque() + + # Mark 1s as UNSEEN and add all 0s to queue + for i in range(m): + for j in range(n): + if mat[i][j] == 0: + queue.append((i, j)) + else: + mat[i][j] = UNSEEN + + # BFS from all 0s simultaneously + directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] + while queue: + row, col = queue.popleft() + for dr, dc in directions: + r, c = row + dr, col + dc + if 0 <= r < m and 0 <= c < n and mat[r][c] == UNSEEN: + mat[r][c] = mat[row][col] + 1 + queue.append((r, c)) + + return mat diff --git a/leetcode/zero_one_matrix/test_solution.py b/leetcode/zero_one_matrix/test_solution.py new file mode 100644 index 0000000..ea195cb --- /dev/null +++ b/leetcode/zero_one_matrix/test_solution.py @@ -0,0 +1,25 @@ +import pytest + +from leetcode_py.test_utils import logged_test + +from .helpers import assert_update_matrix, run_update_matrix +from .solution import Solution + + +class TestZeroOneMatrix: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "mat, expected", + [ + ([[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]]), + ([[0, 0, 0], [0, 1, 0], [1, 1, 1]], [[0, 0, 0], [0, 1, 0], [1, 2, 1]]), + ([[0]], [[0]]), + ([[1, 0]], [[1, 0]]), + ], + ) + def test_update_matrix(self, mat: list[list[int]], expected: list[list[int]]): + result = run_update_matrix(Solution, mat) + assert_update_matrix(result, expected) From 8fc2d767d457f49b0a844853453ba025c0c821fc Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 14:48:07 +0700 Subject: [PATCH 19/39] docs: add test-case-enhancement.md --- .amazonq/rules/test-case-enhancement.md | 124 ++++++++++++++++++++++++ .templates/check_test_cases.py | 70 ++++++++++--- 2 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 .amazonq/rules/test-case-enhancement.md diff --git a/.amazonq/rules/test-case-enhancement.md b/.amazonq/rules/test-case-enhancement.md new file mode 100644 index 0000000..1fbf322 --- /dev/null +++ b/.amazonq/rules/test-case-enhancement.md @@ -0,0 +1,124 @@ +# Test Case Enhancement Rules + +## Assistant Workflow for Adding Comprehensive Test Cases + +When user requests to enhance test cases for a problem, the assistant will: + +### 1. Problem Resolution (Priority Order) + +- **FIRST**: Try to resolve from context - check active file path or user-provided problem name +- **SECOND**: If context resolution fails, THEN run `poetry run python .templates/check_test_cases.py --threshold=10 --max=1` to auto-detect 1 problem with <10 test cases +- **LAST**: If both above fail, ask user to explicitly specify problem name + +### 2. Test Case Generation + +- Read `leetcode/{problem_name}/README.md` for problem understanding +- Analyze existing test cases in `leetcode/{problem_name}/tests.py` +- Generate comprehensive test cases covering: + - **Edge cases**: Empty inputs, single elements, boundary values + - **Corner cases**: Maximum/minimum constraints, special patterns + - **Normal cases**: Typical scenarios with varied complexity + - **Error cases**: Invalid inputs (if applicable) + +### 3. Initial Validation + +- Run `make p-test PROBLEM={problem_name}` to verify current implementation +- **If errors found**: + - DO NOT update implementation automatically + - Only update test cases if they're incorrect + - If implementation seems wrong, ASK USER first before modifying + +### 4. JSON Template Update + +- Update corresponding `.templates/leetcode/json/{problem_name}.json` +- Add new test cases to `test_cases` field in proper format +- Maintain existing test structure and naming conventions + +### 5. Backup and Regeneration Process + +- **Backup**: Move `leetcode/{problem_name}/` to `.cache/leetcode/{problem_name}/` +- **Regenerate**: Run `make p-gen PROBLEM={problem_name} FORCE=1` +- **Lint check**: Run `make p-lint PROBLEM={problem_name}` +- **Iterate**: If lint fails, update JSON and regenerate until passes + +### 6. Solution Preservation + +- Copy `solution.py` from backup to newly generated structure +- Run `make p-test PROBLEM={problem_name}` to verify tests pass +- **If tests fail**: Go back to step 4, update JSON, and iterate until passes + +### 7. Cleanup and Restore + +- Remove newly generated files +- Restore original structure from backup +- Verify final state with `make p-test PROBLEM={problem_name}` + +## Test Case Quality Standards + +### Coverage Requirements + +- **Minimum 10 test cases** per problem +- **Edge cases**: 20-30% of total test cases +- **Normal cases**: 50-60% of total test cases +- **Corner cases**: 20-30% of total test cases + +### Test Case Categories + +#### Edge Cases + +- Empty inputs: `[]`, `""`, `None` +- Single element: `[1]`, `"a"` +- Boundary values: `[0]`, `[1]`, `[-1]` +- Maximum/minimum constraints from problem description + +#### Corner Cases + +- Duplicate elements: `[1,1,1]` +- Sorted/reverse sorted arrays: `[1,2,3]`, `[3,2,1]` +- All same elements: `[5,5,5,5]` +- Alternating patterns: `[1,0,1,0]` + +#### Normal Cases + +- Mixed positive/negative numbers +- Various array sizes within constraints +- Different data patterns and structures +- Representative problem scenarios + +### JSON Format Requirements + +- Use single quotes for Python strings in test cases +- Follow existing parametrize format +- Maintain type hints in parametrize_typed +- Ensure test_cases string is valid Python list syntax + +## Commands Reference + +```bash +# Find problems needing more test cases +poetry run python .templates/check_test_cases.py --threshold=10 --max=1 + +# Test specific problem +make p-test PROBLEM={problem_name} + +# Generate from JSON template +make p-gen PROBLEM={problem_name} FORCE=1 + +# Lint specific problem +make p-lint PROBLEM={problem_name} +``` + +## Error Handling + +- **Implementation errors**: Ask user before modifying solution code +- **Test failures**: Update JSON template and regenerate +- **Lint failures**: Fix JSON format and iterate +- **Backup failures**: Ensure `.cache/leetcode/` directory exists + +## Success Criteria + +- All tests pass with enhanced test cases +- Minimum 10 comprehensive test cases per problem +- Original solution code preserved and working +- JSON template updated for future regeneration +- Clean final state with no temporary files diff --git a/.templates/check_test_cases.py b/.templates/check_test_cases.py index a0fa441..4c36ffe 100644 --- a/.templates/check_test_cases.py +++ b/.templates/check_test_cases.py @@ -2,6 +2,9 @@ import json from pathlib import Path +from typing import Optional +import typer + def count_test_cases(json_data): """Count total test cases across all test methods.""" @@ -15,13 +18,43 @@ def count_test_cases(json_data): for method in test_methods: test_cases = method.get("test_cases", "") if test_cases.strip(): - # Count tuples/lists in test_cases string - total += max(test_cases.count("("), test_cases.count("[")) + # Parse the test_cases string to count actual test cases + try: + # Remove outer brackets and split by top-level commas + cases_str = test_cases.strip() + if cases_str.startswith("[") and cases_str.endswith("]"): + cases_str = cases_str[1:-1] # Remove outer brackets + + # Count test cases by counting commas at parenthesis depth 0 + depth = 0 + case_count = 1 if cases_str.strip() else 0 + + for char in cases_str: + if char in "([{": + depth += 1 + elif char in ")]}": + depth -= 1 + elif char == "," and depth == 0: + case_count += 1 + + total += case_count + except Exception: + # Fallback to old method if parsing fails + total += test_cases.count("(") - test_cases.count("([") + test_cases.count("[(") return total -def main(): + +def main( + threshold: int = typer.Option( + 10, "--threshold", "-t", help="Show files with test cases <= threshold" + ), + max_results: str = typer.Option( + 1, "--max", "-m", help="Maximum number of results to show ('none' for no limit)" + ), +): + """Check test case counts in LeetCode JSON templates.""" json_dir = Path(".templates/leetcode/json") - files_with_few_tests = [] + all_files = [] for json_file in json_dir.glob("*.json"): try: @@ -29,17 +62,30 @@ def main(): data = json.load(f) test_count = count_test_cases(data) - if test_count <= 10: - files_with_few_tests.append((json_file.name, test_count)) + all_files.append((json_file.name, test_count)) except Exception as e: - print(f"Error reading {json_file.name}: {e}") + typer.echo(f"Error reading {json_file.name}: {e}", err=True) # Sort by test count - files_with_few_tests.sort(key=lambda x: x[1]) + all_files.sort(key=lambda x: x[1]) + + # Filter by threshold + filtered_files = [f for f in all_files if f[1] <= threshold] + + # Apply max results limit + if max_results.lower() not in ["none", "null", "-1"]: + try: + max_count = int(max_results) + if max_count > 0: + filtered_files = filtered_files[:max_count] + except ValueError: + typer.echo(f"Invalid max_results value: {max_results}", err=True) + raise typer.Exit(1) + + typer.echo(f"Files with ≤{threshold} test cases ({len(filtered_files)} total):") + for filename, count in filtered_files: + typer.echo(f"{filename}: {count} test cases") - print(f"Files with ≤10 test cases ({len(files_with_few_tests)} total):") - for filename, count in files_with_few_tests: - print(f"{filename}: {count} test cases") if __name__ == "__main__": - main() + typer.run(main) From a1c2bd9abfdc7673e9a0f41b610acb212ec167be Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 15:06:53 +0700 Subject: [PATCH 20/39] feat: check solution with old version --- leetcode/lru_cache/playground.ipynb | 46 +++++++++--- .../solution.py | 28 ++++++++ .../partition_equal_subset_sum/solution.py | 71 +++++++++++++++++++ .../test_solution.py | 9 ++- leetcode/task_scheduler/solution.py | 32 ++++++++- leetcode/task_scheduler/test_solution.py | 9 ++- .../validate_binary_search_tree/solution.py | 44 ++++++++++++ .../test_solution.py | 9 ++- leetcode/zero_one_matrix/playground.ipynb | 37 ++++++++-- 9 files changed, 253 insertions(+), 32 deletions(-) diff --git a/leetcode/lru_cache/playground.ipynb b/leetcode/lru_cache/playground.ipynb index 0387f35..687c6f1 100644 --- a/leetcode/lru_cache/playground.ipynb +++ b/leetcode/lru_cache/playground.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "imports", "metadata": {}, "outputs": [], @@ -13,7 +13,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "setup", "metadata": {}, "outputs": [], @@ -26,22 +26,51 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "run", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[None, None, None, 1, None, -1, None, -1, 3, 4]\n" + ] + }, + { + "data": { + "text/plain": [ + "OrderedDict([(3, 3), (4, 4)])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "result, cache = run_lru_cache(LRUCache, operations, inputs)\n", "print(result)\n", - "cache" + "cache.cache" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "assert", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "assert_lru_cache(result, expected)" ] @@ -61,7 +90,8 @@ "file_extension": ".py", "mimetype": "text/x-python", "name": "python", - "nbconvert_exporter": "python3", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", "version": "3.13.7" } }, diff --git a/leetcode/maximum_profit_in_job_scheduling/solution.py b/leetcode/maximum_profit_in_job_scheduling/solution.py index 964c396..c1653df 100644 --- a/leetcode/maximum_profit_in_job_scheduling/solution.py +++ b/leetcode/maximum_profit_in_job_scheduling/solution.py @@ -20,3 +20,31 @@ def job_scheduling(self, start_time: list[int], end_time: list[int], profit: lis dp[i] = max(take, skip) return dp[-1] if jobs else 0 + + +# bisect and insort Explanation: +# +# Etymology: "bisect" = bi (two) + sect (cut) = cut into two parts +# Bisection method = binary search algorithm that repeatedly cuts search space in half +# +# bisect module provides binary search for SORTED lists (O(log n)): +# - bisect_left(arr, x): leftmost insertion position +# - bisect_right(arr, x): rightmost insertion position (default) +# - bisect(arr, x): alias for bisect_right +# +# insort module maintains sorted order while inserting: +# - insort_left(arr, x): insert at leftmost position +# - insort_right(arr, x): insert at rightmost position (default) +# - insort(arr, x): alias for insort_right +# +# Examples: +# arr = [1, 3, 3, 5] +# bisect_left(arr, 3) → 1 (before existing 3s) +# bisect_right(arr, 3) → 3 (after existing 3s) +# bisect_right(arr, 4) → 3 (between 3 and 5) +# +# insort(arr, 4) → arr becomes [1, 3, 3, 4, 5] +# +# In our solution: +# bisect_right([2,4,6], 5) = 2 (insertion position) +# j = 2 - 1 = 1 (index of latest job ending ≤ start_time) diff --git a/leetcode/partition_equal_subset_sum/solution.py b/leetcode/partition_equal_subset_sum/solution.py index e3a254d..c3bcdfe 100644 --- a/leetcode/partition_equal_subset_sum/solution.py +++ b/leetcode/partition_equal_subset_sum/solution.py @@ -3,6 +3,23 @@ class Solution: # Time: O(n * sum) # Space: O(sum) def can_partition(self, nums: list[int]) -> bool: + """ + Example: nums = [1, 5, 11, 5], target = 11 + + Initial: dp = [T, F, F, F, F, F, F, F, F, F, F, F] + 0 1 2 3 4 5 6 7 8 9 10 11 + + After num=1: [T, T, F, F, F, F, F, F, F, F, F, F] + └─┘ (can make sum 1) + + After num=5: [T, T, F, F, F, T, T, F, F, F, F, F] + └─┘ └─┘ └─┘ (can make sums 5,6) + + After num=11:[T, T, F, F, F, T, T, F, F, F, F, T] + └─┘ (target!) + + Backward iteration prevents using same number twice + """ total = sum(nums) if total % 2: return False @@ -20,3 +37,57 @@ def can_partition(self, nums: list[int]) -> bool: return True return False + + +class SolutionBitset: + # Time: O(n * sum) + # Space: O(1) + def can_partition(self, nums: list[int]) -> bool: + """ + Example: nums = [1, 5, 11, 5], target = 11 + + Bitset representation (bit position = achievable sum): + + Initial: dp = 1 (binary: 1) + Bits: ...0001 + Sums: {0} + + After num=1: dp |= dp << 1 + a = dp = 1 (bin: 0001) + b = dp << 1 = 2 (bin: 0010) + c = a | b = 3 (bin: 0011) + Sums: {0, 1} + + After num=5: dp |= dp << 5 + a = dp = 3 (bin: 0000011) + b = dp << 5 = 96 (bin: 1100000) + c = a | b = 99 (bin: 1100011) + Sums: {0, 1, 5, 6} + + After num=11: dp |= dp << 11 + a = dp = 99 (bin: 00000001100011) + b = dp << 11 = 202752 (bin: 110001100000000) + c = a | b = 202851 (bin: 110001101100011) + Sums: {0, 1, 5, 6, 11, 12, 16, 17} + + Check: (dp & (1 << 11)) != 0 + a = dp = 202851 (bin: 110001101100011) + b = 1 << 11 = 2048 (bin: 100000000000) + c = a & b = 2048 (bin: 100000000000) + c != 0 → bit 11 is set → True! + """ + total = sum(nums) + if total % 2 != 0: + return False + + target = total // 2 + dp = 1 + + for num in nums: + dp |= dp << num + + # Early termination: found target sum! + if (dp & (1 << target)) != 0: + return True + + return False diff --git a/leetcode/partition_equal_subset_sum/test_solution.py b/leetcode/partition_equal_subset_sum/test_solution.py index 1eee974..a04af23 100644 --- a/leetcode/partition_equal_subset_sum/test_solution.py +++ b/leetcode/partition_equal_subset_sum/test_solution.py @@ -3,14 +3,13 @@ from leetcode_py.test_utils import logged_test from .helpers import assert_can_partition, run_can_partition -from .solution import Solution +from .solution import Solution, SolutionBitset class TestPartitionEqualSubsetSum: - def setup_method(self): - self.solution = Solution() @logged_test + @pytest.mark.parametrize("solution_class", [Solution, SolutionBitset]) @pytest.mark.parametrize( "nums, expected", [ @@ -23,6 +22,6 @@ def setup_method(self): ([1, 2, 5], False), ], ) - def test_can_partition(self, nums: list[int], expected: bool): - result = run_can_partition(Solution, nums) + def test_can_partition(self, nums: list[int], expected: bool, solution_class: type): + result = run_can_partition(solution_class, nums) assert_can_partition(result, expected) diff --git a/leetcode/task_scheduler/solution.py b/leetcode/task_scheduler/solution.py index 438bc66..d6a6f15 100644 --- a/leetcode/task_scheduler/solution.py +++ b/leetcode/task_scheduler/solution.py @@ -1,10 +1,38 @@ +import heapq +from collections import Counter, deque + + class Solution: + # Time: O(T * n + m log m) where T = len(tasks), worst case with many idle periods + # Space: O(m) where m ≤ 26, so O(1) + def least_interval(self, tasks: list[str], n: int) -> int: + counts = Counter(tasks) + max_heap = [-count for count in counts.values()] + heapq.heapify(max_heap) + + step_num = 0 + queue: deque[tuple[int, int]] = deque() # (count, available_time) + while max_heap or queue: + step_num += 1 + + while queue and queue[0][1] <= step_num: + count, _ = queue.popleft() + heapq.heappush(max_heap, count) + + if max_heap: + count = heapq.heappop(max_heap) + count += 1 # Decrease count (was negative) + if count < 0: # Still has tasks left + queue.append((count, step_num + n + 1)) + + return step_num + + +class SolutionGreedy: # Time: O(T + m) where T = len(tasks), m = unique tasks ≤ 26, so O(T) # Space: O(m) where m ≤ 26, so O(1) def least_interval(self, tasks: list[str], n: int) -> int: - from collections import Counter - """ Mathematical approach: diff --git a/leetcode/task_scheduler/test_solution.py b/leetcode/task_scheduler/test_solution.py index 58bdcd1..b2db53e 100644 --- a/leetcode/task_scheduler/test_solution.py +++ b/leetcode/task_scheduler/test_solution.py @@ -3,14 +3,13 @@ from leetcode_py.test_utils import logged_test from .helpers import assert_least_interval, run_least_interval -from .solution import Solution +from .solution import Solution, SolutionGreedy class TestTaskScheduler: - def setup_method(self): - self.solution = Solution() @logged_test + @pytest.mark.parametrize("solution_class", [Solution, SolutionGreedy]) @pytest.mark.parametrize( "tasks, n, expected", [ @@ -22,6 +21,6 @@ def setup_method(self): (["A", "B"], 0, 2), ], ) - def test_least_interval(self, tasks: list[str], n: int, expected: int): - result = run_least_interval(Solution, tasks, n) + def test_least_interval(self, tasks: list[str], n: int, expected: int, solution_class: type): + result = run_least_interval(solution_class, tasks, n) assert_least_interval(result, expected) diff --git a/leetcode/validate_binary_search_tree/solution.py b/leetcode/validate_binary_search_tree/solution.py index b87123f..6d531db 100644 --- a/leetcode/validate_binary_search_tree/solution.py +++ b/leetcode/validate_binary_search_tree/solution.py @@ -1,3 +1,5 @@ +from collections import deque + from leetcode_py import TreeNode @@ -15,3 +17,45 @@ def validate(cls, node: TreeNode[int] | None, min_val: float, max_val: float) -> # Space: O(h) def is_valid_bst(self, root: TreeNode[int] | None) -> bool: return self.validate(root, float("-inf"), float("inf")) + + +class SolutionDFS: + # Time: O(n) + # Space: O(h) + def is_valid_bst(self, root: TreeNode[int] | None) -> bool: + if not root: + return True + + stack = [(root, float("-inf"), float("inf"))] + + while stack: + node, min_val, max_val = stack.pop() + if node.val <= min_val or node.val >= max_val: + return False + if node.right: + stack.append((node.right, node.val, max_val)) + if node.left: + stack.append((node.left, min_val, node.val)) + + return True + + +class SolutionBFS: + # Time: O(n) + # Space: O(w) where w is max width + def is_valid_bst(self, root: TreeNode[int] | None) -> bool: + if not root: + return True + + queue = deque([(root, float("-inf"), float("inf"))]) + + while queue: + node, min_val, max_val = queue.popleft() + if node.val <= min_val or node.val >= max_val: + return False + if node.right: + queue.append((node.right, node.val, max_val)) + if node.left: + queue.append((node.left, min_val, node.val)) + + return True diff --git a/leetcode/validate_binary_search_tree/test_solution.py b/leetcode/validate_binary_search_tree/test_solution.py index 825fa25..e7af72f 100644 --- a/leetcode/validate_binary_search_tree/test_solution.py +++ b/leetcode/validate_binary_search_tree/test_solution.py @@ -3,14 +3,13 @@ from leetcode_py.test_utils import logged_test from .helpers import assert_is_valid_bst, run_is_valid_bst -from .solution import Solution +from .solution import Solution, SolutionBFS, SolutionDFS class TestValidateBinarySearchTree: - def setup_method(self): - self.solution = Solution() @logged_test + @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS]) @pytest.mark.parametrize( "root_list, expected", [ @@ -22,6 +21,6 @@ def setup_method(self): ([2, 1, 3, None, None, None, 4], True), ], ) - def test_is_valid_bst(self, root_list: list[int | None], expected: bool): - result = run_is_valid_bst(Solution, root_list) + def test_is_valid_bst(self, root_list: list[int | None], expected: bool, solution_class: type): + result = run_is_valid_bst(solution_class, root_list) assert_is_valid_bst(result, expected) diff --git a/leetcode/zero_one_matrix/playground.ipynb b/leetcode/zero_one_matrix/playground.ipynb index 5802a61..6b3b709 100644 --- a/leetcode/zero_one_matrix/playground.ipynb +++ b/leetcode/zero_one_matrix/playground.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "imports", "metadata": {}, "outputs": [], @@ -13,7 +13,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "setup", "metadata": {}, "outputs": [], @@ -25,10 +25,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "run", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[[0, 0, 0], [0, 1, 0], [1, 2, 1]]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "result = run_update_matrix(Solution, mat)\n", "result" @@ -36,10 +47,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "assert", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "assert_update_matrix(result, expected)" ] @@ -59,7 +81,8 @@ "file_extension": ".py", "mimetype": "text/x-python", "name": "python", - "nbconvert_exporter": "python3", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", "version": "3.13.7" } }, From 7a87496e4926778b4e41cb096dbb4c765a657652 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 15:20:13 +0700 Subject: [PATCH 21/39] feat: add more test cases for find_median_from_data_stream --- .amazonq/rules/problem-creation.md | 1 + .amazonq/rules/test-case-enhancement.md | 7 +- .../json/find_median_from_data_stream.json | 2 +- .../test_solution.py | 95 ++++++++++++++++++- 4 files changed, 101 insertions(+), 4 deletions(-) diff --git a/.amazonq/rules/problem-creation.md b/.amazonq/rules/problem-creation.md index 4eb1fee..4cafbf6 100644 --- a/.amazonq/rules/problem-creation.md +++ b/.amazonq/rules/problem-creation.md @@ -135,6 +135,7 @@ When creating JSON properties that use PascalCase (solution_class_name, test_cla - Multiple methods including `__init__` - Complex test setup with operation sequences - Import custom class in test_imports +- **NEVER include custom solution classes** in test_imports - only import the main solution class specified in solution_class_name ### Dict-based Tree Problems (Trie, etc.) diff --git a/.amazonq/rules/test-case-enhancement.md b/.amazonq/rules/test-case-enhancement.md index 1fbf322..478716f 100644 --- a/.amazonq/rules/test-case-enhancement.md +++ b/.amazonq/rules/test-case-enhancement.md @@ -49,9 +49,11 @@ When user requests to enhance test cases for a problem, the assistant will: ### 7. Cleanup and Restore -- Remove newly generated files -- Restore original structure from backup +- **CRITICAL**: Remove entire newly generated `leetcode/{problem_name}/` directory +- **CRITICAL**: Restore original structure from `.cache/leetcode/{problem_name}/` backup +- **CRITICAL**: Only THEN copy enhanced `test_solution.py` from generated files to restored structure - Verify final state with `make p-test PROBLEM={problem_name}` +- Clean up backup directory after successful verification ## Test Case Quality Standards @@ -91,6 +93,7 @@ When user requests to enhance test cases for a problem, the assistant will: - Follow existing parametrize format - Maintain type hints in parametrize_typed - Ensure test_cases string is valid Python list syntax +- **NEVER include custom solution classes** in test_imports - only import the main solution class specified in solution_class_name ## Commands Reference diff --git a/.templates/leetcode/json/find_median_from_data_stream.json b/.templates/leetcode/json/find_median_from_data_stream.json index 200846e..3a85fa4 100644 --- a/.templates/leetcode/json/find_median_from_data_stream.json +++ b/.templates/leetcode/json/find_median_from_data_stream.json @@ -57,7 +57,7 @@ "name": "test_median_finder", "signature": "(self, operations: list[str], inputs: list[list[int]], expected: list[float | None])", "parametrize": "operations, inputs, expected", - "test_cases": "[(['MedianFinder', 'addNum', 'addNum', 'findMedian', 'addNum', 'findMedian'], [[], [1], [2], [], [3], []], [None, None, None, 1.5, None, 2.0])]", + "test_cases": "[(['MedianFinder', 'addNum', 'addNum', 'findMedian', 'addNum', 'findMedian'], [[], [1], [2], [], [3], []], [None, None, None, 1.5, None, 2.0]), (['MedianFinder', 'addNum', 'findMedian'], [[], [1], []], [None, None, 1.0]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [1], [1], [1], []], [None, None, None, None, 1.0]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [1], [2], [3], [4], []], [None, None, None, None, None, 2.5]), (['MedianFinder', 'addNum', 'addNum', 'findMedian', 'addNum', 'addNum', 'findMedian'], [[], [-1], [0], [], [1], [2], []], [None, None, None, -0.5, None, None, 0.5]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [5], [1], [3], [2], [4], []], [None, None, None, None, None, None, 3.0]), (['MedianFinder', 'addNum', 'findMedian', 'addNum', 'findMedian', 'addNum', 'findMedian'], [[], [100000], [], [-100000], [], [0], []], [None, None, 100000.0, None, 0.0, None, 0.0]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [10], [5], [15], [3], [7], [12], [18], []], [None, None, None, None, None, None, None, None, 10.0]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [6], [10], [2], [6], [5], [0], []], [None, None, None, None, None, None, None, 5.5]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [1], [2], [3], [4], [5], [6], [7], [8], []], [None, None, None, None, None, None, None, None, None, 4.5]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [9], [8], [7], [6], [5], [4], [3], [2], [1], []], [None, None, None, None, None, None, None, None, None, None, 5.0]), (['MedianFinder', 'addNum', 'addNum', 'addNum', 'addNum', 'addNum', 'findMedian'], [[], [0], [0], [0], [0], [0], []], [None, None, None, None, None, None, 0.0])]", "body": " result, _ = run_median_finder(MedianFinder, operations, inputs)\n assert_median_finder(result, expected)" } ] diff --git a/leetcode/find_median_from_data_stream/test_solution.py b/leetcode/find_median_from_data_stream/test_solution.py index a4f759d..25ec573 100644 --- a/leetcode/find_median_from_data_stream/test_solution.py +++ b/leetcode/find_median_from_data_stream/test_solution.py @@ -17,7 +17,100 @@ class TestFindMedianFromDataStream: ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"], [[], [1], [2], [], [3], []], [None, None, None, 1.5, None, 2.0], - ) + ), + (["MedianFinder", "addNum", "findMedian"], [[], [1], []], [None, None, 1.0]), + ( + ["MedianFinder", "addNum", "addNum", "addNum", "findMedian"], + [[], [1], [1], [1], []], + [None, None, None, None, 1.0], + ), + ( + ["MedianFinder", "addNum", "addNum", "addNum", "addNum", "findMedian"], + [[], [1], [2], [3], [4], []], + [None, None, None, None, None, 2.5], + ), + ( + ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "addNum", "findMedian"], + [[], [-1], [0], [], [1], [2], []], + [None, None, None, -0.5, None, None, 0.5], + ), + ( + ["MedianFinder", "addNum", "addNum", "addNum", "addNum", "addNum", "findMedian"], + [[], [5], [1], [3], [2], [4], []], + [None, None, None, None, None, None, 3.0], + ), + ( + ["MedianFinder", "addNum", "findMedian", "addNum", "findMedian", "addNum", "findMedian"], + [[], [100000], [], [-100000], [], [0], []], + [None, None, 100000.0, None, 0.0, None, 0.0], + ), + ( + [ + "MedianFinder", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "findMedian", + ], + [[], [10], [5], [15], [3], [7], [12], [18], []], + [None, None, None, None, None, None, None, None, 10.0], + ), + ( + [ + "MedianFinder", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "findMedian", + ], + [[], [6], [10], [2], [6], [5], [0], []], + [None, None, None, None, None, None, None, 5.5], + ), + ( + [ + "MedianFinder", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "findMedian", + ], + [[], [1], [2], [3], [4], [5], [6], [7], [8], []], + [None, None, None, None, None, None, None, None, None, 4.5], + ), + ( + [ + "MedianFinder", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "addNum", + "findMedian", + ], + [[], [9], [8], [7], [6], [5], [4], [3], [2], [1], []], + [None, None, None, None, None, None, None, None, None, None, 5.0], + ), + ( + ["MedianFinder", "addNum", "addNum", "addNum", "addNum", "addNum", "findMedian"], + [[], [0], [0], [0], [0], [0], []], + [None, None, None, None, None, None, 0.0], + ), ], ) def test_median_finder( From f9ad24aa06bc86829c49f88ca493b553137fa50e Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 15:35:37 +0700 Subject: [PATCH 22/39] feat: add more test cases for clone graph --- .amazonq/rules/test-case-enhancement.md | 2 + .templates/leetcode/json/clone_graph.json | 2 +- .../json/time_based_key_value_store.json | 2 +- leetcode/clone_graph/test_solution.py | 18 ++++- .../test_solution.py | 72 ++++++++++++++++++- 5 files changed, 92 insertions(+), 4 deletions(-) diff --git a/.amazonq/rules/test-case-enhancement.md b/.amazonq/rules/test-case-enhancement.md index 478716f..7a65e36 100644 --- a/.amazonq/rules/test-case-enhancement.md +++ b/.amazonq/rules/test-case-enhancement.md @@ -52,6 +52,7 @@ When user requests to enhance test cases for a problem, the assistant will: - **CRITICAL**: Remove entire newly generated `leetcode/{problem_name}/` directory - **CRITICAL**: Restore original structure from `.cache/leetcode/{problem_name}/` backup - **CRITICAL**: Only THEN copy enhanced `test_solution.py` from generated files to restored structure +- **CRITICAL**: Preserve existing solution class parametrization - if original test had multiple solution classes, restore them - Verify final state with `make p-test PROBLEM={problem_name}` - Clean up backup directory after successful verification @@ -94,6 +95,7 @@ When user requests to enhance test cases for a problem, the assistant will: - Maintain type hints in parametrize_typed - Ensure test_cases string is valid Python list syntax - **NEVER include custom solution classes** in test_imports - only import the main solution class specified in solution_class_name +- **PRESERVE existing solution class parametrization** - if original test had multiple solution classes, restore them after JSON regeneration ## Commands Reference diff --git a/.templates/leetcode/json/clone_graph.json b/.templates/leetcode/json/clone_graph.json index 594a2fc..7165af5 100644 --- a/.templates/leetcode/json/clone_graph.json +++ b/.templates/leetcode/json/clone_graph.json @@ -55,7 +55,7 @@ "name": "test_clone_graph", "signature": "(self, adj_list: list[list[int]])", "parametrize": "adj_list", - "test_cases": "[[[2, 4], [1, 3], [2, 4], [1, 3]], [[]], []]", + "test_cases": "[[[2, 4], [1, 3], [2, 4], [1, 3]], [[]], [], [[2], [1]], [[2, 3], [1], [1]], [[2], [3], [4], []], [[2, 3, 4], [1], [1], [1]], [[2, 3], [1, 3], [1, 2]], [[2, 5], [1, 3], [2, 4], [3, 5], [1, 4]], [[2, 3], [1, 4], [1, 4], [2, 3]], [[2, 3, 4, 5], [1], [1], [1], [1]], [[2], [3], [4], [5], []]]", "body": " result = run_clone_graph(Solution, adj_list)\n assert_clone_graph(result, adj_list)" } ] diff --git a/.templates/leetcode/json/time_based_key_value_store.json b/.templates/leetcode/json/time_based_key_value_store.json index 3bdbbe2..31178eb 100644 --- a/.templates/leetcode/json/time_based_key_value_store.json +++ b/.templates/leetcode/json/time_based_key_value_store.json @@ -57,7 +57,7 @@ "name": "test_time_map_operations", "signature": "(self, operations: list[str], inputs: list[list], expected: list)", "parametrize": "operations, inputs, expected", - "test_cases": "[(['TimeMap', 'set', 'get', 'get', 'set', 'get', 'get'], [[], ['foo', 'bar', 1], ['foo', 1], ['foo', 3], ['foo', 'bar2', 4], ['foo', 4], ['foo', 5]], [None, None, 'bar', 'bar', None, 'bar2', 'bar2'])]", + "test_cases": "[(['TimeMap', 'set', 'get', 'get', 'set', 'get', 'get'], [[], ['foo', 'bar', 1], ['foo', 1], ['foo', 3], ['foo', 'bar2', 4], ['foo', 4], ['foo', 5]], [None, None, 'bar', 'bar', None, 'bar2', 'bar2']), (['TimeMap', 'get'], [[], ['key', 1]], [None, '']), (['TimeMap', 'set', 'get'], [[], ['a', 'val', 1], ['a', 1]], [None, None, 'val']), (['TimeMap', 'set', 'get', 'get'], [[], ['key', 'value', 5], ['key', 3], ['key', 7]], [None, None, '', 'value']), (['TimeMap', 'set', 'set', 'get', 'get', 'get'], [[], ['x', 'v1', 1], ['x', 'v2', 2], ['x', 1], ['x', 2], ['x', 3]], [None, None, None, 'v1', 'v2', 'v2']), (['TimeMap', 'set', 'set', 'set', 'get', 'get', 'get'], [[], ['k', 'a', 10], ['k', 'b', 20], ['k', 'c', 30], ['k', 15], ['k', 25], ['k', 35]], [None, None, None, None, 'a', 'b', 'c']), (['TimeMap', 'set', 'set', 'get', 'get'], [[], ['key1', 'val1', 1], ['key2', 'val2', 2], ['key1', 1], ['key2', 2]], [None, None, None, 'val1', 'val2']), (['TimeMap', 'set', 'set', 'set', 'get', 'get', 'get'], [[], ['a', 'x', 1], ['b', 'y', 2], ['c', 'z', 3], ['a', 1], ['b', 2], ['c', 3]], [None, None, None, None, 'x', 'y', 'z']), (['TimeMap', 'set', 'get', 'set', 'get', 'set', 'get'], [[], ['test', 'first', 1], ['test', 1], ['test', 'second', 100], ['test', 50], ['test', 'third', 1000], ['test', 500]], [None, None, 'first', None, 'first', None, 'second']), (['TimeMap', 'set', 'set', 'set', 'set', 'get'], [[], ['data', 'v1', 1], ['data', 'v2', 10], ['data', 'v3', 100], ['data', 'v4', 1000], ['data', 555]], [None, None, None, None, None, 'v3']), (['TimeMap', 'set', 'get', 'get', 'get'], [[], ['single', 'value', 42], ['single', 1], ['single', 42], ['single', 100]], [None, None, '', 'value', 'value']), (['TimeMap', 'set', 'set', 'get', 'get', 'get', 'get'], [[], ['boundary', 'min', 1], ['boundary', 'max', 10000000], ['boundary', 0], ['boundary', 1], ['boundary', 5000000], ['boundary', 10000000]], [None, None, None, '', 'min', 'min', 'max'])]", "body": " result = run_time_map_operations(TimeMap, operations, inputs)\n assert_time_map_operations(result, expected)" } ] diff --git a/leetcode/clone_graph/test_solution.py b/leetcode/clone_graph/test_solution.py index f4825c9..95bb87f 100644 --- a/leetcode/clone_graph/test_solution.py +++ b/leetcode/clone_graph/test_solution.py @@ -12,7 +12,23 @@ def setup_method(self): @logged_test @pytest.mark.parametrize("solution_class", [Solution, SolutionBFS, SolutionDFS]) - @pytest.mark.parametrize("adj_list", [[[2, 4], [1, 3], [2, 4], [1, 3]], [[]], []]) + @pytest.mark.parametrize( + "adj_list", + [ + [[2, 4], [1, 3], [2, 4], [1, 3]], + [[]], + [], + [[2], [1]], + [[2, 3], [1], [1]], + [[2], [3], [4], []], + [[2, 3, 4], [1], [1], [1]], + [[2, 3], [1, 3], [1, 2]], + [[2, 5], [1, 3], [2, 4], [3, 5], [1, 4]], + [[2, 3], [1, 4], [1, 4], [2, 3]], + [[2, 3, 4, 5], [1], [1], [1], [1]], + [[2], [3], [4], [5], []], + ], + ) def test_clone_graph(self, solution_class: type, adj_list: list[list[int]]): result = run_clone_graph(solution_class, adj_list) assert_clone_graph(result, adj_list) diff --git a/leetcode/time_based_key_value_store/test_solution.py b/leetcode/time_based_key_value_store/test_solution.py index b72dd3d..c0c16ff 100644 --- a/leetcode/time_based_key_value_store/test_solution.py +++ b/leetcode/time_based_key_value_store/test_solution.py @@ -24,7 +24,77 @@ class TestTimeBasedKeyValueStore: ["foo", 5], ], [None, None, "bar", "bar", None, "bar2", "bar2"], - ) + ), + (["TimeMap", "get"], [[], ["key", 1]], [None, ""]), + (["TimeMap", "set", "get"], [[], ["a", "val", 1], ["a", 1]], [None, None, "val"]), + ( + ["TimeMap", "set", "get", "get"], + [[], ["key", "value", 5], ["key", 3], ["key", 7]], + [None, None, "", "value"], + ), + ( + ["TimeMap", "set", "set", "get", "get", "get"], + [[], ["x", "v1", 1], ["x", "v2", 2], ["x", 1], ["x", 2], ["x", 3]], + [None, None, None, "v1", "v2", "v2"], + ), + ( + ["TimeMap", "set", "set", "set", "get", "get", "get"], + [[], ["k", "a", 10], ["k", "b", 20], ["k", "c", 30], ["k", 15], ["k", 25], ["k", 35]], + [None, None, None, None, "a", "b", "c"], + ), + ( + ["TimeMap", "set", "set", "get", "get"], + [[], ["key1", "val1", 1], ["key2", "val2", 2], ["key1", 1], ["key2", 2]], + [None, None, None, "val1", "val2"], + ), + ( + ["TimeMap", "set", "set", "set", "get", "get", "get"], + [[], ["a", "x", 1], ["b", "y", 2], ["c", "z", 3], ["a", 1], ["b", 2], ["c", 3]], + [None, None, None, None, "x", "y", "z"], + ), + ( + ["TimeMap", "set", "get", "set", "get", "set", "get"], + [ + [], + ["test", "first", 1], + ["test", 1], + ["test", "second", 100], + ["test", 50], + ["test", "third", 1000], + ["test", 500], + ], + [None, None, "first", None, "first", None, "second"], + ), + ( + ["TimeMap", "set", "set", "set", "set", "get"], + [ + [], + ["data", "v1", 1], + ["data", "v2", 10], + ["data", "v3", 100], + ["data", "v4", 1000], + ["data", 555], + ], + [None, None, None, None, None, "v3"], + ), + ( + ["TimeMap", "set", "get", "get", "get"], + [[], ["single", "value", 42], ["single", 1], ["single", 42], ["single", 100]], + [None, None, "", "value", "value"], + ), + ( + ["TimeMap", "set", "set", "get", "get", "get", "get"], + [ + [], + ["boundary", "min", 1], + ["boundary", "max", 10000000], + ["boundary", 0], + ["boundary", 1], + ["boundary", 5000000], + ["boundary", 10000000], + ], + [None, None, None, "", "min", "min", "max"], + ), ], ) def test_time_map_operations(self, operations: list[str], inputs: list[list], expected: list): From 43cf917705fa696739740376c70874930ee7e436 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 18:36:15 +0700 Subject: [PATCH 23/39] feat: delete generated problems from old template --- .templates/leetcode/examples_old/README.md | 41 ---- .templates/leetcode/examples_old/basic.json5 | 88 ------- .templates/leetcode/examples_old/design.json5 | 84 ------- .../leetcode/json_old/accounts_merge.json | 47 ---- .templates/leetcode/json_old/add_binary.json | 43 ---- .../json_old/balanced_binary_tree.json | 48 ---- .../leetcode/json_old/basic_calculator.json | 39 --- .../best_time_to_buy_and_sell_stock.json | 47 ---- .../leetcode/json_old/binary_search.json | 47 ---- .../binary_tree_level_order_traversal.json | 46 ---- .../json_old/binary_tree_right_side_view.json | 49 ---- .../leetcode/json_old/climbing_stairs.json | 42 ---- .templates/leetcode/json_old/clone_graph.json | 50 ---- .templates/leetcode/json_old/coin_change.json | 46 ---- .../leetcode/json_old/combination_sum.json | 48 ---- .../json_old/container_with_most_water.json | 45 ---- .../leetcode/json_old/contains_duplicate.json | 48 ---- .../leetcode/json_old/course_schedule.json | 47 ---- .../json_old/diameter_of_binary_tree.json | 45 ---- .../evaluate_reverse_polish_notation.json | 50 ---- .../find_median_from_data_stream.json | 39 --- .../leetcode/json_old/first_bad_version.json | 56 ----- .templates/leetcode/json_old/flood_fill.json | 47 ---- .../implement_queue_using_stacks.json | 41 ---- .../json_old/implement_trie_prefix_tree.json | 50 ---- .../leetcode/json_old/insert_interval.json | 47 ---- .../leetcode/json_old/invert_binary_tree.json | 44 ---- .../json_old/k_closest_points_to_origin.json | 47 ---- .../kth_smallest_element_in_a_bst.json | 47 ---- .../largest_rectangle_in_histogram.json | 47 ---- .../leetcode/json_old/linked_list_cycle.json | 55 ----- .../leetcode/json_old/longest_palindrome.json | 47 ---- .../longest_palindromic_substring.json | 45 ---- ...ubstring_without_repeating_characters.json | 50 ---- ...mmon_ancestor_of_a_binary_search_tree.json | 48 ---- ...west_common_ancestor_of_a_binary_tree.json | 48 ---- .templates/leetcode/json_old/lru_cache.json | 44 ---- .../leetcode/json_old/majority_element.json | 43 ---- .../maximum_depth_of_binary_tree.json | 45 ---- .../maximum_profit_in_job_scheduling.json | 50 ---- .../leetcode/json_old/maximum_subarray.json | 50 ---- .../leetcode/json_old/merge_intervals.json | 50 ---- .../json_old/merge_k_sorted_lists.json | 46 ---- .../json_old/merge_two_sorted_lists.json | 46 ---- .../json_old/middle_of_the_linked_list.json | 47 ---- .templates/leetcode/json_old/min_stack.json | 41 ---- .../json_old/minimum_height_trees.json | 47 ---- .../json_old/minimum_window_substring.json | 50 ---- .../leetcode/json_old/number_of_islands.json | 47 ---- .../json_old/partition_equal_subset_sum.json | 47 ---- .../leetcode/json_old/permutations.json | 46 ---- .../product_of_array_except_self.json | 43 ---- .templates/leetcode/json_old/ransom_note.json | 44 ---- .../json_old/reverse_linked_list.json | 48 ---- .../json_old/reverse_linked_list_ii.json | 43 ---- .../leetcode/json_old/rotting_oranges.json | 50 ---- .../search_in_rotated_sorted_array.json | 44 ---- ...serialize_and_deserialize_binary_tree.json | 52 ---- .templates/leetcode/json_old/sort_colors.json | 43 ---- .../leetcode/json_old/spiral_matrix.json | 47 ---- .../json_old/string_to_integer_atoi.json | 51 ---- .../leetcode/json_old/task_scheduler.json | 50 ---- .templates/leetcode/json_old/three_sum.json | 50 ---- .../json_old/time_based_key_value_store.json | 49 ---- .../json_old/trapping_rain_water.json | 40 ---- .../leetcode/json_old/valid_anagram.json | 43 ---- .../leetcode/json_old/valid_palindrome.json | 50 ---- .../leetcode/json_old/valid_parentheses.json | 41 ---- .../json_old/validate_binary_search_tree.json | 47 ---- .templates/leetcode/json_old/word_break.json | 50 ---- .templates/leetcode/json_old/word_ladder.json | 47 ---- .../leetcode/json_old/zero_one_matrix.json | 47 ---- Makefile | 6 +- leetcode_ideal/clone_graph/README.md | 72 ------ leetcode_ideal/clone_graph/__init__.py | 0 leetcode_ideal/clone_graph/helpers.py | 22 -- leetcode_ideal/clone_graph/playground.ipynb | 149 ------------ leetcode_ideal/clone_graph/solution.py | 73 ------ leetcode_ideal/clone_graph/test_solution.py | 24 -- leetcode_ideal/contains_duplicate/README.md | 43 ---- leetcode_ideal/contains_duplicate/__init__.py | 0 leetcode_ideal/contains_duplicate/helpers.py | 7 - .../contains_duplicate/playground.ipynb | 91 ------- leetcode_ideal/contains_duplicate/solution.py | 10 - .../contains_duplicate/test_solution.py | 31 --- leetcode_ideal/first_bad_version/README.md | 47 ---- leetcode_ideal/first_bad_version/__init__.py | 0 leetcode_ideal/first_bad_version/helpers.py | 8 - .../first_bad_version/playground.ipynb | 92 ------- leetcode_ideal/first_bad_version/solution.py | 45 ---- .../first_bad_version/test_solution.py | 18 -- .../implement_trie_prefix_tree/README.md | 48 ---- .../implement_trie_prefix_tree/__init__.py | 0 .../implement_trie_prefix_tree/helpers.py | 25 -- .../playground.ipynb | 204 ---------------- .../implement_trie_prefix_tree/solution.py | 40 ---- .../test_solution.py | 41 ---- leetcode_ideal/invert_binary_tree/README.md | 39 --- leetcode_ideal/invert_binary_tree/__init__.py | 0 leetcode_ideal/invert_binary_tree/helpers.py | 15 -- .../invert_binary_tree/playground.ipynb | 185 -------------- leetcode_ideal/invert_binary_tree/solution.py | 59 ----- .../invert_binary_tree/test_solution.py | 25 -- leetcode_ideal/linked_list_cycle/README.md | 58 ----- leetcode_ideal/linked_list_cycle/__init__.py | 0 leetcode_ideal/linked_list_cycle/helpers.py | 27 --- .../linked_list_cycle/playground.ipynb | 92 ------- leetcode_ideal/linked_list_cycle/solution.py | 18 -- .../linked_list_cycle/test_solution.py | 38 --- leetcode_ideal/lru_cache/README.md | 50 ---- leetcode_ideal/lru_cache/__init__.py | 0 leetcode_ideal/lru_cache/helpers.py | 23 -- leetcode_ideal/lru_cache/playground.ipynb | 100 -------- leetcode_ideal/lru_cache/solution.py | 126 ---------- leetcode_ideal/lru_cache/test_solution.py | 40 ---- leetcode_ideal/reverse_linked_list/README.md | 45 ---- .../reverse_linked_list/__init__.py | 0 leetcode_ideal/reverse_linked_list/helpers.py | 15 -- .../reverse_linked_list/playground.ipynb | 159 ------------- .../reverse_linked_list/solution.py | 46 ---- .../reverse_linked_list/test_solution.py | 29 --- .../README.md | 38 --- .../__init__.py | 0 .../helpers.py | 26 -- .../playground.ipynb | 159 ------------- .../solution.py | 81 ------- .../test_solution.py | 42 ---- .../tests.py | 100 -------- leetcode_ideal/valid_parentheses/README.md | 59 ----- leetcode_ideal/valid_parentheses/__init__.py | 0 leetcode_ideal/valid_parentheses/helpers.py | 7 - .../valid_parentheses/playground.ipynb | 91 ------- leetcode_ideal/valid_parentheses/solution.py | 14 -- .../valid_parentheses/test_solution.py | 44 ---- leetcode_old/__init__.py | 0 leetcode_old/accounts_merge/README.md | 41 ---- leetcode_old/accounts_merge/__init__.py | 0 leetcode_old/accounts_merge/playground.ipynb | 93 -------- leetcode_old/accounts_merge/solution.py | 34 --- leetcode_old/accounts_merge/tests.py | 112 --------- leetcode_old/add_binary/README.md | 33 --- leetcode_old/add_binary/__init__.py | 0 leetcode_old/add_binary/playground.ipynb | 80 ------- leetcode_old/add_binary/solution.py | 26 -- leetcode_old/add_binary/tests.py | 25 -- leetcode_old/balanced_binary_tree/README.md | 45 ---- leetcode_old/balanced_binary_tree/__init__.py | 0 .../balanced_binary_tree/playground.ipynb | 171 ------------- leetcode_old/balanced_binary_tree/solution.py | 20 -- leetcode_old/balanced_binary_tree/tests.py | 33 --- leetcode_old/basic_calculator/README.md | 46 ---- leetcode_old/basic_calculator/__init__.py | 0 .../basic_calculator/playground.ipynb | 79 ------ leetcode_old/basic_calculator/solution.py | 61 ----- leetcode_old/basic_calculator/tests.py | 68 ------ .../best_time_to_buy_and_sell_stock/README.md | 41 ---- .../__init__.py | 0 .../playground.ipynb | 79 ------ .../solution.py | 12 - .../best_time_to_buy_and_sell_stock/tests.py | 28 --- leetcode_old/binary_search/README.md | 40 ---- leetcode_old/binary_search/__init__.py | 0 leetcode_old/binary_search/playground.ipynb | 69 ------ leetcode_old/binary_search/solution.py | 17 -- leetcode_old/binary_search/tests.py | 39 --- .../README.md | 41 ---- .../__init__.py | 0 .../playground.ipynb | 71 ------ .../solution.py | 31 --- .../tests.py | 39 --- .../binary_tree_right_side_view/README.md | 50 ---- .../binary_tree_right_side_view/__init__.py | 0 .../playground.ipynb | 82 ------- .../binary_tree_right_side_view/solution.py | 67 ------ .../binary_tree_right_side_view/tests.py | 30 --- leetcode_old/climbing_stairs/README.md | 44 ---- leetcode_old/climbing_stairs/__init__.py | 0 leetcode_old/climbing_stairs/playground.ipynb | 79 ------ leetcode_old/climbing_stairs/solution.py | 16 -- leetcode_old/climbing_stairs/tests.py | 19 -- leetcode_old/clone_graph/README.md | 72 ------ leetcode_old/clone_graph/__init__.py | 0 leetcode_old/clone_graph/playground.ipynb | 209 ---------------- leetcode_old/clone_graph/solution.py | 73 ------ leetcode_old/clone_graph/tests.py | 25 -- leetcode_old/coin_change/README.md | 46 ---- leetcode_old/coin_change/__init__.py | 0 leetcode_old/coin_change/playground.ipynb | 80 ------- leetcode_old/coin_change/solution.py | 21 -- leetcode_old/coin_change/tests.py | 31 --- leetcode_old/combination_sum/README.md | 47 ---- leetcode_old/combination_sum/__init__.py | 0 leetcode_old/combination_sum/playground.ipynb | 74 ------ leetcode_old/combination_sum/solution.py | 19 -- leetcode_old/combination_sum/tests.py | 44 ---- .../container_with_most_water/README.md | 42 ---- .../container_with_most_water/__init__.py | 0 .../playground.ipynb | 68 ------ .../container_with_most_water/solution.py | 17 -- .../container_with_most_water/tests.py | 40 ---- leetcode_old/contains_duplicate/README.md | 43 ---- leetcode_old/contains_duplicate/__init__.py | 0 .../contains_duplicate/playground.ipynb | 68 ------ leetcode_old/contains_duplicate/solution.py | 10 - leetcode_old/contains_duplicate/tests.py | 32 --- leetcode_old/course_schedule/README.md | 43 ---- leetcode_old/course_schedule/__init__.py | 0 leetcode_old/course_schedule/playground.ipynb | 69 ------ leetcode_old/course_schedule/solution.py | 29 --- leetcode_old/course_schedule/tests.py | 40 ---- .../diameter_of_binary_tree/README.md | 40 ---- .../diameter_of_binary_tree/__init__.py | 0 .../diameter_of_binary_tree/playground.ipynb | 171 ------------- .../diameter_of_binary_tree/solution.py | 22 -- leetcode_old/diameter_of_binary_tree/tests.py | 31 --- .../README.md | 56 ----- .../__init__.py | 0 .../playground.ipynb | 79 ------ .../solution.py | 20 -- .../evaluate_reverse_polish_notation/tests.py | 38 --- .../find_median_from_data_stream/README.md | 54 ----- .../find_median_from_data_stream/__init__.py | 0 .../playground.ipynb | 79 ------ .../find_median_from_data_stream/solution.py | 115 --------- .../find_median_from_data_stream/tests.py | 71 ------ leetcode_old/first_bad_version/README.md | 47 ---- leetcode_old/first_bad_version/__init__.py | 0 .../first_bad_version/playground.ipynb | 81 ------- leetcode_old/first_bad_version/solution.py | 45 ---- leetcode_old/first_bad_version/tests.py | 17 -- leetcode_old/flood_fill/README.md | 51 ---- leetcode_old/flood_fill/__init__.py | 0 leetcode_old/flood_fill/playground.ipynb | 71 ------ leetcode_old/flood_fill/solution.py | 17 -- leetcode_old/flood_fill/tests.py | 26 -- .../implement_queue_using_stacks/README.md | 54 ----- .../implement_queue_using_stacks/__init__.py | 0 .../playground.ipynb | 81 ------- .../implement_queue_using_stacks/solution.py | 51 ---- .../implement_queue_using_stacks/tests.py | 48 ---- .../implement_trie_prefix_tree/README.md | 48 ---- .../implement_trie_prefix_tree/__init__.py | 0 .../playground.ipynb | 217 ----------------- .../implement_trie_prefix_tree/solution.py | 40 ---- .../implement_trie_prefix_tree/tests.py | 52 ---- leetcode_old/insert_interval/README.md | 41 ---- leetcode_old/insert_interval/__init__.py | 0 leetcode_old/insert_interval/playground.ipynb | 69 ------ leetcode_old/insert_interval/solution.py | 22 -- leetcode_old/insert_interval/tests.py | 41 ---- leetcode_old/invert_binary_tree/README.md | 39 --- leetcode_old/invert_binary_tree/__init__.py | 0 .../invert_binary_tree/playground.ipynb | 71 ------ leetcode_old/invert_binary_tree/solution.py | 59 ----- leetcode_old/invert_binary_tree/tests.py | 26 -- .../k_closest_points_to_origin/README.md | 42 ---- .../k_closest_points_to_origin/__init__.py | 0 .../playground.ipynb | 69 ------ .../k_closest_points_to_origin/solution.py | 16 -- .../k_closest_points_to_origin/tests.py | 38 --- .../kth_smallest_element_in_a_bst/README.md | 39 --- .../kth_smallest_element_in_a_bst/__init__.py | 0 .../playground.ipynb | 160 ------------- .../kth_smallest_element_in_a_bst/solution.py | 41 ---- .../kth_smallest_element_in_a_bst/tests.py | 21 -- .../largest_rectangle_in_histogram/README.md | 38 --- .../__init__.py | 0 .../playground.ipynb | 79 ------ .../solution.py | 29 --- .../largest_rectangle_in_histogram/tests.py | 46 ---- leetcode_old/linked_list_cycle/README.md | 58 ----- leetcode_old/linked_list_cycle/__init__.py | 0 .../linked_list_cycle/playground.ipynb | 87 ------- leetcode_old/linked_list_cycle/solution.py | 18 -- leetcode_old/linked_list_cycle/tests.py | 59 ----- leetcode_old/longest_palindrome/README.md | 38 --- leetcode_old/longest_palindrome/__init__.py | 0 .../longest_palindrome/playground.ipynb | 68 ------ leetcode_old/longest_palindrome/solution.py | 17 -- leetcode_old/longest_palindrome/tests.py | 31 --- .../longest_palindromic_substring/README.md | 34 --- .../longest_palindromic_substring/__init__.py | 0 .../playground.ipynb | 79 ------ .../longest_palindromic_substring/solution.py | 52 ---- .../longest_palindromic_substring/tests.py | 74 ------ .../README.md | 46 ---- .../__init__.py | 0 .../playground.ipynb | 79 ------ .../solution.py | 15 -- .../tests.py | 35 --- .../README.md | 52 ---- .../__init__.py | 0 .../playground.ipynb | 225 ------------------ .../solution.py | 20 -- .../tests.py | 35 --- .../README.md | 50 ---- .../__init__.py | 0 .../playground.ipynb | 222 ----------------- .../solution.py | 21 -- .../tests.py | 40 ---- leetcode_old/lru_cache/README.md | 50 ---- leetcode_old/lru_cache/__init__.py | 0 leetcode_old/lru_cache/playground.ipynb | 79 ------ leetcode_old/lru_cache/solution.py | 108 --------- leetcode_old/lru_cache/tests.py | 49 ---- leetcode_old/majority_element/README.md | 37 --- leetcode_old/majority_element/__init__.py | 0 .../majority_element/playground.ipynb | 79 ------ leetcode_old/majority_element/solution.py | 14 -- leetcode_old/majority_element/tests.py | 19 -- .../maximum_depth_of_binary_tree/README.md | 36 --- .../maximum_depth_of_binary_tree/__init__.py | 0 .../playground.ipynb | 171 ------------- .../maximum_depth_of_binary_tree/solution.py | 14 -- .../maximum_depth_of_binary_tree/tests.py | 39 --- .../README.md | 54 ----- .../__init__.py | 0 .../playground.ipynb | 174 -------------- .../solution.py | 51 ---- .../maximum_profit_in_job_scheduling/tests.py | 34 --- leetcode_old/maximum_subarray/README.md | 47 ---- leetcode_old/maximum_subarray/__init__.py | 0 .../maximum_subarray/playground.ipynb | 68 ------ leetcode_old/maximum_subarray/solution.py | 11 - leetcode_old/maximum_subarray/tests.py | 27 --- leetcode_old/merge_intervals/README.md | 46 ---- leetcode_old/merge_intervals/__init__.py | 0 leetcode_old/merge_intervals/playground.ipynb | 68 ------ leetcode_old/merge_intervals/solution.py | 14 -- leetcode_old/merge_intervals/tests.py | 32 --- leetcode_old/merge_k_sorted_lists/README.md | 61 ----- leetcode_old/merge_k_sorted_lists/__init__.py | 0 .../merge_k_sorted_lists/playground.ipynb | 83 ------- leetcode_old/merge_k_sorted_lists/solution.py | 35 --- leetcode_old/merge_k_sorted_lists/tests.py | 45 ---- leetcode_old/merge_two_sorted_lists/README.md | 46 ---- .../merge_two_sorted_lists/__init__.py | 0 .../merge_two_sorted_lists/playground.ipynb | 74 ------ .../merge_two_sorted_lists/solution.py | 22 -- leetcode_old/merge_two_sorted_lists/tests.py | 37 --- .../middle_of_the_linked_list/README.md | 42 ---- .../middle_of_the_linked_list/__init__.py | 0 .../playground.ipynb | 127 ---------- .../middle_of_the_linked_list/solution.py | 13 - .../middle_of_the_linked_list/tests.py | 31 --- leetcode_old/min_stack/README.md | 53 ----- leetcode_old/min_stack/__init__.py | 0 leetcode_old/min_stack/playground.ipynb | 95 -------- leetcode_old/min_stack/solution.py | 43 ---- leetcode_old/min_stack/tests.py | 52 ---- leetcode_old/minimum_height_trees/README.md | 47 ---- leetcode_old/minimum_height_trees/__init__.py | 0 .../minimum_height_trees/playground.ipynb | 80 ------- leetcode_old/minimum_height_trees/solution.py | 29 --- leetcode_old/minimum_height_trees/tests.py | 23 -- .../minimum_window_substring/README.md | 51 ---- .../minimum_window_substring/__init__.py | 0 .../minimum_window_substring/playground.ipynb | 80 ------- .../minimum_window_substring/solution.py | 48 ---- .../minimum_window_substring/tests.py | 39 --- leetcode_old/number_of_islands/README.md | 46 ---- leetcode_old/number_of_islands/__init__.py | 0 .../number_of_islands/playground.ipynb | 73 ------ leetcode_old/number_of_islands/solution.py | 27 --- leetcode_old/number_of_islands/tests.py | 67 ------ .../partition_equal_subset_sum/README.md | 36 --- .../partition_equal_subset_sum/__init__.py | 0 .../playground.ipynb | 79 ------ .../partition_equal_subset_sum/solution.py | 92 ------- .../partition_equal_subset_sum/tests.py | 56 ----- leetcode_old/permutations/README.md | 40 ---- leetcode_old/permutations/__init__.py | 0 leetcode_old/permutations/playground.ipynb | 75 ------ leetcode_old/permutations/solution.py | 18 -- leetcode_old/permutations/tests.py | 43 ---- .../product_of_array_except_self/README.md | 39 --- .../product_of_array_except_self/__init__.py | 0 .../playground.ipynb | 79 ------ .../product_of_array_except_self/solution.py | 25 -- .../product_of_array_except_self/tests.py | 38 --- leetcode_old/ransom_note/README.md | 41 ---- leetcode_old/ransom_note/__init__.py | 0 leetcode_old/ransom_note/playground.ipynb | 80 ------- leetcode_old/ransom_note/solution.py | 18 -- leetcode_old/ransom_note/tests.py | 38 --- leetcode_old/reverse_linked_list/README.md | 45 ---- leetcode_old/reverse_linked_list/__init__.py | 0 .../reverse_linked_list/playground.ipynb | 174 -------------- leetcode_old/reverse_linked_list/solution.py | 46 ---- leetcode_old/reverse_linked_list/tests.py | 44 ---- leetcode_old/reverse_linked_list_ii/README.md | 36 --- .../reverse_linked_list_ii/__init__.py | 0 .../reverse_linked_list_ii/playground.ipynb | 72 ------ .../reverse_linked_list_ii/solution.py | 52 ---- leetcode_old/reverse_linked_list_ii/tests.py | 24 -- leetcode_old/rotting_oranges/README.md | 55 ----- leetcode_old/rotting_oranges/__init__.py | 0 leetcode_old/rotting_oranges/playground.ipynb | 68 ------ leetcode_old/rotting_oranges/solution.py | 43 ---- leetcode_old/rotting_oranges/tests.py | 60 ----- .../search_in_rotated_sorted_array/README.md | 48 ---- .../__init__.py | 0 .../playground.ipynb | 80 ------- .../solution.py | 26 -- .../search_in_rotated_sorted_array/tests.py | 41 ---- .../README.md | 38 --- .../__init__.py | 0 .../playground.ipynb | 167 ------------- .../solution.py | 81 ------- .../tests.py | 100 -------- leetcode_old/sort_colors/README.md | 39 --- leetcode_old/sort_colors/__init__.py | 0 leetcode_old/sort_colors/playground.ipynb | 80 ------- leetcode_old/sort_colors/solution.py | 19 -- leetcode_old/sort_colors/tests.py | 27 --- leetcode_old/spiral_matrix/README.md | 38 --- leetcode_old/spiral_matrix/__init__.py | 0 leetcode_old/spiral_matrix/playground.ipynb | 68 ------ leetcode_old/spiral_matrix/solution.py | 41 ---- leetcode_old/spiral_matrix/tests.py | 40 ---- leetcode_old/string_to_integer_atoi/README.md | 109 --------- .../string_to_integer_atoi/__init__.py | 0 .../string_to_integer_atoi/playground.ipynb | 79 ------ .../string_to_integer_atoi/solution.py | 30 --- leetcode_old/string_to_integer_atoi/tests.py | 49 ---- leetcode_old/task_scheduler/README.md | 54 ----- leetcode_old/task_scheduler/__init__.py | 0 leetcode_old/task_scheduler/playground.ipynb | 88 ------- leetcode_old/task_scheduler/solution.py | 65 ----- leetcode_old/task_scheduler/tests.py | 30 --- leetcode_old/three_sum/README.md | 52 ---- leetcode_old/three_sum/__init__.py | 0 leetcode_old/three_sum/playground.ipynb | 73 ------ leetcode_old/three_sum/solution.py | 23 -- leetcode_old/three_sum/tests.py | 43 ---- .../time_based_key_value_store/README.md | 49 ---- .../time_based_key_value_store/__init__.py | 0 .../playground.ipynb | 89 ------- .../time_based_key_value_store/solution.py | 34 --- .../time_based_key_value_store/tests.py | 40 ---- leetcode_old/trapping_rain_water/README.md | 37 --- leetcode_old/trapping_rain_water/__init__.py | 0 .../trapping_rain_water/playground.ipynb | 79 ------ leetcode_old/trapping_rain_water/solution.py | 52 ---- leetcode_old/trapping_rain_water/tests.py | 42 ---- leetcode_old/valid_anagram/README.md | 34 --- leetcode_old/valid_anagram/__init__.py | 0 leetcode_old/valid_anagram/playground.ipynb | 80 ------- leetcode_old/valid_anagram/solution.py | 8 - leetcode_old/valid_anagram/tests.py | 38 --- leetcode_old/valid_palindrome/README.md | 47 ---- leetcode_old/valid_palindrome/__init__.py | 0 .../valid_palindrome/playground.ipynb | 68 ------ leetcode_old/valid_palindrome/solution.py | 19 -- leetcode_old/valid_palindrome/tests.py | 28 --- leetcode_old/valid_parentheses/README.md | 59 ----- leetcode_old/valid_parentheses/__init__.py | 0 .../valid_parentheses/playground.ipynb | 68 ------ leetcode_old/valid_parentheses/solution.py | 14 -- leetcode_old/valid_parentheses/tests.py | 46 ---- .../validate_binary_search_tree/README.md | 44 ---- .../validate_binary_search_tree/__init__.py | 0 .../playground.ipynb | 82 ------- .../validate_binary_search_tree/solution.py | 60 ----- .../validate_binary_search_tree/tests.py | 30 --- leetcode_old/word_break/README.md | 49 ---- leetcode_old/word_break/__init__.py | 0 leetcode_old/word_break/playground.ipynb | 80 ------- leetcode_old/word_break/solution.py | 15 -- leetcode_old/word_break/tests.py | 27 --- leetcode_old/word_ladder/README.md | 47 ---- leetcode_old/word_ladder/__init__.py | 0 leetcode_old/word_ladder/playground.ipynb | 81 ------- leetcode_old/word_ladder/solution.py | 36 --- leetcode_old/word_ladder/tests.py | 42 ---- leetcode_old/zero_one_matrix/README.md | 44 ---- leetcode_old/zero_one_matrix/__init__.py | 0 leetcode_old/zero_one_matrix/playground.ipynb | 68 ------ leetcode_old/zero_one_matrix/solution.py | 30 --- leetcode_old/zero_one_matrix/tests.py | 29 --- 480 files changed, 2 insertions(+), 21275 deletions(-) delete mode 100644 .templates/leetcode/examples_old/README.md delete mode 100644 .templates/leetcode/examples_old/basic.json5 delete mode 100644 .templates/leetcode/examples_old/design.json5 delete mode 100644 .templates/leetcode/json_old/accounts_merge.json delete mode 100644 .templates/leetcode/json_old/add_binary.json delete mode 100644 .templates/leetcode/json_old/balanced_binary_tree.json delete mode 100644 .templates/leetcode/json_old/basic_calculator.json delete mode 100644 .templates/leetcode/json_old/best_time_to_buy_and_sell_stock.json delete mode 100644 .templates/leetcode/json_old/binary_search.json delete mode 100644 .templates/leetcode/json_old/binary_tree_level_order_traversal.json delete mode 100644 .templates/leetcode/json_old/binary_tree_right_side_view.json delete mode 100644 .templates/leetcode/json_old/climbing_stairs.json delete mode 100644 .templates/leetcode/json_old/clone_graph.json delete mode 100644 .templates/leetcode/json_old/coin_change.json delete mode 100644 .templates/leetcode/json_old/combination_sum.json delete mode 100644 .templates/leetcode/json_old/container_with_most_water.json delete mode 100644 .templates/leetcode/json_old/contains_duplicate.json delete mode 100644 .templates/leetcode/json_old/course_schedule.json delete mode 100644 .templates/leetcode/json_old/diameter_of_binary_tree.json delete mode 100644 .templates/leetcode/json_old/evaluate_reverse_polish_notation.json delete mode 100644 .templates/leetcode/json_old/find_median_from_data_stream.json delete mode 100644 .templates/leetcode/json_old/first_bad_version.json delete mode 100644 .templates/leetcode/json_old/flood_fill.json delete mode 100644 .templates/leetcode/json_old/implement_queue_using_stacks.json delete mode 100644 .templates/leetcode/json_old/implement_trie_prefix_tree.json delete mode 100644 .templates/leetcode/json_old/insert_interval.json delete mode 100644 .templates/leetcode/json_old/invert_binary_tree.json delete mode 100644 .templates/leetcode/json_old/k_closest_points_to_origin.json delete mode 100644 .templates/leetcode/json_old/kth_smallest_element_in_a_bst.json delete mode 100644 .templates/leetcode/json_old/largest_rectangle_in_histogram.json delete mode 100644 .templates/leetcode/json_old/linked_list_cycle.json delete mode 100644 .templates/leetcode/json_old/longest_palindrome.json delete mode 100644 .templates/leetcode/json_old/longest_palindromic_substring.json delete mode 100644 .templates/leetcode/json_old/longest_substring_without_repeating_characters.json delete mode 100644 .templates/leetcode/json_old/lowest_common_ancestor_of_a_binary_search_tree.json delete mode 100644 .templates/leetcode/json_old/lowest_common_ancestor_of_a_binary_tree.json delete mode 100644 .templates/leetcode/json_old/lru_cache.json delete mode 100644 .templates/leetcode/json_old/majority_element.json delete mode 100644 .templates/leetcode/json_old/maximum_depth_of_binary_tree.json delete mode 100644 .templates/leetcode/json_old/maximum_profit_in_job_scheduling.json delete mode 100644 .templates/leetcode/json_old/maximum_subarray.json delete mode 100644 .templates/leetcode/json_old/merge_intervals.json delete mode 100644 .templates/leetcode/json_old/merge_k_sorted_lists.json delete mode 100644 .templates/leetcode/json_old/merge_two_sorted_lists.json delete mode 100644 .templates/leetcode/json_old/middle_of_the_linked_list.json delete mode 100644 .templates/leetcode/json_old/min_stack.json delete mode 100644 .templates/leetcode/json_old/minimum_height_trees.json delete mode 100644 .templates/leetcode/json_old/minimum_window_substring.json delete mode 100644 .templates/leetcode/json_old/number_of_islands.json delete mode 100644 .templates/leetcode/json_old/partition_equal_subset_sum.json delete mode 100644 .templates/leetcode/json_old/permutations.json delete mode 100644 .templates/leetcode/json_old/product_of_array_except_self.json delete mode 100644 .templates/leetcode/json_old/ransom_note.json delete mode 100644 .templates/leetcode/json_old/reverse_linked_list.json delete mode 100644 .templates/leetcode/json_old/reverse_linked_list_ii.json delete mode 100644 .templates/leetcode/json_old/rotting_oranges.json delete mode 100644 .templates/leetcode/json_old/search_in_rotated_sorted_array.json delete mode 100644 .templates/leetcode/json_old/serialize_and_deserialize_binary_tree.json delete mode 100644 .templates/leetcode/json_old/sort_colors.json delete mode 100644 .templates/leetcode/json_old/spiral_matrix.json delete mode 100644 .templates/leetcode/json_old/string_to_integer_atoi.json delete mode 100644 .templates/leetcode/json_old/task_scheduler.json delete mode 100644 .templates/leetcode/json_old/three_sum.json delete mode 100644 .templates/leetcode/json_old/time_based_key_value_store.json delete mode 100644 .templates/leetcode/json_old/trapping_rain_water.json delete mode 100644 .templates/leetcode/json_old/valid_anagram.json delete mode 100644 .templates/leetcode/json_old/valid_palindrome.json delete mode 100644 .templates/leetcode/json_old/valid_parentheses.json delete mode 100644 .templates/leetcode/json_old/validate_binary_search_tree.json delete mode 100644 .templates/leetcode/json_old/word_break.json delete mode 100644 .templates/leetcode/json_old/word_ladder.json delete mode 100644 .templates/leetcode/json_old/zero_one_matrix.json delete mode 100644 leetcode_ideal/clone_graph/README.md delete mode 100644 leetcode_ideal/clone_graph/__init__.py delete mode 100644 leetcode_ideal/clone_graph/helpers.py delete mode 100644 leetcode_ideal/clone_graph/playground.ipynb delete mode 100644 leetcode_ideal/clone_graph/solution.py delete mode 100644 leetcode_ideal/clone_graph/test_solution.py delete mode 100644 leetcode_ideal/contains_duplicate/README.md delete mode 100644 leetcode_ideal/contains_duplicate/__init__.py delete mode 100644 leetcode_ideal/contains_duplicate/helpers.py delete mode 100644 leetcode_ideal/contains_duplicate/playground.ipynb delete mode 100644 leetcode_ideal/contains_duplicate/solution.py delete mode 100644 leetcode_ideal/contains_duplicate/test_solution.py delete mode 100644 leetcode_ideal/first_bad_version/README.md delete mode 100644 leetcode_ideal/first_bad_version/__init__.py delete mode 100644 leetcode_ideal/first_bad_version/helpers.py delete mode 100644 leetcode_ideal/first_bad_version/playground.ipynb delete mode 100644 leetcode_ideal/first_bad_version/solution.py delete mode 100644 leetcode_ideal/first_bad_version/test_solution.py delete mode 100644 leetcode_ideal/implement_trie_prefix_tree/README.md delete mode 100644 leetcode_ideal/implement_trie_prefix_tree/__init__.py delete mode 100644 leetcode_ideal/implement_trie_prefix_tree/helpers.py delete mode 100644 leetcode_ideal/implement_trie_prefix_tree/playground.ipynb delete mode 100644 leetcode_ideal/implement_trie_prefix_tree/solution.py delete mode 100644 leetcode_ideal/implement_trie_prefix_tree/test_solution.py delete mode 100644 leetcode_ideal/invert_binary_tree/README.md delete mode 100644 leetcode_ideal/invert_binary_tree/__init__.py delete mode 100644 leetcode_ideal/invert_binary_tree/helpers.py delete mode 100644 leetcode_ideal/invert_binary_tree/playground.ipynb delete mode 100644 leetcode_ideal/invert_binary_tree/solution.py delete mode 100644 leetcode_ideal/invert_binary_tree/test_solution.py delete mode 100644 leetcode_ideal/linked_list_cycle/README.md delete mode 100644 leetcode_ideal/linked_list_cycle/__init__.py delete mode 100644 leetcode_ideal/linked_list_cycle/helpers.py delete mode 100644 leetcode_ideal/linked_list_cycle/playground.ipynb delete mode 100644 leetcode_ideal/linked_list_cycle/solution.py delete mode 100644 leetcode_ideal/linked_list_cycle/test_solution.py delete mode 100644 leetcode_ideal/lru_cache/README.md delete mode 100644 leetcode_ideal/lru_cache/__init__.py delete mode 100644 leetcode_ideal/lru_cache/helpers.py delete mode 100644 leetcode_ideal/lru_cache/playground.ipynb delete mode 100644 leetcode_ideal/lru_cache/solution.py delete mode 100644 leetcode_ideal/lru_cache/test_solution.py delete mode 100644 leetcode_ideal/reverse_linked_list/README.md delete mode 100644 leetcode_ideal/reverse_linked_list/__init__.py delete mode 100644 leetcode_ideal/reverse_linked_list/helpers.py delete mode 100644 leetcode_ideal/reverse_linked_list/playground.ipynb delete mode 100644 leetcode_ideal/reverse_linked_list/solution.py delete mode 100644 leetcode_ideal/reverse_linked_list/test_solution.py delete mode 100644 leetcode_ideal/serialize_and_deserialize_binary_tree/README.md delete mode 100644 leetcode_ideal/serialize_and_deserialize_binary_tree/__init__.py delete mode 100644 leetcode_ideal/serialize_and_deserialize_binary_tree/helpers.py delete mode 100644 leetcode_ideal/serialize_and_deserialize_binary_tree/playground.ipynb delete mode 100644 leetcode_ideal/serialize_and_deserialize_binary_tree/solution.py delete mode 100644 leetcode_ideal/serialize_and_deserialize_binary_tree/test_solution.py delete mode 100644 leetcode_ideal/serialize_and_deserialize_binary_tree/tests.py delete mode 100644 leetcode_ideal/valid_parentheses/README.md delete mode 100644 leetcode_ideal/valid_parentheses/__init__.py delete mode 100644 leetcode_ideal/valid_parentheses/helpers.py delete mode 100644 leetcode_ideal/valid_parentheses/playground.ipynb delete mode 100644 leetcode_ideal/valid_parentheses/solution.py delete mode 100644 leetcode_ideal/valid_parentheses/test_solution.py delete mode 100644 leetcode_old/__init__.py delete mode 100644 leetcode_old/accounts_merge/README.md delete mode 100644 leetcode_old/accounts_merge/__init__.py delete mode 100644 leetcode_old/accounts_merge/playground.ipynb delete mode 100644 leetcode_old/accounts_merge/solution.py delete mode 100644 leetcode_old/accounts_merge/tests.py delete mode 100644 leetcode_old/add_binary/README.md delete mode 100644 leetcode_old/add_binary/__init__.py delete mode 100644 leetcode_old/add_binary/playground.ipynb delete mode 100644 leetcode_old/add_binary/solution.py delete mode 100644 leetcode_old/add_binary/tests.py delete mode 100644 leetcode_old/balanced_binary_tree/README.md delete mode 100644 leetcode_old/balanced_binary_tree/__init__.py delete mode 100644 leetcode_old/balanced_binary_tree/playground.ipynb delete mode 100644 leetcode_old/balanced_binary_tree/solution.py delete mode 100644 leetcode_old/balanced_binary_tree/tests.py delete mode 100644 leetcode_old/basic_calculator/README.md delete mode 100644 leetcode_old/basic_calculator/__init__.py delete mode 100644 leetcode_old/basic_calculator/playground.ipynb delete mode 100644 leetcode_old/basic_calculator/solution.py delete mode 100644 leetcode_old/basic_calculator/tests.py delete mode 100644 leetcode_old/best_time_to_buy_and_sell_stock/README.md delete mode 100644 leetcode_old/best_time_to_buy_and_sell_stock/__init__.py delete mode 100644 leetcode_old/best_time_to_buy_and_sell_stock/playground.ipynb delete mode 100644 leetcode_old/best_time_to_buy_and_sell_stock/solution.py delete mode 100644 leetcode_old/best_time_to_buy_and_sell_stock/tests.py delete mode 100644 leetcode_old/binary_search/README.md delete mode 100644 leetcode_old/binary_search/__init__.py delete mode 100644 leetcode_old/binary_search/playground.ipynb delete mode 100644 leetcode_old/binary_search/solution.py delete mode 100644 leetcode_old/binary_search/tests.py delete mode 100644 leetcode_old/binary_tree_level_order_traversal/README.md delete mode 100644 leetcode_old/binary_tree_level_order_traversal/__init__.py delete mode 100644 leetcode_old/binary_tree_level_order_traversal/playground.ipynb delete mode 100644 leetcode_old/binary_tree_level_order_traversal/solution.py delete mode 100644 leetcode_old/binary_tree_level_order_traversal/tests.py delete mode 100644 leetcode_old/binary_tree_right_side_view/README.md delete mode 100644 leetcode_old/binary_tree_right_side_view/__init__.py delete mode 100644 leetcode_old/binary_tree_right_side_view/playground.ipynb delete mode 100644 leetcode_old/binary_tree_right_side_view/solution.py delete mode 100644 leetcode_old/binary_tree_right_side_view/tests.py delete mode 100644 leetcode_old/climbing_stairs/README.md delete mode 100644 leetcode_old/climbing_stairs/__init__.py delete mode 100644 leetcode_old/climbing_stairs/playground.ipynb delete mode 100644 leetcode_old/climbing_stairs/solution.py delete mode 100644 leetcode_old/climbing_stairs/tests.py delete mode 100644 leetcode_old/clone_graph/README.md delete mode 100644 leetcode_old/clone_graph/__init__.py delete mode 100644 leetcode_old/clone_graph/playground.ipynb delete mode 100644 leetcode_old/clone_graph/solution.py delete mode 100644 leetcode_old/clone_graph/tests.py delete mode 100644 leetcode_old/coin_change/README.md delete mode 100644 leetcode_old/coin_change/__init__.py delete mode 100644 leetcode_old/coin_change/playground.ipynb delete mode 100644 leetcode_old/coin_change/solution.py delete mode 100644 leetcode_old/coin_change/tests.py delete mode 100644 leetcode_old/combination_sum/README.md delete mode 100644 leetcode_old/combination_sum/__init__.py delete mode 100644 leetcode_old/combination_sum/playground.ipynb delete mode 100644 leetcode_old/combination_sum/solution.py delete mode 100644 leetcode_old/combination_sum/tests.py delete mode 100644 leetcode_old/container_with_most_water/README.md delete mode 100644 leetcode_old/container_with_most_water/__init__.py delete mode 100644 leetcode_old/container_with_most_water/playground.ipynb delete mode 100644 leetcode_old/container_with_most_water/solution.py delete mode 100644 leetcode_old/container_with_most_water/tests.py delete mode 100644 leetcode_old/contains_duplicate/README.md delete mode 100644 leetcode_old/contains_duplicate/__init__.py delete mode 100644 leetcode_old/contains_duplicate/playground.ipynb delete mode 100644 leetcode_old/contains_duplicate/solution.py delete mode 100644 leetcode_old/contains_duplicate/tests.py delete mode 100644 leetcode_old/course_schedule/README.md delete mode 100644 leetcode_old/course_schedule/__init__.py delete mode 100644 leetcode_old/course_schedule/playground.ipynb delete mode 100644 leetcode_old/course_schedule/solution.py delete mode 100644 leetcode_old/course_schedule/tests.py delete mode 100644 leetcode_old/diameter_of_binary_tree/README.md delete mode 100644 leetcode_old/diameter_of_binary_tree/__init__.py delete mode 100644 leetcode_old/diameter_of_binary_tree/playground.ipynb delete mode 100644 leetcode_old/diameter_of_binary_tree/solution.py delete mode 100644 leetcode_old/diameter_of_binary_tree/tests.py delete mode 100644 leetcode_old/evaluate_reverse_polish_notation/README.md delete mode 100644 leetcode_old/evaluate_reverse_polish_notation/__init__.py delete mode 100644 leetcode_old/evaluate_reverse_polish_notation/playground.ipynb delete mode 100644 leetcode_old/evaluate_reverse_polish_notation/solution.py delete mode 100644 leetcode_old/evaluate_reverse_polish_notation/tests.py delete mode 100644 leetcode_old/find_median_from_data_stream/README.md delete mode 100644 leetcode_old/find_median_from_data_stream/__init__.py delete mode 100644 leetcode_old/find_median_from_data_stream/playground.ipynb delete mode 100644 leetcode_old/find_median_from_data_stream/solution.py delete mode 100644 leetcode_old/find_median_from_data_stream/tests.py delete mode 100644 leetcode_old/first_bad_version/README.md delete mode 100644 leetcode_old/first_bad_version/__init__.py delete mode 100644 leetcode_old/first_bad_version/playground.ipynb delete mode 100644 leetcode_old/first_bad_version/solution.py delete mode 100644 leetcode_old/first_bad_version/tests.py delete mode 100644 leetcode_old/flood_fill/README.md delete mode 100644 leetcode_old/flood_fill/__init__.py delete mode 100644 leetcode_old/flood_fill/playground.ipynb delete mode 100644 leetcode_old/flood_fill/solution.py delete mode 100644 leetcode_old/flood_fill/tests.py delete mode 100644 leetcode_old/implement_queue_using_stacks/README.md delete mode 100644 leetcode_old/implement_queue_using_stacks/__init__.py delete mode 100644 leetcode_old/implement_queue_using_stacks/playground.ipynb delete mode 100644 leetcode_old/implement_queue_using_stacks/solution.py delete mode 100644 leetcode_old/implement_queue_using_stacks/tests.py delete mode 100644 leetcode_old/implement_trie_prefix_tree/README.md delete mode 100644 leetcode_old/implement_trie_prefix_tree/__init__.py delete mode 100644 leetcode_old/implement_trie_prefix_tree/playground.ipynb delete mode 100644 leetcode_old/implement_trie_prefix_tree/solution.py delete mode 100644 leetcode_old/implement_trie_prefix_tree/tests.py delete mode 100644 leetcode_old/insert_interval/README.md delete mode 100644 leetcode_old/insert_interval/__init__.py delete mode 100644 leetcode_old/insert_interval/playground.ipynb delete mode 100644 leetcode_old/insert_interval/solution.py delete mode 100644 leetcode_old/insert_interval/tests.py delete mode 100644 leetcode_old/invert_binary_tree/README.md delete mode 100644 leetcode_old/invert_binary_tree/__init__.py delete mode 100644 leetcode_old/invert_binary_tree/playground.ipynb delete mode 100644 leetcode_old/invert_binary_tree/solution.py delete mode 100644 leetcode_old/invert_binary_tree/tests.py delete mode 100644 leetcode_old/k_closest_points_to_origin/README.md delete mode 100644 leetcode_old/k_closest_points_to_origin/__init__.py delete mode 100644 leetcode_old/k_closest_points_to_origin/playground.ipynb delete mode 100644 leetcode_old/k_closest_points_to_origin/solution.py delete mode 100644 leetcode_old/k_closest_points_to_origin/tests.py delete mode 100644 leetcode_old/kth_smallest_element_in_a_bst/README.md delete mode 100644 leetcode_old/kth_smallest_element_in_a_bst/__init__.py delete mode 100644 leetcode_old/kth_smallest_element_in_a_bst/playground.ipynb delete mode 100644 leetcode_old/kth_smallest_element_in_a_bst/solution.py delete mode 100644 leetcode_old/kth_smallest_element_in_a_bst/tests.py delete mode 100644 leetcode_old/largest_rectangle_in_histogram/README.md delete mode 100644 leetcode_old/largest_rectangle_in_histogram/__init__.py delete mode 100644 leetcode_old/largest_rectangle_in_histogram/playground.ipynb delete mode 100644 leetcode_old/largest_rectangle_in_histogram/solution.py delete mode 100644 leetcode_old/largest_rectangle_in_histogram/tests.py delete mode 100644 leetcode_old/linked_list_cycle/README.md delete mode 100644 leetcode_old/linked_list_cycle/__init__.py delete mode 100644 leetcode_old/linked_list_cycle/playground.ipynb delete mode 100644 leetcode_old/linked_list_cycle/solution.py delete mode 100644 leetcode_old/linked_list_cycle/tests.py delete mode 100644 leetcode_old/longest_palindrome/README.md delete mode 100644 leetcode_old/longest_palindrome/__init__.py delete mode 100644 leetcode_old/longest_palindrome/playground.ipynb delete mode 100644 leetcode_old/longest_palindrome/solution.py delete mode 100644 leetcode_old/longest_palindrome/tests.py delete mode 100644 leetcode_old/longest_palindromic_substring/README.md delete mode 100644 leetcode_old/longest_palindromic_substring/__init__.py delete mode 100644 leetcode_old/longest_palindromic_substring/playground.ipynb delete mode 100644 leetcode_old/longest_palindromic_substring/solution.py delete mode 100644 leetcode_old/longest_palindromic_substring/tests.py delete mode 100644 leetcode_old/longest_substring_without_repeating_characters/README.md delete mode 100644 leetcode_old/longest_substring_without_repeating_characters/__init__.py delete mode 100644 leetcode_old/longest_substring_without_repeating_characters/playground.ipynb delete mode 100644 leetcode_old/longest_substring_without_repeating_characters/solution.py delete mode 100644 leetcode_old/longest_substring_without_repeating_characters/tests.py delete mode 100644 leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/README.md delete mode 100644 leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/__init__.py delete mode 100644 leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb delete mode 100644 leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/solution.py delete mode 100644 leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/tests.py delete mode 100644 leetcode_old/lowest_common_ancestor_of_a_binary_tree/README.md delete mode 100644 leetcode_old/lowest_common_ancestor_of_a_binary_tree/__init__.py delete mode 100644 leetcode_old/lowest_common_ancestor_of_a_binary_tree/playground.ipynb delete mode 100644 leetcode_old/lowest_common_ancestor_of_a_binary_tree/solution.py delete mode 100644 leetcode_old/lowest_common_ancestor_of_a_binary_tree/tests.py delete mode 100644 leetcode_old/lru_cache/README.md delete mode 100644 leetcode_old/lru_cache/__init__.py delete mode 100644 leetcode_old/lru_cache/playground.ipynb delete mode 100644 leetcode_old/lru_cache/solution.py delete mode 100644 leetcode_old/lru_cache/tests.py delete mode 100644 leetcode_old/majority_element/README.md delete mode 100644 leetcode_old/majority_element/__init__.py delete mode 100644 leetcode_old/majority_element/playground.ipynb delete mode 100644 leetcode_old/majority_element/solution.py delete mode 100644 leetcode_old/majority_element/tests.py delete mode 100644 leetcode_old/maximum_depth_of_binary_tree/README.md delete mode 100644 leetcode_old/maximum_depth_of_binary_tree/__init__.py delete mode 100644 leetcode_old/maximum_depth_of_binary_tree/playground.ipynb delete mode 100644 leetcode_old/maximum_depth_of_binary_tree/solution.py delete mode 100644 leetcode_old/maximum_depth_of_binary_tree/tests.py delete mode 100644 leetcode_old/maximum_profit_in_job_scheduling/README.md delete mode 100644 leetcode_old/maximum_profit_in_job_scheduling/__init__.py delete mode 100644 leetcode_old/maximum_profit_in_job_scheduling/playground.ipynb delete mode 100644 leetcode_old/maximum_profit_in_job_scheduling/solution.py delete mode 100644 leetcode_old/maximum_profit_in_job_scheduling/tests.py delete mode 100644 leetcode_old/maximum_subarray/README.md delete mode 100644 leetcode_old/maximum_subarray/__init__.py delete mode 100644 leetcode_old/maximum_subarray/playground.ipynb delete mode 100644 leetcode_old/maximum_subarray/solution.py delete mode 100644 leetcode_old/maximum_subarray/tests.py delete mode 100644 leetcode_old/merge_intervals/README.md delete mode 100644 leetcode_old/merge_intervals/__init__.py delete mode 100644 leetcode_old/merge_intervals/playground.ipynb delete mode 100644 leetcode_old/merge_intervals/solution.py delete mode 100644 leetcode_old/merge_intervals/tests.py delete mode 100644 leetcode_old/merge_k_sorted_lists/README.md delete mode 100644 leetcode_old/merge_k_sorted_lists/__init__.py delete mode 100644 leetcode_old/merge_k_sorted_lists/playground.ipynb delete mode 100644 leetcode_old/merge_k_sorted_lists/solution.py delete mode 100644 leetcode_old/merge_k_sorted_lists/tests.py delete mode 100644 leetcode_old/merge_two_sorted_lists/README.md delete mode 100644 leetcode_old/merge_two_sorted_lists/__init__.py delete mode 100644 leetcode_old/merge_two_sorted_lists/playground.ipynb delete mode 100644 leetcode_old/merge_two_sorted_lists/solution.py delete mode 100644 leetcode_old/merge_two_sorted_lists/tests.py delete mode 100644 leetcode_old/middle_of_the_linked_list/README.md delete mode 100644 leetcode_old/middle_of_the_linked_list/__init__.py delete mode 100644 leetcode_old/middle_of_the_linked_list/playground.ipynb delete mode 100644 leetcode_old/middle_of_the_linked_list/solution.py delete mode 100644 leetcode_old/middle_of_the_linked_list/tests.py delete mode 100644 leetcode_old/min_stack/README.md delete mode 100644 leetcode_old/min_stack/__init__.py delete mode 100644 leetcode_old/min_stack/playground.ipynb delete mode 100644 leetcode_old/min_stack/solution.py delete mode 100644 leetcode_old/min_stack/tests.py delete mode 100644 leetcode_old/minimum_height_trees/README.md delete mode 100644 leetcode_old/minimum_height_trees/__init__.py delete mode 100644 leetcode_old/minimum_height_trees/playground.ipynb delete mode 100644 leetcode_old/minimum_height_trees/solution.py delete mode 100644 leetcode_old/minimum_height_trees/tests.py delete mode 100644 leetcode_old/minimum_window_substring/README.md delete mode 100644 leetcode_old/minimum_window_substring/__init__.py delete mode 100644 leetcode_old/minimum_window_substring/playground.ipynb delete mode 100644 leetcode_old/minimum_window_substring/solution.py delete mode 100644 leetcode_old/minimum_window_substring/tests.py delete mode 100644 leetcode_old/number_of_islands/README.md delete mode 100644 leetcode_old/number_of_islands/__init__.py delete mode 100644 leetcode_old/number_of_islands/playground.ipynb delete mode 100644 leetcode_old/number_of_islands/solution.py delete mode 100644 leetcode_old/number_of_islands/tests.py delete mode 100644 leetcode_old/partition_equal_subset_sum/README.md delete mode 100644 leetcode_old/partition_equal_subset_sum/__init__.py delete mode 100644 leetcode_old/partition_equal_subset_sum/playground.ipynb delete mode 100644 leetcode_old/partition_equal_subset_sum/solution.py delete mode 100644 leetcode_old/partition_equal_subset_sum/tests.py delete mode 100644 leetcode_old/permutations/README.md delete mode 100644 leetcode_old/permutations/__init__.py delete mode 100644 leetcode_old/permutations/playground.ipynb delete mode 100644 leetcode_old/permutations/solution.py delete mode 100644 leetcode_old/permutations/tests.py delete mode 100644 leetcode_old/product_of_array_except_self/README.md delete mode 100644 leetcode_old/product_of_array_except_self/__init__.py delete mode 100644 leetcode_old/product_of_array_except_self/playground.ipynb delete mode 100644 leetcode_old/product_of_array_except_self/solution.py delete mode 100644 leetcode_old/product_of_array_except_self/tests.py delete mode 100644 leetcode_old/ransom_note/README.md delete mode 100644 leetcode_old/ransom_note/__init__.py delete mode 100644 leetcode_old/ransom_note/playground.ipynb delete mode 100644 leetcode_old/ransom_note/solution.py delete mode 100644 leetcode_old/ransom_note/tests.py delete mode 100644 leetcode_old/reverse_linked_list/README.md delete mode 100644 leetcode_old/reverse_linked_list/__init__.py delete mode 100644 leetcode_old/reverse_linked_list/playground.ipynb delete mode 100644 leetcode_old/reverse_linked_list/solution.py delete mode 100644 leetcode_old/reverse_linked_list/tests.py delete mode 100644 leetcode_old/reverse_linked_list_ii/README.md delete mode 100644 leetcode_old/reverse_linked_list_ii/__init__.py delete mode 100644 leetcode_old/reverse_linked_list_ii/playground.ipynb delete mode 100644 leetcode_old/reverse_linked_list_ii/solution.py delete mode 100644 leetcode_old/reverse_linked_list_ii/tests.py delete mode 100644 leetcode_old/rotting_oranges/README.md delete mode 100644 leetcode_old/rotting_oranges/__init__.py delete mode 100644 leetcode_old/rotting_oranges/playground.ipynb delete mode 100644 leetcode_old/rotting_oranges/solution.py delete mode 100644 leetcode_old/rotting_oranges/tests.py delete mode 100644 leetcode_old/search_in_rotated_sorted_array/README.md delete mode 100644 leetcode_old/search_in_rotated_sorted_array/__init__.py delete mode 100644 leetcode_old/search_in_rotated_sorted_array/playground.ipynb delete mode 100644 leetcode_old/search_in_rotated_sorted_array/solution.py delete mode 100644 leetcode_old/search_in_rotated_sorted_array/tests.py delete mode 100644 leetcode_old/serialize_and_deserialize_binary_tree/README.md delete mode 100644 leetcode_old/serialize_and_deserialize_binary_tree/__init__.py delete mode 100644 leetcode_old/serialize_and_deserialize_binary_tree/playground.ipynb delete mode 100644 leetcode_old/serialize_and_deserialize_binary_tree/solution.py delete mode 100644 leetcode_old/serialize_and_deserialize_binary_tree/tests.py delete mode 100644 leetcode_old/sort_colors/README.md delete mode 100644 leetcode_old/sort_colors/__init__.py delete mode 100644 leetcode_old/sort_colors/playground.ipynb delete mode 100644 leetcode_old/sort_colors/solution.py delete mode 100644 leetcode_old/sort_colors/tests.py delete mode 100644 leetcode_old/spiral_matrix/README.md delete mode 100644 leetcode_old/spiral_matrix/__init__.py delete mode 100644 leetcode_old/spiral_matrix/playground.ipynb delete mode 100644 leetcode_old/spiral_matrix/solution.py delete mode 100644 leetcode_old/spiral_matrix/tests.py delete mode 100644 leetcode_old/string_to_integer_atoi/README.md delete mode 100644 leetcode_old/string_to_integer_atoi/__init__.py delete mode 100644 leetcode_old/string_to_integer_atoi/playground.ipynb delete mode 100644 leetcode_old/string_to_integer_atoi/solution.py delete mode 100644 leetcode_old/string_to_integer_atoi/tests.py delete mode 100644 leetcode_old/task_scheduler/README.md delete mode 100644 leetcode_old/task_scheduler/__init__.py delete mode 100644 leetcode_old/task_scheduler/playground.ipynb delete mode 100644 leetcode_old/task_scheduler/solution.py delete mode 100644 leetcode_old/task_scheduler/tests.py delete mode 100644 leetcode_old/three_sum/README.md delete mode 100644 leetcode_old/three_sum/__init__.py delete mode 100644 leetcode_old/three_sum/playground.ipynb delete mode 100644 leetcode_old/three_sum/solution.py delete mode 100644 leetcode_old/three_sum/tests.py delete mode 100644 leetcode_old/time_based_key_value_store/README.md delete mode 100644 leetcode_old/time_based_key_value_store/__init__.py delete mode 100644 leetcode_old/time_based_key_value_store/playground.ipynb delete mode 100644 leetcode_old/time_based_key_value_store/solution.py delete mode 100644 leetcode_old/time_based_key_value_store/tests.py delete mode 100644 leetcode_old/trapping_rain_water/README.md delete mode 100644 leetcode_old/trapping_rain_water/__init__.py delete mode 100644 leetcode_old/trapping_rain_water/playground.ipynb delete mode 100644 leetcode_old/trapping_rain_water/solution.py delete mode 100644 leetcode_old/trapping_rain_water/tests.py delete mode 100644 leetcode_old/valid_anagram/README.md delete mode 100644 leetcode_old/valid_anagram/__init__.py delete mode 100644 leetcode_old/valid_anagram/playground.ipynb delete mode 100644 leetcode_old/valid_anagram/solution.py delete mode 100644 leetcode_old/valid_anagram/tests.py delete mode 100644 leetcode_old/valid_palindrome/README.md delete mode 100644 leetcode_old/valid_palindrome/__init__.py delete mode 100644 leetcode_old/valid_palindrome/playground.ipynb delete mode 100644 leetcode_old/valid_palindrome/solution.py delete mode 100644 leetcode_old/valid_palindrome/tests.py delete mode 100644 leetcode_old/valid_parentheses/README.md delete mode 100644 leetcode_old/valid_parentheses/__init__.py delete mode 100644 leetcode_old/valid_parentheses/playground.ipynb delete mode 100644 leetcode_old/valid_parentheses/solution.py delete mode 100644 leetcode_old/valid_parentheses/tests.py delete mode 100644 leetcode_old/validate_binary_search_tree/README.md delete mode 100644 leetcode_old/validate_binary_search_tree/__init__.py delete mode 100644 leetcode_old/validate_binary_search_tree/playground.ipynb delete mode 100644 leetcode_old/validate_binary_search_tree/solution.py delete mode 100644 leetcode_old/validate_binary_search_tree/tests.py delete mode 100644 leetcode_old/word_break/README.md delete mode 100644 leetcode_old/word_break/__init__.py delete mode 100644 leetcode_old/word_break/playground.ipynb delete mode 100644 leetcode_old/word_break/solution.py delete mode 100644 leetcode_old/word_break/tests.py delete mode 100644 leetcode_old/word_ladder/README.md delete mode 100644 leetcode_old/word_ladder/__init__.py delete mode 100644 leetcode_old/word_ladder/playground.ipynb delete mode 100644 leetcode_old/word_ladder/solution.py delete mode 100644 leetcode_old/word_ladder/tests.py delete mode 100644 leetcode_old/zero_one_matrix/README.md delete mode 100644 leetcode_old/zero_one_matrix/__init__.py delete mode 100644 leetcode_old/zero_one_matrix/playground.ipynb delete mode 100644 leetcode_old/zero_one_matrix/solution.py delete mode 100644 leetcode_old/zero_one_matrix/tests.py diff --git a/.templates/leetcode/examples_old/README.md b/.templates/leetcode/examples_old/README.md deleted file mode 100644 index df7d99b..0000000 --- a/.templates/leetcode/examples_old/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# JSON Template Examples - -This directory contains comprehensive examples for creating LeetCode problem templates. - -## Files - -- **`basic.json5`** - Covers all standard problem types: - - Array problems (Container With Most Water) - - String problems (with JSON escaping notes) - - Tree problems (import and parameter examples) - - Linked list problems (import and parameter examples) - - Matrix problems - - Number problems - -- **`design.json5`** - Data structure design problems: - - Custom class names (LRUCache, not Solution) - - Multiple methods including `__init__` - - Complex test setup with operation sequences - - Custom imports - -## Key Differences - -### Standard Problems (basic.json5) - -- `solution_class_name`: Always "Solution" -- Single method (usually) -- Simple test cases with direct assertions -- Standard imports - -### Design Problems (design.json5) - -- `solution_class_name`: Custom class name (e.g., "LRUCache") -- Multiple methods including constructor -- Operation sequence testing -- Import custom class in tests - -## Critical Notes - -- **JSON Escaping**: Use single quotes for Python strings in playground fields -- **Type Hints**: Use modern syntax (`list[int]`, `TreeNode | None`) -- **PascalCase**: Keep acronyms ALL CAPS (LRUCache, ReverseLinkedListII) diff --git a/.templates/leetcode/examples_old/basic.json5 b/.templates/leetcode/examples_old/basic.json5 deleted file mode 100644 index 391c7c6..0000000 --- a/.templates/leetcode/examples_old/basic.json5 +++ /dev/null @@ -1,88 +0,0 @@ -{ - // Basic problem template - for array, string, number problems - // Example: Container With Most Water, Spiral Matrix - // NOTE: PascalCase naming - keep acronyms/Roman numerals ALL CAPS (LRUCache, ReverseLinkedListII) - - // === PROBLEM IDENTIFICATION === - "problem_name": "container_with_most_water", // snake_case: used for directory/file names - "solution_class_name": "Solution", // Always "Solution" for basic problems - "problem_number": "11", // LeetCode problem number as string - "problem_title": "Container With Most Water", // Exact title from LeetCode - "difficulty": "Medium", // Easy, Medium, Hard - "topics": "Array, Two Pointers, Greedy", // Comma-separated topics from LeetCode - "tags": ["grind-75"], // Optional: common problem set tags - - // === README CONTENT === - // IMPORTANT: Preserve rich HTML content from LeetCode including: - // - Code snippets with backticks: `code` - // - Bold text: **bold** or bold - // - Italic text: *italic* or italic - // - Images: tags with proper src and styling - // - HTML formatting:

    ,
    ,

      ,
    • , etc. - // - Mathematical expressions and special characters - "readme_description": "You are given an integer array `height` of length `n`. There are `n` vertical lines drawn such that the two endpoints of the `i`th line are `(i, 0)` and `(i, height[i])`.\n\nFind two lines that together with the x-axis form a container, such that the container contains the most water.\n\nReturn the maximum amount of water a container can store.\n\nNotice that you may not slant the container.", - - "readme_examples": [ - { - // Include images for visual problems - use HTML img tags - "content": "\"\"\n\n```\nInput: height = [1,8,6,2,5,4,8,3,7]\nOutput: 49\nExplanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.\n```" - }, - { - // Additional examples without images - "content": "```\nInput: height = [1,1]\nOutput: 1\n```" - } - ], - - "readme_constraints": "- n == height.length\n- 2 <= n <= 10^5\n- 0 <= height[i] <= 10^4", - "readme_additional": "", // Optional: additional notes, follow-up questions - - // === SOLUTION TEMPLATE === - "solution_imports": "", // Empty for basic problems - // For tree: "from leetcode_py import TreeNode" - // For linked list: "from leetcode_py import ListNode" - "solution_methods": [ - { - "name": "max_area", // snake_case method name - "parameters": "height: list[int]", // Modern Python type hints (list[int], not List[int]) - // For tree: "root: TreeNode | None" - // For linked list: "head: ListNode | None" - // For string: "s: str" - "return_type": "int", // Return type annotation - "dummy_return": "0" // Default return value - // For string: "\"\"" - // For bool: "False" - // For list: "[]" - // For tree/linked list: "None" - } - ], - - // === TEST CONFIGURATION === - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "ContainerWithMostWater", // PascalCase: TestClassName for pytest class - "test_helper_methods": [ - { - "name": "setup_method", - "parameters": "", - "body": "self.solution = Solution()" - } - ], - "test_methods": [ - { - "name": "test_max_area", // test_{method_name} - "parametrize": "height, expected", // pytest parametrize parameters - "parametrize_typed": "height: list[int], expected: int", // Method signature with type hints - "test_cases": "[([1,8,6,2,5,4,8,3,7], 49), ([1,1], 1), ([1,2,1], 2)]", // Test data as string - "body": "result = self.solution.max_area(height)\nassert result == expected" - } - ], - - // === PLAYGROUND NOTEBOOK === - // CRITICAL: Use single quotes for Python strings to avoid JSON escaping issues with Jupyter notebooks - // Double quotes in JSON + cookiecutter + Jupyter notebook = triple escaping issues - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nheight = [1,8,6,2,5,4,8,3,7]\nexpected = 49", - // For string problems: "s = 'hello'\nexpected = 'olleh'" - // For tree: "root_list = [3,9,20,None,None,15,7]\nroot = TreeNode.from_list(root_list)" - "playground_execution": "result = Solution().max_area(height)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/examples_old/design.json5 b/.templates/leetcode/examples_old/design.json5 deleted file mode 100644 index dbe2096..0000000 --- a/.templates/leetcode/examples_old/design.json5 +++ /dev/null @@ -1,84 +0,0 @@ -{ - // Design problem template - for data structure design problems - // Example: LRU Cache - // Key differences: Custom class name, multiple methods, complex test setup - // NOTE: PascalCase naming - keep acronyms/Roman numerals ALL CAPS (LRUCache, ReverseLinkedListII) - - // === PROBLEM IDENTIFICATION === - problem_name: "lru_cache", // snake_case: used for directory/file names - solution_class_name: "LRUCache", // IMPORTANT: Custom class name for design problems - problem_number: "146", // LeetCode problem number as string - problem_title: "LRU Cache", // Exact title from LeetCode - difficulty: "Medium", // Easy, Medium, Hard - topics: "Hash Table, Linked List, Design, Doubly-Linked List", // Design-related topics - tags: ["grind-75"], // Optional: common problem set tags - - // === README CONTENT === - // IMPORTANT: Preserve rich HTML content from LeetCode including: - // - Code snippets with backticks: `code` - // - Bold text: **bold** or bold - // - Italic text: *italic* or italic - // - Images: tags with proper src and styling - // - HTML formatting:

      ,
      ,

        ,
      • , etc. - // - Mathematical expressions and special characters - readme_description: "Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.\\n\\nImplement the `LRUCache` class:\\n\\n- `LRUCache(int capacity)` Initialize the LRU cache with positive size capacity\\n- `int get(int key)` Return the value of the key if the key exists, otherwise return -1\\n- `void put(int key, int value)` Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key\\n\\nThe functions `get` and `put` must each run in `O(1)` average time complexity.", - - readme_examples: [ - { - // Design problems often have complex operation sequences - content: '```\\nInput\\n[\\"LRUCache\\", \\"put\\", \\"put\\", \\"get\\", \\"put\\", \\"get\\", \\"put\\", \\"get\\", \\"get\\", \\"get\\"]\\n[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\\nOutput\\n[null, null, null, 1, null, -1, null, -1, 3, 4]\\n\\nExplanation\\nLRUCache lRUCache = new LRUCache(2);\\nlRUCache.put(1, 1); // cache is {1=1}\\nlRUCache.put(2, 2); // cache is {1=1, 2=2}\\nlRUCache.get(1); // return 1\\nlRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}\\nlRUCache.get(2); // returns -1 (not found)\\nlRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}\\nlRUCache.get(1); // return -1 (not found)\\nlRUCache.get(3); // return 3\\nlRUCache.get(4); // return 4\\n```', - }, - ], - - readme_constraints: "- 1 <= capacity <= 3000\\n- 0 <= key <= 10^4\\n- 0 <= value <= 10^5\\n- At most 2 * 10^5 calls will be made to get and put", - readme_additional: "", - - // === SOLUTION TEMPLATE === - solution_imports: "", // Usually empty for design problems - // IMPORTANT: Design problems have multiple methods including __init__ - solution_methods: [ - { - name: "__init__", // Constructor method - parameters: "capacity: int", // Constructor parameters - return_type: "None", // Constructor returns None - dummy_return: "", // Empty for None return - }, - { - name: "get", // Instance method - parameters: "key: int", - return_type: "int", - dummy_return: "-1", // Specific default for this method - }, - { - name: "put", // Instance method - parameters: "key: int, value: int", - return_type: "None", - dummy_return: "", // Empty for None return - }, - ], - - // === TEST CONFIGURATION === - // IMPORTANT: Design problems import the custom class, not Solution - test_imports: "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import LRUCache", - test_class_name: "LRUCache", // PascalCase: TestClassName for pytest class - test_helper_methods: [], // Often empty for design problems - test_methods: [ - { - name: "test_lru_cache", - // IMPORTANT: Design tests use operations, inputs, expected pattern - parametrize: "operations, inputs, expected", - parametrize_typed: "operations: list[str], inputs: list[list[int]], expected: list[int | None]", - test_cases: '[(["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"], [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]], [None, None, None, 1, None, -1, None, -1, 3, 4])]', - // IMPORTANT: Complex test body that executes operation sequences - body: 'cache: LRUCache | None = None\nresults: list[int | None] = []\nfor i, op in enumerate(operations):\n if op == "LRUCache":\n cache = LRUCache(inputs[i][0])\n results.append(None)\n elif op == "get" and cache is not None:\n results.append(cache.get(inputs[i][0]))\n elif op == "put" and cache is not None:\n cache.put(inputs[i][0], inputs[i][1])\n results.append(None)\nassert results == expected', - }, - ], - - // === PLAYGROUND NOTEBOOK === - // CRITICAL: Use single quotes for Python strings to avoid JSON escaping issues with Jupyter notebooks - // Double quotes in JSON + cookiecutter + Jupyter notebook = triple escaping issues - playground_imports: "from solution import LRUCache", - playground_test_case: "# Example test case\noperations = ['LRUCache', 'put', 'put', 'get', 'put', 'get', 'put', 'get', 'get', 'get']\ninputs = [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\nexpected = [None, None, None, 1, None, -1, None, -1, 3, 4]", - playground_execution: "cache = None\nresults: list[int | None] = []\nfor i, op in enumerate(operations):\n if op == 'LRUCache':\n cache = LRUCache(inputs[i][0])\n results.append(None)\n elif op == 'get' and cache is not None:\n results.append(cache.get(inputs[i][0]))\n elif op == 'put' and cache is not None:\n cache.put(inputs[i][0], inputs[i][1])\n results.append(None)\nresults", - playground_assertion: "assert results == expected", -} diff --git a/.templates/leetcode/json_old/accounts_merge.json b/.templates/leetcode/json_old/accounts_merge.json deleted file mode 100644 index 9a601d1..0000000 --- a/.templates/leetcode/json_old/accounts_merge.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "accounts_merge", - "solution_class_name": "Solution", - "problem_number": "721", - "problem_title": "Accounts Merge", - "difficulty": "Medium", - "topics": "Array, Hash Table, String, Depth-First Search, Breadth-First Search, Union Find, Sorting", - "tags": ["grind-75"], - "readme_description": "Given a list of `accounts` where each element `accounts[i]` is a list of strings, where the first element `accounts[i][0]` is a name, and the rest of the elements are **emails** representing emails of the account.\n\nNow, we would like to merge these accounts. Two accounts definitely belong to the same person if there is some common email to both accounts. Note that even if two accounts have the same name, they may belong to different people as people could have the same name. A person can have any number of accounts initially, but all of their accounts definitely have the same name.\n\nAfter merging the accounts, return the accounts in the following format: the first element of each account is the name, and the rest of the elements are emails **in sorted order**. The accounts themselves can be returned in **any order**.", - "readme_examples": [ - { - "content": "```\nInput: accounts = [[\"John\",\"johnsmith@mail.com\",\"john_newyork@mail.com\"],[\"John\",\"johnsmith@mail.com\",\"john00@mail.com\"],[\"Mary\",\"mary@mail.com\"],[\"John\",\"johnnybravo@mail.com\"]]\nOutput: [[\"John\",\"john00@mail.com\",\"john_newyork@mail.com\",\"johnsmith@mail.com\"],[\"Mary\",\"mary@mail.com\"],[\"John\",\"johnnybravo@mail.com\"]]\n```\n**Explanation:** The first and second John's are the same person as they have the common email \"johnsmith@mail.com\". The third John and Mary are different people as none of their email addresses are used by other accounts." - }, - { - "content": "```\nInput: accounts = [[\"Gabe\",\"Gabe0@m.co\",\"Gabe3@m.co\",\"Gabe1@m.co\"],[\"Kevin\",\"Kevin3@m.co\",\"Kevin5@m.co\",\"Kevin0@m.co\"],[\"Ethan\",\"Ethan5@m.co\",\"Ethan4@m.co\",\"Ethan0@m.co\"],[\"Hanzo\",\"Hanzo3@m.co\",\"Hanzo1@m.co\",\"Hanzo0@m.co\"],[\"Fern\",\"Fern5@m.co\",\"Fern1@m.co\",\"Fern0@m.co\"]]\nOutput: [[\"Ethan\",\"Ethan0@m.co\",\"Ethan4@m.co\",\"Ethan5@m.co\"],[\"Gabe\",\"Gabe0@m.co\",\"Gabe1@m.co\",\"Gabe3@m.co\"],[\"Hanzo\",\"Hanzo0@m.co\",\"Hanzo1@m.co\",\"Hanzo3@m.co\"],[\"Kevin\",\"Kevin0@m.co\",\"Kevin3@m.co\",\"Kevin5@m.co\"],[\"Fern\",\"Fern0@m.co\",\"Fern1@m.co\",\"Fern5@m.co\"]]\n```" - } - ], - "readme_constraints": "- `1 <= accounts.length <= 1000`\n- `2 <= accounts[i].length <= 10`\n- `1 <= accounts[i][j].length <= 30`\n- `accounts[i][0]` consists of English letters.\n- `accounts[i][j] (for j > 0)` is a valid email.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "accounts_merge", - "parameters": "accounts: list[list[str]]", - "return_type": "list[list[str]]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "AccountsMerge", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_accounts_merge", - "parametrize": "accounts, expected", - "parametrize_typed": "accounts: list[list[str]], expected: list[list[str]]", - "test_cases": "[([[\"John\", \"johnsmith@mail.com\", \"john_newyork@mail.com\"], [\"John\", \"johnsmith@mail.com\", \"john00@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]], [[\"John\", \"john00@mail.com\", \"john_newyork@mail.com\", \"johnsmith@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]), ([[\"Gabe\", \"Gabe0@m.co\", \"Gabe3@m.co\", \"Gabe1@m.co\"], [\"Kevin\", \"Kevin3@m.co\", \"Kevin5@m.co\", \"Kevin0@m.co\"], [\"Ethan\", \"Ethan5@m.co\", \"Ethan4@m.co\", \"Ethan0@m.co\"], [\"Hanzo\", \"Hanzo3@m.co\", \"Hanzo1@m.co\", \"Hanzo0@m.co\"], [\"Fern\", \"Fern5@m.co\", \"Fern1@m.co\", \"Fern0@m.co\"]], [[\"Ethan\", \"Ethan0@m.co\", \"Ethan4@m.co\", \"Ethan5@m.co\"], [\"Gabe\", \"Gabe0@m.co\", \"Gabe1@m.co\", \"Gabe3@m.co\"], [\"Hanzo\", \"Hanzo0@m.co\", \"Hanzo1@m.co\", \"Hanzo3@m.co\"], [\"Kevin\", \"Kevin0@m.co\", \"Kevin3@m.co\", \"Kevin5@m.co\"], [\"Fern\", \"Fern0@m.co\", \"Fern1@m.co\", \"Fern5@m.co\"]])]", - "body": "result = self.solution.accounts_merge(accounts)\n# Sort both result and expected for comparison since order doesn't matter\nresult_sorted = [sorted(account) for account in sorted(result)]\nexpected_sorted = [sorted(account) for account in sorted(expected)]\nassert result_sorted == expected_sorted" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\naccounts = [[\"John\", \"johnsmith@mail.com\", \"john_newyork@mail.com\"], [\"John\", \"johnsmith@mail.com\", \"john00@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]\nexpected = [[\"John\", \"john00@mail.com\", \"john_newyork@mail.com\", \"johnsmith@mail.com\"], [\"Mary\", \"mary@mail.com\"], [\"John\", \"johnnybravo@mail.com\"]]", - "playground_execution": "result = Solution().accounts_merge(accounts)\nresult", - "playground_assertion": "# Sort for comparison since order doesn't matter\nresult_sorted = [sorted(account) for account in sorted(result)]\nexpected_sorted = [sorted(account) for account in sorted(expected)]\nassert result_sorted == expected_sorted" -} diff --git a/.templates/leetcode/json_old/add_binary.json b/.templates/leetcode/json_old/add_binary.json deleted file mode 100644 index e89d7f9..0000000 --- a/.templates/leetcode/json_old/add_binary.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "problem_name": "add_binary", - "solution_class_name": "Solution", - "problem_number": "67", - "problem_title": "Add Binary", - "difficulty": "Easy", - "topics": "Math, String, Bit Manipulation, Simulation", - "tags": ["grind-75"], - "readme_description": "Given two binary strings `a` and `b`, return *their sum as a binary string*.", - "readme_examples": [ - { "content": "```\nInput: a = \"11\", b = \"1\"\nOutput: \"100\"\n```" }, - { "content": "```\nInput: a = \"1010\", b = \"1011\"\nOutput: \"10101\"\n```" } - ], - "readme_constraints": "- `1 <= a.length, b.length <= 10^4`\n- `a` and `b` consist only of `'0'` or `'1'` characters.\n- Each string does not contain leading zeros except for the zero itself.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "add_binary", - "parameters": "a: str, b: str", - "return_type": "str", - "dummy_return": "\"\"" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "AddBinary", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_add_binary", - "parametrize": "a, b, expected", - "parametrize_typed": "a: str, b: str, expected: str", - "test_cases": "[('11', '1', '100'), ('1010', '1011', '10101'), ('0', '0', '0'), ('1', '1', '10'), ('1111', '1111', '11110')]", - "body": "result = self.solution.add_binary(a, b)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\na = '11'\nb = '1'\nexpected = '100'", - "playground_execution": "result = Solution().add_binary(a, b)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/balanced_binary_tree.json b/.templates/leetcode/json_old/balanced_binary_tree.json deleted file mode 100644 index b9f3446..0000000 --- a/.templates/leetcode/json_old/balanced_binary_tree.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "problem_name": "balanced_binary_tree", - "solution_class_name": "Solution", - "problem_number": "110", - "problem_title": "Balanced Binary Tree", - "difficulty": "Easy", - "topics": "Tree, Depth-First Search, Binary Tree", - "tags": ["grind-75"], - "readme_description": "Given a binary tree, determine if it is **height-balanced**.\n\nA height-balanced binary tree is a binary tree in which the depth of the two subtrees of every node never differs by more than one.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2020/10/06/balance_1.jpg)\n\n```\nInput: root = [3,9,20,null,null,15,7]\nOutput: true\n```" - }, - { - "content": "![Example 2](https://assets.leetcode.com/uploads/2020/10/06/balance_2.jpg)\n\n```\nInput: root = [1,2,2,3,3,null,null,4,4]\nOutput: false\n```" - }, - { "content": "```\nInput: root = []\nOutput: true\n```" } - ], - "readme_constraints": "- The number of nodes in the tree is in the range `[0, 5000]`.\n- `-10^4 <= Node.val <= 10^4`", - "readme_additional": "", - "solution_imports": "from leetcode_py import TreeNode", - "solution_methods": [ - { - "name": "is_balanced", - "parameters": "root: TreeNode | None", - "return_type": "bool", - "dummy_return": "False" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution", - "test_class_name": "BalancedBinaryTree", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_is_balanced", - "parametrize": "root_list, expected", - "parametrize_typed": "root_list: list[int | None], expected: bool", - "test_cases": "[([3, 9, 20, None, None, 15, 7], True), ([1, 2, 2, 3, 3, None, None, 4, 4], False), ([], True)]", - "body": "root = TreeNode.from_list(root_list)\nresult = self.solution.is_balanced(root)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode", - "playground_test_case": "# Example test case\nroot_list = [3, 9, 20, None, None, 15, 7]\nroot = TreeNode.from_list(root_list)\nexpected = True", - "playground_execution": "result = Solution().is_balanced(root)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/basic_calculator.json b/.templates/leetcode/json_old/basic_calculator.json deleted file mode 100644 index d8c0cce..0000000 --- a/.templates/leetcode/json_old/basic_calculator.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "problem_name": "basic_calculator", - "solution_class_name": "Solution", - "problem_number": "224", - "problem_title": "Basic Calculator", - "difficulty": "Hard", - "topics": "Math, String, Stack, Recursion", - "tags": ["grind-75"], - "readme_description": "Given a string `s` representing a valid expression, implement a basic calculator to evaluate it, and return the result of the evaluation.\n\n**Note:** You are **not** allowed to use any built-in function which evaluates strings as mathematical expressions, such as `eval()`.", - "readme_examples": [ - { "content": "```\nInput: s = \"1 + 1\"\nOutput: 2\n```" }, - { "content": "```\nInput: s = \" 2-1 + 2 \"\nOutput: 3\n```" }, - { "content": "```\nInput: s = \"(1+(4+5+2)-3)+(6+8)\"\nOutput: 23\n```" } - ], - "readme_constraints": "- `1 <= s.length <= 3 * 10^5`\n- `s` consists of digits, `'+'`, `'-'`, `'('`, `')'`, and `' '`.\n- `s` represents a valid expression.\n- `'+'` is **not** used as a unary operation (i.e., `\"+1\"` and `\"+(2 + 3)\"` is invalid).\n- `'-'` could be used as a unary operation (i.e., `\"-1\"` and `\"-(2 + 3)\"` is valid).\n- There will be no two consecutive operators in the input.\n- Every number and running calculation will fit in a signed 32-bit integer.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { "name": "calculate", "parameters": "s: str", "return_type": "int", "dummy_return": "0" } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "BasicCalculator", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_calculate", - "parametrize": "s, expected", - "parametrize_typed": "s: str, expected: int", - "test_cases": "[(\"1 + 1\", 2), (\" 2-1 + 2 \", 3), (\"(1+(4+5+2)-3)+(6+8)\", 23), (\"1\", 1), (\"-1\", -1), (\"-(1+2)\", -3), (\"2147483647\", 2147483647), (\"1-1+1\", 1)]", - "body": "result = self.solution.calculate(s)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ns = '(1+(4+5+2)-3)+(6+8)'\nexpected = 23", - "playground_execution": "result = Solution().calculate(s)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/best_time_to_buy_and_sell_stock.json b/.templates/leetcode/json_old/best_time_to_buy_and_sell_stock.json deleted file mode 100644 index 9822f0a..0000000 --- a/.templates/leetcode/json_old/best_time_to_buy_and_sell_stock.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "best_time_to_buy_and_sell_stock", - "solution_class_name": "Solution", - "problem_number": "121", - "problem_title": "Best Time to Buy and Sell Stock", - "difficulty": "Easy", - "topics": "Array, Dynamic Programming", - "tags": ["grind-75"], - "readme_description": "You are given an array `prices` where `prices[i]` is the price of a given stock on the ith day.\n\nYou want to maximize your profit by choosing a **single day** to buy one stock and choosing a **different day in the future** to sell that stock.\n\nReturn *the maximum profit you can achieve from this transaction*. If you cannot achieve any profit, return `0`.", - "readme_examples": [ - { - "content": "```\nInput: prices = [7,1,5,3,6,4]\nOutput: 5\n```\n**Explanation:** Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.\nNote that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell." - }, - { - "content": "```\nInput: prices = [7,6,4,3,1]\nOutput: 0\n```\n**Explanation:** In this case, no transactions are done and the max profit = 0." - } - ], - "readme_constraints": "- 1 <= prices.length <= 10^5\n- 0 <= prices[i] <= 10^4", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "max_profit", - "parameters": "prices: list[int]", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "BestTimeToBuyAndSellStock", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_max_profit", - "parametrize": "prices, expected", - "parametrize_typed": "prices: list[int], expected: int", - "test_cases": "[([7, 1, 5, 3, 6, 4], 5), ([7, 6, 4, 3, 1], 0), ([1, 2, 3, 4, 5], 4), ([5, 4, 3, 2, 1], 0), ([1], 0), ([2, 1], 0), ([1, 2], 1), ([3, 2, 6, 5, 0, 3], 4)]", - "body": "result = self.solution.max_profit(prices)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nprices = [7, 1, 5, 3, 6, 4]\nexpected = 5", - "playground_execution": "result = Solution().max_profit(prices)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/binary_search.json b/.templates/leetcode/json_old/binary_search.json deleted file mode 100644 index ed3b7c4..0000000 --- a/.templates/leetcode/json_old/binary_search.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "binary_search", - "solution_class_name": "Solution", - "problem_number": "704", - "problem_title": "Binary Search", - "difficulty": "Easy", - "topics": "Array, Binary Search", - "tags": ["grind-75"], - "readme_description": "Given an array of integers `nums` which is sorted in ascending order, and an integer `target`, write a function to search `target` in `nums`. If `target` exists, then return its index. Otherwise, return `-1`.\n\nYou must write an algorithm with `O(log n)` runtime complexity.", - "readme_examples": [ - { - "content": "```\nInput: nums = [-1,0,3,5,9,12], target = 9\nOutput: 4\n```\n**Explanation:** 9 exists in nums and its index is 4" - }, - { - "content": "```\nInput: nums = [-1,0,3,5,9,12], target = 2\nOutput: -1\n```\n**Explanation:** 2 does not exist in nums so return -1" - } - ], - "readme_constraints": "- `1 <= nums.length <= 10^4`\n- `-10^4 < nums[i], target < 10^4`\n- All the integers in `nums` are **unique**.\n- `nums` is sorted in ascending order.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "search", - "parameters": "nums: list[int], target: int", - "return_type": "int", - "dummy_return": "-1" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "BinarySearch", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_search", - "parametrize": "nums, target, expected", - "parametrize_typed": "nums: list[int], target: int, expected: int", - "test_cases": "[([\u22121, 0, 3, 5, 9, 12], 9, 4), ([\u22121, 0, 3, 5, 9, 12], 2, \u22121), ([5], 5, 0), ([5], \u22125, \u22121), ([1, 3, 5, 7, 9], 1, 0), ([1, 3, 5, 7, 9], 9, 4), ([1, 3, 5, 7, 9], 4, \u22121)]", - "body": "result = self.solution.search(nums, target)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnums = [-1, 0, 3, 5, 9, 12]\ntarget = 9\nexpected = 4", - "playground_execution": "result = Solution().search(nums, target)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/binary_tree_level_order_traversal.json b/.templates/leetcode/json_old/binary_tree_level_order_traversal.json deleted file mode 100644 index b9f6380..0000000 --- a/.templates/leetcode/json_old/binary_tree_level_order_traversal.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "problem_name": "binary_tree_level_order_traversal", - "solution_class_name": "Solution", - "problem_number": "102", - "problem_title": "Binary Tree Level Order Traversal", - "difficulty": "Medium", - "topics": "Tree, Breadth-First Search, Binary Tree", - "tags": ["grind-75"], - "readme_description": "Given the `root` of a binary tree, return the level order traversal of its nodes' values. (i.e., from left to right, level by level).", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2021/02/19/tree1.jpg)\n\n```\nInput: root = [3,9,20,null,null,15,7]\nOutput: [[3],[9,20],[15,7]]\n```" - }, - { "content": "```\nInput: root = [1]\nOutput: [[1]]\n```" }, - { "content": "```\nInput: root = []\nOutput: []\n```" } - ], - "readme_constraints": "- The number of nodes in the tree is in the range [0, 2000]\n- -1000 <= Node.val <= 1000", - "readme_additional": "", - "solution_imports": "from leetcode_py import TreeNode", - "solution_methods": [ - { - "name": "level_order", - "parameters": "root: TreeNode | None", - "return_type": "list[list[int]]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution", - "test_class_name": "BinaryTreeLevelOrderTraversal", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_level_order", - "parametrize": "root_list, expected", - "parametrize_typed": "root_list: list[int | None], expected: list[list[int]]", - "test_cases": "[([3, 9, 20, None, None, 15, 7], [[3], [9, 20], [15, 7]]), ([1], [[1]]), ([], []), ([1, 2, 3, 4, 5, 6, 7], [[1], [2, 3], [4, 5, 6, 7]]), ([1, 2, None, 3, None, 4, None, 5], [[1], [2], [3], [4], [5]])]", - "body": "root = TreeNode.from_list(root_list) if root_list else None\nresult = self.solution.level_order(root)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode", - "playground_test_case": "# Example test case\nroot_list = [3, 9, 20, None, None, 15, 7]\nroot = TreeNode.from_list(root_list)\nexpected = [[3], [9, 20], [15, 7]]", - "playground_execution": "result = Solution().level_order(root)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/binary_tree_right_side_view.json b/.templates/leetcode/json_old/binary_tree_right_side_view.json deleted file mode 100644 index a5165db..0000000 --- a/.templates/leetcode/json_old/binary_tree_right_side_view.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "problem_name": "binary_tree_right_side_view", - "solution_class_name": "Solution", - "problem_number": "199", - "problem_title": "Binary Tree Right Side View", - "difficulty": "Medium", - "topics": "Tree, Depth-First Search, Breadth-First Search, Binary Tree", - "tags": ["grind-75"], - "readme_description": "Given the `root` of a binary tree, imagine yourself standing on the **right side** of it, return *the values of the nodes you can see ordered from top to bottom*.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2024/11/24/tmpd5jn43fs-1.png)\n\n```\nInput: root = [1,2,3,null,5,null,4]\nOutput: [1,3,4]\n```" - }, - { - "content": "![Example 2](https://assets.leetcode.com/uploads/2024/11/24/tmpkpe40xeh-1.png)\n\n```\nInput: root = [1,2,3,4,null,null,null,5]\nOutput: [1,3,4,5]\n```" - }, - { "content": "```\nInput: root = [1,null,3]\nOutput: [1,3]\n```" }, - { "content": "```\nInput: root = []\nOutput: []\n```" } - ], - "readme_constraints": "- The number of nodes in the tree is in the range `[0, 100]`.\n- `-100 <= Node.val <= 100`", - "readme_additional": "", - "solution_imports": "from leetcode_py import TreeNode", - "solution_methods": [ - { - "name": "right_side_view", - "parameters": "root: TreeNode[int] | None", - "return_type": "list[int]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution", - "test_class_name": "BinaryTreeRightSideView", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_right_side_view", - "parametrize": "root_list, expected", - "parametrize_typed": "root_list: list[int | None], expected: list[int]", - "test_cases": "[([1, 2, 3, None, 5, None, 4], [1, 3, 4]), ([1, 2, 3, 4, None, None, None, 5], [1, 3, 4, 5]), ([1, None, 3], [1, 3]), ([], [])]", - "body": "root = TreeNode.from_list(root_list)\nresult = self.solution.right_side_view(root)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode", - "playground_test_case": "# Example test case\nroot_list: list[int | None] = [1, 2, 3, None, 5, None, 4]\nexpected = [1, 3, 4]", - "playground_execution": "root = TreeNode.from_list(root_list)\nresult = Solution().right_side_view(root)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/climbing_stairs.json b/.templates/leetcode/json_old/climbing_stairs.json deleted file mode 100644 index 37a0d7f..0000000 --- a/.templates/leetcode/json_old/climbing_stairs.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "problem_name": "climbing_stairs", - "solution_class_name": "Solution", - "problem_number": "70", - "problem_title": "Climbing Stairs", - "difficulty": "Easy", - "topics": "Math, Dynamic Programming, Memoization", - "tags": ["grind-75"], - "readme_description": "You are climbing a staircase. It takes `n` steps to reach the top.\n\nEach time you can either climb `1` or `2` steps. In how many distinct ways can you climb to the top?", - "readme_examples": [ - { - "content": "```\nInput: n = 2\nOutput: 2\n```\n**Explanation:** There are two ways to climb to the top.\n1. 1 step + 1 step\n2. 2 steps" - }, - { - "content": "```\nInput: n = 3\nOutput: 3\n```\n**Explanation:** There are three ways to climb to the top.\n1. 1 step + 1 step + 1 step\n2. 1 step + 2 steps\n3. 2 steps + 1 step" - } - ], - "readme_constraints": "- 1 <= n <= 45", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { "name": "climb_stairs", "parameters": "n: int", "return_type": "int", "dummy_return": "1" } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "ClimbingStairs", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_climb_stairs", - "parametrize": "n, expected", - "parametrize_typed": "n: int, expected: int", - "test_cases": "[(1, 1), (2, 2), (3, 3), (4, 5), (5, 8), (6, 13), (10, 89), (20, 10946), (45, 1836311903)]", - "body": "result = self.solution.climb_stairs(n)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nn = 3\nexpected = 3", - "playground_execution": "result = Solution().climb_stairs(n)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/clone_graph.json b/.templates/leetcode/json_old/clone_graph.json deleted file mode 100644 index 2e338d6..0000000 --- a/.templates/leetcode/json_old/clone_graph.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "clone_graph", - "solution_class_name": "Solution", - "problem_number": "133", - "problem_title": "Clone Graph", - "difficulty": "Medium", - "topics": "Hash Table, Depth-First Search, Breadth-First Search, Graph", - "tags": ["grind-75"], - "readme_description": "Given a reference of a node in a **connected** undirected graph.\n\nReturn a **deep copy** (clone) of the graph.\n\nEach node in the graph contains a value (`int`) and a list (`List[Node]`) of its neighbors.\n\n```\nclass Node {\n public int val;\n public List neighbors;\n}\n```\n\n**Test case format:**\n\nFor simplicity, each node's value is the same as the node's index (1-indexed). For example, the first node with `val == 1`, the second node with `val == 2`, and so on. The graph is represented in the test case using an adjacency list.\n\n**An adjacency list** is a collection of unordered **lists** used to represent a finite graph. Each list describes the set of neighbors of a node in the graph.\n\nThe given node will always be the first node with `val = 1`. You must return the **copy of the given node** as a reference to the cloned graph.", - "readme_examples": [ - { - "content": "\"\"\n\n```\nInput: adjList = [[2,4],[1,3],[2,4],[1,3]]\nOutput: [[2,4],[1,3],[2,4],[1,3]]\nExplanation: There are 4 nodes in the graph.\n1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).\n2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).\n3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).\n4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).\n```" - }, - { - "content": "\"\"\n\n```\nInput: adjList = [[]]\nOutput: [[]]\nExplanation: Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors.\n```" - }, - { - "content": "```\nInput: adjList = []\nOutput: []\nExplanation: This an empty graph, it does not have any nodes.\n```" - } - ], - "readme_constraints": "- The number of nodes in the graph is in the range `[0, 100]`.\n- `1 <= Node.val <= 100`\n- `Node.val` is unique for each node.\n- There are no repeated edges and no self-loops in the graph.\n- The Graph is connected and all nodes can be visited starting from the given node.", - "readme_additional": "", - "solution_imports": "from leetcode_py import GraphNode", - "solution_methods": [ - { - "name": "clone_graph", - "parameters": "node: GraphNode | None", - "return_type": "GraphNode | None", - "dummy_return": "None" - } - ], - "test_imports": "import pytest\n\nfrom leetcode_py import GraphNode\nfrom leetcode_py.test_utils import logged_test\n\nfrom .solution import Solution", - "test_class_name": "CloneGraph", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_clone_graph", - "parametrize": "adj_list", - "parametrize_typed": "adj_list: list[list[int]]", - "test_cases": "[[[2, 4], [1, 3], [2, 4], [1, 3]], [[]], []]", - "body": "node = GraphNode.from_adjacency_list(adj_list)\nresult = self.solution.clone_graph(node)\nassert result.is_clone(node) if result else node is None" - } - ], - "playground_imports": "from solution import Solution\n\nfrom leetcode_py import GraphNode", - "playground_test_case": "# Example test case\nadj_list = [[2,4],[1,3],[2,4],[1,3]]\nnode = GraphNode.from_adjacency_list(adj_list)", - "playground_execution": "result = Solution().clone_graph(node)\nresult", - "playground_assertion": "assert result.is_clone(node) if result else node is None" -} diff --git a/.templates/leetcode/json_old/coin_change.json b/.templates/leetcode/json_old/coin_change.json deleted file mode 100644 index 61ebbc8..0000000 --- a/.templates/leetcode/json_old/coin_change.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "problem_name": "coin_change", - "solution_class_name": "Solution", - "problem_number": "322", - "problem_title": "Coin Change", - "difficulty": "Medium", - "topics": "Array, Dynamic Programming, Breadth-First Search", - "tags": ["grind-75"], - "readme_description": "You are given an integer array `coins` representing coins of different denominations and an integer `amount` representing a total amount of money.\n\nReturn the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return `-1`.\n\nYou may assume that you have an infinite number of each kind of coin.", - "readme_examples": [ - { - "content": "```\nInput: coins = [1,2,5], amount = 11\nOutput: 3\n```\n**Explanation:** 11 = 5 + 5 + 1" - }, - { "content": "```\nInput: coins = [2], amount = 3\nOutput: -1\n```" }, - { "content": "```\nInput: coins = [1], amount = 0\nOutput: 0\n```" } - ], - "readme_constraints": "- `1 <= coins.length <= 12`\n- `1 <= coins[i] <= 2^31 - 1`\n- `0 <= amount <= 10^4`", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "coin_change", - "parameters": "coins: list[int], amount: int", - "return_type": "int", - "dummy_return": "-1" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "CoinChange", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_coin_change", - "parametrize": "coins, amount, expected", - "parametrize_typed": "coins: list[int], amount: int, expected: int", - "test_cases": "[([1, 2, 5], 11, 3), ([2], 3, -1), ([1], 0, 0), ([1, 3, 4], 6, 2), ([2, 5, 10, 1], 27, 4), ([5], 3, -1), ([1], 1, 1), ([1, 2], 2, 1), ([186, 419, 83, 408], 6249, 20)]", - "body": "result = self.solution.coin_change(coins, amount)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ncoins = [1, 2, 5]\namount = 11\nexpected = 3", - "playground_execution": "result = Solution().coin_change(coins, amount)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/combination_sum.json b/.templates/leetcode/json_old/combination_sum.json deleted file mode 100644 index de4cbfb..0000000 --- a/.templates/leetcode/json_old/combination_sum.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "problem_name": "combination_sum", - "solution_class_name": "Solution", - "problem_number": "39", - "problem_title": "Combination Sum", - "difficulty": "Medium", - "topics": "Array, Backtracking", - "tags": ["grind-75"], - "readme_description": "Given an array of **distinct** integers `candidates` and a target integer `target`, return *a list of all **unique combinations** of* `candidates` *where the chosen numbers sum to* `target`. You may return the combinations in **any order**.\n\nThe **same** number may be chosen from `candidates` an **unlimited number of times**. Two combinations are unique if the frequency of at least one of the chosen numbers is different.\n\nThe test cases are generated such that the number of unique combinations that sum up to `target` is less than `150` combinations for the given input.", - "readme_examples": [ - { - "content": "```\nInput: candidates = [2,3,6,7], target = 7\nOutput: [[2,2,3],[7]]\n```\n**Explanation:** 2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times. 7 is a candidate, and 7 = 7. These are the only two combinations." - }, - { - "content": "```\nInput: candidates = [2,3,5], target = 8\nOutput: [[2,2,2,2],[2,3,3],[3,5]]\n```" - }, - { "content": "```\nInput: candidates = [2], target = 1\nOutput: []\n```" } - ], - "readme_constraints": "- 1 <= candidates.length <= 30\n- 2 <= candidates[i] <= 40\n- All elements of candidates are distinct.\n- 1 <= target <= 40", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "combination_sum", - "parameters": "candidates: list[int], target: int", - "return_type": "list[list[int]]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "CombinationSum", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_combination_sum", - "parametrize": "candidates, target, expected", - "parametrize_typed": "candidates: list[int], target: int, expected: list[list[int]]", - "test_cases": "[([2, 3, 6, 7], 7, [[2, 2, 3], [7]]), ([2, 3, 5], 8, [[2, 2, 2, 2], [2, 3, 3], [3, 5]]), ([2], 1, [])]", - "body": "result = self.solution.combination_sum(candidates, target)\n# Sort both result and expected for comparison\nresult_sorted = [sorted(combo) for combo in result]\nexpected_sorted = [sorted(combo) for combo in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ncandidates = [2, 3, 6, 7]\ntarget = 7\nexpected = [[2, 2, 3], [7]]", - "playground_execution": "result = Solution().combination_sum(candidates, target)\nresult", - "playground_assertion": "# Sort for comparison\nresult_sorted = [sorted(combo) for combo in result]\nexpected_sorted = [sorted(combo) for combo in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted" -} diff --git a/.templates/leetcode/json_old/container_with_most_water.json b/.templates/leetcode/json_old/container_with_most_water.json deleted file mode 100644 index 86f29ac..0000000 --- a/.templates/leetcode/json_old/container_with_most_water.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "problem_name": "container_with_most_water", - "solution_class_name": "Solution", - "problem_number": "11", - "problem_title": "Container With Most Water", - "difficulty": "Medium", - "topics": "Array, Two Pointers, Greedy", - "tags": ["grind-75"], - "readme_description": "You are given an integer array `height` of length `n`. There are `n` vertical lines drawn such that the two endpoints of the `i`th line are `(i, 0)` and `(i, height[i])`.\n\nFind two lines that together with the x-axis form a container, such that the container contains the most water.\n\nReturn the maximum amount of water a container can store.\n\nNotice that you may not slant the container.", - "readme_examples": [ - { - "content": "\"\"\n\n```\nInput: height = [1,8,6,2,5,4,8,3,7]\nOutput: 49\nExplanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.\n```" - }, - { "content": "```\nInput: height = [1,1]\nOutput: 1\n```" } - ], - "readme_constraints": "- n == height.length\n- 2 <= n <= 10^5\n- 0 <= height[i] <= 10^4", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "max_area", - "parameters": "height: list[int]", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "ContainerWithMostWater", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_max_area", - "parametrize": "height, expected", - "parametrize_typed": "height: list[int], expected: int", - "test_cases": "[([1,8,6,2,5,4,8,3,7], 49), ([1,1], 1), ([1,2,1], 2)]", - "body": "result = self.solution.max_area(height)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nheight = [1,8,6,2,5,4,8,3,7]\nexpected = 49", - "playground_execution": "result = Solution().max_area(height)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/contains_duplicate.json b/.templates/leetcode/json_old/contains_duplicate.json deleted file mode 100644 index 23ce24b..0000000 --- a/.templates/leetcode/json_old/contains_duplicate.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "problem_name": "contains_duplicate", - "solution_class_name": "Solution", - "problem_number": "217", - "problem_title": "Contains Duplicate", - "difficulty": "Easy", - "topics": "Array, Hash Table, Sorting", - "tags": ["grind-75"], - "readme_description": "Given an integer array `nums`, return `true` if any value appears **at least twice** in the array, and return `false` if every element is distinct.", - "readme_examples": [ - { - "content": "```\nInput: nums = [1,2,3,1]\nOutput: true\n```\n**Explanation:** The element 1 occurs at the indices 0 and 3." - }, - { - "content": "```\nInput: nums = [1,2,3,4]\nOutput: false\n```\n**Explanation:** All elements are distinct." - }, - { "content": "```\nInput: nums = [1,1,1,3,3,4,3,2,4,2]\nOutput: true\n```" } - ], - "readme_constraints": "- 1 <= nums.length <= 10^5\n- -10^9 <= nums[i] <= 10^9", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "contains_duplicate", - "parameters": "nums: list[int]", - "return_type": "bool", - "dummy_return": "False" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "ContainsDuplicate", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_contains_duplicate", - "parametrize": "nums, expected", - "parametrize_typed": "nums: list[int], expected: bool", - "test_cases": "[([1, 2, 3, 1], True), ([1, 2, 3, 4], False), ([1, 1, 1, 3, 3, 4, 3, 2, 4, 2], True)]", - "body": "result = self.solution.contains_duplicate(nums)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnums = [1, 2, 3, 1]\nexpected = True", - "playground_execution": "result = Solution().contains_duplicate(nums)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/course_schedule.json b/.templates/leetcode/json_old/course_schedule.json deleted file mode 100644 index 9ac990d..0000000 --- a/.templates/leetcode/json_old/course_schedule.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "course_schedule", - "solution_class_name": "Solution", - "problem_number": "207", - "problem_title": "Course Schedule", - "difficulty": "Medium", - "topics": "Depth-First Search, Breadth-First Search, Graph, Topological Sort", - "tags": ["grind-75"], - "readme_description": "There are a total of `numCourses` courses you have to take, labeled from `0` to `numCourses - 1`. You are given an array `prerequisites` where `prerequisites[i] = [ai, bi]` indicates that you **must** take course `bi` first if you want to take course `ai`.\n\n- For example, the pair `[0, 1]`, indicates that to take course `0` you have to first take course `1`.\n\nReturn `true` if you can finish all courses. Otherwise, return `false`.", - "readme_examples": [ - { - "content": "```\nInput: numCourses = 2, prerequisites = [[1,0]]\nOutput: true\n```\n**Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible." - }, - { - "content": "```\nInput: numCourses = 2, prerequisites = [[1,0],[0,1]]\nOutput: false\n```\n**Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible." - } - ], - "readme_constraints": "- `1 <= numCourses <= 2000`\n- `0 <= prerequisites.length <= 5000`\n- `prerequisites[i].length == 2`\n- `0 <= ai, bi < numCourses`\n- All the pairs prerequisites[i] are **unique**.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "can_finish", - "parameters": "num_courses: int, prerequisites: list[list[int]]", - "return_type": "bool", - "dummy_return": "False" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "CourseSchedule", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_can_finish", - "parametrize": "num_courses, prerequisites, expected", - "parametrize_typed": "num_courses: int, prerequisites: list[list[int]], expected: bool", - "test_cases": "[(2, [[1, 0]], True), (2, [[1, 0], [0, 1]], False), (1, [], True), (3, [[1, 0], [2, 1]], True), (4, [[1, 0], [2, 1], [3, 2], [1, 3]], False)]", - "body": "result = self.solution.can_finish(num_courses, prerequisites)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnum_courses = 2\nprerequisites = [[1, 0]]\nexpected = True", - "playground_execution": "result = Solution().can_finish(num_courses, prerequisites)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/diameter_of_binary_tree.json b/.templates/leetcode/json_old/diameter_of_binary_tree.json deleted file mode 100644 index bf3d6bb..0000000 --- a/.templates/leetcode/json_old/diameter_of_binary_tree.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "problem_name": "diameter_of_binary_tree", - "solution_class_name": "Solution", - "problem_number": "543", - "problem_title": "Diameter of Binary Tree", - "difficulty": "Easy", - "topics": "Tree, Depth-First Search, Binary Tree", - "tags": ["grind-75"], - "readme_description": "Given the `root` of a binary tree, return the length of the **diameter** of the tree.\n\nThe **diameter** of a binary tree is the **length** of the longest path between any two nodes in a tree. This path may or may not pass through the `root`.\n\nThe **length** of a path between two nodes is represented by the number of edges between them.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2021/03/06/diamtree.jpg)\n\n```\nInput: root = [1,2,3,4,5]\nOutput: 3\n```\n**Explanation:** 3 is the length of the path [4,2,1,3] or [5,2,1,3]." - }, - { "content": "```\nInput: root = [1,2]\nOutput: 1\n```" } - ], - "readme_constraints": "- The number of nodes in the tree is in the range [1, 10^4].\n- -100 <= Node.val <= 100", - "readme_additional": "", - "solution_imports": "from leetcode_py import TreeNode", - "solution_methods": [ - { - "name": "diameter_of_binary_tree", - "parameters": "root: TreeNode | None", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution", - "test_class_name": "DiameterOfBinaryTree", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_diameter_of_binary_tree", - "parametrize": "root_list, expected", - "parametrize_typed": "root_list: list[int | None], expected: int", - "test_cases": "[([1, 2, 3, 4, 5], 3), ([1, 2], 1)]", - "body": "root = TreeNode.from_list(root_list)\nresult = self.solution.diameter_of_binary_tree(root)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode", - "playground_test_case": "# Example test case\nroot_list: list[int | None] = [1, 2, 3, 4, 5]\nexpected = 3", - "playground_execution": "root = TreeNode.from_list(root_list)\nresult = Solution().diameter_of_binary_tree(root)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/evaluate_reverse_polish_notation.json b/.templates/leetcode/json_old/evaluate_reverse_polish_notation.json deleted file mode 100644 index 08e7061..0000000 --- a/.templates/leetcode/json_old/evaluate_reverse_polish_notation.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "evaluate_reverse_polish_notation", - "solution_class_name": "Solution", - "problem_number": "150", - "problem_title": "Evaluate Reverse Polish Notation", - "difficulty": "Medium", - "topics": "Array, Math, Stack", - "tags": ["grind-75"], - "readme_description": "You are given an array of strings `tokens` that represents an arithmetic expression in a **Reverse Polish Notation**.\n\nEvaluate the expression. Return *an integer that represents the value of the expression*.\n\n**Note that:**\n\n- The valid operators are `'+'`, `'-'`, `'*'`, and `'/'`.\n- Each operand may be an integer or another expression.\n- The division between two integers always **truncates toward zero**.\n- There will not be any division by zero.\n- The input represents a valid arithmetic expression in a reverse polish notation.\n- The answer and all the intermediate calculations can be represented in a **32-bit** integer.", - "readme_examples": [ - { - "content": "```\nInput: tokens = [\"2\",\"1\",\"+\",\"3\",\"*\"]\nOutput: 9\n```\n**Explanation:** ((2 + 1) * 3) = 9" - }, - { - "content": "```\nInput: tokens = [\"4\",\"13\",\"5\",\"/\",\"+\"]\nOutput: 6\n```\n**Explanation:** (4 + (13 / 5)) = 6" - }, - { - "content": "```\nInput: tokens = [\"10\",\"6\",\"9\",\"3\",\"+\",\"-11\",\"*\",\"/\",\"*\",\"17\",\"+\",\"5\",\"+\"]\nOutput: 22\n```\n**Explanation:** ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = 22" - } - ], - "readme_constraints": "- `1 <= tokens.length <= 10^4`\n- `tokens[i]` is either an operator: `\"+\"`, `\"-\"`, `\"*\"`, or `\"/\"`, or an integer in the range `[-200, 200]`.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "eval_rpn", - "parameters": "tokens: list[str]", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "EvaluateReversePolishNotation", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_eval_rpn", - "parametrize": "tokens, expected", - "parametrize_typed": "tokens: list[str], expected: int", - "test_cases": "[([\"2\", \"1\", \"+\", \"3\", \"*\"], 9), ([\"4\", \"13\", \"5\", \"/\", \"+\"], 6), ([\"10\", \"6\", \"9\", \"3\", \"+\", \"-11\", \"*\", \"/\", \"*\", \"17\", \"+\", \"5\", \"+\"], 22)]", - "body": "result = self.solution.eval_rpn(tokens)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ntokens = [\\\"2\\\", \\\"1\\\", \\\"+\\\", \\\"3\\\", \\\"*\\\"]\nexpected = 9", - "playground_execution": "result = Solution().eval_rpn(tokens)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/find_median_from_data_stream.json b/.templates/leetcode/json_old/find_median_from_data_stream.json deleted file mode 100644 index 7cee350..0000000 --- a/.templates/leetcode/json_old/find_median_from_data_stream.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "problem_name": "find_median_from_data_stream", - "solution_class_name": "MedianFinder", - "problem_number": "295", - "problem_title": "Find Median from Data Stream", - "difficulty": "Hard", - "topics": "Two Pointers, Design, Sorting, Heap (Priority Queue), Data Stream", - "tags": ["grind-75"], - "readme_description": "The **median** is the middle value in an ordered integer list. If the size of the list is even, there is no middle value, and the median is the mean of the two middle values.\n\n- For example, for `arr = [2,3,4]`, the median is `3`.\n- For example, for `arr = [2,3]`, the median is `(2 + 3) / 2 = 2.5`.\n\nImplement the MedianFinder class:\n\n- `MedianFinder()` initializes the `MedianFinder` object.\n- `void addNum(int num)` adds the integer `num` from the data stream to the data structure.\n- `double findMedian()` returns the median of all elements so far. Answers within `10^-5` of the actual answer will be accepted.", - "readme_examples": [ - { - "content": "```\nInput\n[\"MedianFinder\", \"addNum\", \"addNum\", \"findMedian\", \"addNum\", \"findMedian\"]\n[[], [1], [2], [], [3], []]\nOutput\n[null, null, null, 1.5, null, 2.0]\n```\n\n**Explanation:**\n```\nMedianFinder medianFinder = new MedianFinder();\nmedianFinder.addNum(1); // arr = [1]\nmedianFinder.addNum(2); // arr = [1, 2]\nmedianFinder.findMedian(); // return 1.5 (i.e., (1 + 2) / 2)\nmedianFinder.addNum(3); // arr = [1, 2, 3]\nmedianFinder.findMedian(); // return 2.0\n```" - } - ], - "readme_constraints": "- `-10^5 <= num <= 10^5`\n- There will be at least one element in the data structure before calling `findMedian`.\n- At most `5 * 10^4` calls will be made to `addNum` and `findMedian`.", - "readme_additional": "**Follow up:**\n\n- If all integer numbers from the stream are in the range `[0, 100]`, how would you optimize your solution?\n- If `99%` of all integer numbers from the stream are in the range `[0, 100]`, how would you optimize your solution?", - "solution_imports": "", - "solution_methods": [ - { "name": "__init__", "parameters": "", "return_type": "None", "dummy_return": "" }, - { "name": "add_num", "parameters": "num: int", "return_type": "None", "dummy_return": "" }, - { "name": "find_median", "parameters": "", "return_type": "float", "dummy_return": "0.0" } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import MedianFinder", - "test_class_name": "FindMedianFromDataStream", - "test_helper_methods": [], - "test_methods": [ - { - "name": "test_median_finder", - "parametrize": "operations, inputs, expected", - "parametrize_typed": "operations: list[str], inputs: list[list[int]], expected: list[float | None]", - "test_cases": "[([\"MedianFinder\", \"addNum\", \"addNum\", \"findMedian\", \"addNum\", \"findMedian\"], [[], [1], [2], [], [3], []], [None, None, None, 1.5, None, 2.0])]", - "body": "mf: MedianFinder | None = None\nresults: list[float | None] = []\nfor i, op in enumerate(operations):\n if op == \"MedianFinder\":\n mf = MedianFinder()\n results.append(None)\n elif op == \"addNum\" and mf is not None:\n mf.add_num(inputs[i][0])\n results.append(None)\n elif op == \"findMedian\" and mf is not None:\n results.append(mf.find_median())\nassert results == expected" - } - ], - "playground_imports": "from solution import MedianFinder", - "playground_test_case": "# Example test case\noperations = ['MedianFinder', 'addNum', 'addNum', 'findMedian', 'addNum', 'findMedian']\ninputs = [[], [1], [2], [], [3], []]\nexpected = [None, None, None, 1.5, None, 2.0]", - "playground_execution": "mf = None\nresults: list[float | None] = []\nfor i, op in enumerate(operations):\n if op == 'MedianFinder':\n mf = MedianFinder()\n results.append(None)\n elif op == 'addNum' and mf is not None:\n mf.add_num(inputs[i][0])\n results.append(None)\n elif op == 'findMedian' and mf is not None:\n results.append(mf.find_median())\nresults", - "playground_assertion": "assert results == expected" -} diff --git a/.templates/leetcode/json_old/first_bad_version.json b/.templates/leetcode/json_old/first_bad_version.json deleted file mode 100644 index cdaf3bf..0000000 --- a/.templates/leetcode/json_old/first_bad_version.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "problem_name": "first_bad_version", - "solution_class_name": "Solution", - "problem_number": "278", - "problem_title": "First Bad Version", - "difficulty": "Easy", - "topics": "Binary Search, Interactive", - "tags": ["grind-75"], - "readme_description": "You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad.\n\nSuppose you have `n` versions `[1, 2, ..., n]` and you want to find out the first bad one, which causes all the following ones to be bad.\n\nYou are given an API `bool isBadVersion(version)` which returns whether `version` is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API.", - "readme_examples": [ - { - "content": "```\nInput: n = 5, bad = 4\nOutput: 4\n```\n**Explanation:**\n```\ncall isBadVersion(3) -> false\ncall isBadVersion(5) -> true\ncall isBadVersion(4) -> true\n```\nThen 4 is the first bad version." - }, - { "content": "```\nInput: n = 1, bad = 1\nOutput: 1\n```" } - ], - "readme_constraints": "- 1 <= bad <= n <= 2^31 - 1", - "readme_additional": "**Note:** The `isBadVersion` API is already defined for you.", - "solution_imports": "", - "solution_methods": [ - { - "name": "is_bad_version", - "parameters": "version: int", - "return_type": "bool", - "dummy_return": "False" - }, - { - "name": "first_bad_version", - "parameters": "n: int", - "return_type": "int", - "dummy_return": "1" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "FirstBadVersion", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }, - { - "name": "is_bad_version", - "parameters": "version: int, bad: int", - "body": "return version >= bad" - } - ], - "test_methods": [ - { - "name": "test_first_bad_version", - "parametrize": "n, bad, expected", - "parametrize_typed": "n: int, bad: int, expected: int", - "test_cases": "[(5, 4, 4), (1, 1, 1), (3, 1, 1), (10, 7, 7), (5, -1, 1)]", - "body": "solution = Solution()\n# Mock is_bad_version for this test\nsolution.is_bad_version = lambda version: self.is_bad_version(version, bad)\nresult = solution.first_bad_version(n)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nn = 5\nbad = 4\nexpected = 4", - "playground_execution": "solution = Solution()\nsolution.is_bad_version = lambda version: version >= bad\nresult = solution.first_bad_version(n)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/flood_fill.json b/.templates/leetcode/json_old/flood_fill.json deleted file mode 100644 index 5cab119..0000000 --- a/.templates/leetcode/json_old/flood_fill.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "flood_fill", - "solution_class_name": "Solution", - "problem_number": "733", - "problem_title": "Flood Fill", - "difficulty": "Easy", - "topics": "Array, Depth-First Search, Breadth-First Search, Matrix", - "tags": ["grind-75"], - "readme_description": "You are given an image represented by an `m x n` grid of integers `image`, where `image[i][j]` represents the pixel value of the image. You are also given three integers `sr`, `sc`, and `color`. Your task is to perform a **flood fill** on the image starting from the pixel `image[sr][sc]`.\n\nTo perform a **flood fill**:\n\n1. Begin with the starting pixel and change its color to `color`.\n2. Perform the same process for each pixel that is **directly adjacent** (pixels that share a side with the original pixel, either horizontally or vertically) and shares the **same color** as the starting pixel.\n3. Keep **repeating** this process by checking neighboring pixels of the *updated* pixels and modifying their color if it matches the original color of the starting pixel.\n4. The process **stops** when there are **no more** adjacent pixels of the original color to update.\n\nReturn the **modified** image after performing the flood fill.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2021/06/01/flood1-grid.jpg)\n\n```\nInput: image = [[1,1,1],[1,1,0],[1,0,1]], sr = 1, sc = 1, color = 2\nOutput: [[2,2,2],[2,2,0],[2,0,1]]\n```\n**Explanation:** From the center of the image with position `(sr, sc) = (1, 1)` (i.e., the red pixel), all pixels connected by a path of the same color as the starting pixel (i.e., the blue pixels) are colored with the new color. Note the bottom corner is not colored 2, because it is not horizontally or vertically connected to the starting pixel." - }, - { - "content": "```\nInput: image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, color = 0\nOutput: [[0,0,0],[0,0,0]]\n```\n**Explanation:** The starting pixel is already colored with 0, which is the same as the target color. Therefore, no changes are made to the image." - } - ], - "readme_constraints": "- `m == image.length`\n- `n == image[i].length`\n- `1 <= m, n <= 50`\n- `0 <= image[i][j], color < 2^16`\n- `0 <= sr < m`\n- `0 <= sc < n`", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "flood_fill", - "parameters": "image: list[list[int]], sr: int, sc: int, color: int", - "return_type": "list[list[int]]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "FloodFill", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_flood_fill", - "parametrize": "image, sr, sc, color, expected", - "parametrize_typed": "image: list[list[int]], sr: int, sc: int, color: int, expected: list[list[int]]", - "test_cases": "[([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 2, [[2, 2, 2], [2, 2, 0], [2, 0, 1]]), ([[0, 0, 0], [0, 0, 0]], 0, 0, 0, [[0, 0, 0], [0, 0, 0]]), ([[0, 0, 0], [0, 1, 1]], 1, 1, 1, [[0, 0, 0], [0, 1, 1]]), ([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 1, [[1, 1, 1], [1, 1, 0], [1, 0, 1]])]", - "body": "result = self.solution.flood_fill(image, sr, sc, color)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nimage = [[1, 1, 1], [1, 1, 0], [1, 0, 1]]\nsr = 1\nsc = 1\ncolor = 2\nexpected = [[2, 2, 2], [2, 2, 0], [2, 0, 1]]", - "playground_execution": "result = Solution().flood_fill(image, sr, sc, color)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/implement_queue_using_stacks.json b/.templates/leetcode/json_old/implement_queue_using_stacks.json deleted file mode 100644 index cc38ff1..0000000 --- a/.templates/leetcode/json_old/implement_queue_using_stacks.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "problem_name": "implement_queue_using_stacks", - "solution_class_name": "MyQueue", - "problem_number": "232", - "problem_title": "Implement Queue using Stacks", - "difficulty": "Easy", - "topics": "Stack, Design, Queue", - "tags": ["grind-75"], - "readme_description": "Implement a first in first out (FIFO) queue using only two stacks. The implemented queue should support all the functions of a normal queue (`push`, `peek`, `pop`, and `empty`).\n\nImplement the `MyQueue` class:\n\n- `void push(int x)` Pushes element x to the back of the queue.\n- `int pop()` Removes the element from the front of the queue and returns it.\n- `int peek()` Returns the element at the front of the queue.\n- `boolean empty()` Returns `true` if the queue is empty, `false` otherwise.", - "readme_examples": [ - { - "content": "```\nInput\n[\"MyQueue\", \"push\", \"push\", \"peek\", \"pop\", \"empty\"]\n[[], [1], [2], [], [], []]\nOutput\n[null, null, null, 1, 1, false]\n```\n**Explanation:**\n```\nMyQueue myQueue = new MyQueue();\nmyQueue.push(1); // queue is: [1]\nmyQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)\nmyQueue.peek(); // return 1\nmyQueue.pop(); // return 1, queue is [2]\nmyQueue.empty(); // return false\n```" - } - ], - "readme_constraints": "- 1 <= x <= 9\n- At most 100 calls will be made to push, pop, peek, and empty.\n- All the calls to pop and peek are valid.", - "readme_additional": "**Notes:**\n- You must use **only** standard operations of a stack, which means only `push to top`, `peek/pop from top`, `size`, and `is empty` operations are valid.\n- Depending on your language, the stack may not be supported natively. You may simulate a stack using a list or deque (double-ended queue) as long as you use only a stack's standard operations.\n\n**Follow-up:** Can you implement the queue such that each operation is amortized `O(1)` time complexity? In other words, performing `n` operations will take overall `O(n)` time even if one of those operations may take longer.", - "solution_imports": "", - "solution_methods": [ - { "name": "__init__", "parameters": "", "return_type": "None", "dummy_return": "" }, - { "name": "push", "parameters": "x: int", "return_type": "None", "dummy_return": "" }, - { "name": "pop", "parameters": "", "return_type": "int", "dummy_return": "0" }, - { "name": "peek", "parameters": "", "return_type": "int", "dummy_return": "0" }, - { "name": "empty", "parameters": "", "return_type": "bool", "dummy_return": "True" } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import MyQueue", - "test_class_name": "ImplementQueueUsingStacks", - "test_helper_methods": [], - "test_methods": [ - { - "name": "test_queue_operations", - "parametrize": "operations, inputs, expected", - "parametrize_typed": "operations: list[str], inputs: list[list[int]], expected: list[int | None | bool]", - "test_cases": "[(['MyQueue', 'push', 'push', 'peek', 'pop', 'empty'], [[], [1], [2], [], [], []], [None, None, None, 1, 1, False]), (['MyQueue', 'empty', 'push', 'peek', 'pop', 'empty'], [[], [], [1], [], [], []], [None, True, None, 1, 1, True]), (['MyQueue', 'push', 'push', 'push', 'pop', 'pop', 'peek', 'pop', 'empty'], [[], [1], [2], [3], [], [], [], [], []], [None, None, None, None, 1, 2, 3, 3, True])]", - "body": "queue = None\nresults: list[int | None | bool] = []\nfor i, op in enumerate(operations):\n if op == 'MyQueue':\n queue = MyQueue()\n results.append(None)\n elif op == 'push' and queue is not None:\n queue.push(inputs[i][0])\n results.append(None)\n elif op == 'pop' and queue is not None:\n results.append(queue.pop())\n elif op == 'peek' and queue is not None:\n results.append(queue.peek())\n elif op == 'empty' and queue is not None:\n results.append(queue.empty())\nassert results == expected" - } - ], - "playground_imports": "from solution import MyQueue", - "playground_test_case": "# Example test case\nqueue = MyQueue()\nqueue.push(1)\nqueue.push(2)", - "playground_execution": "result_peek = queue.peek()\nresult_pop = queue.pop()\nresult_empty = queue.empty()\nprint(f'peek: {result_peek}, pop: {result_pop}, empty: {result_empty}')", - "playground_assertion": "assert result_peek == 1\nassert result_pop == 1\nassert result_empty == False" -} diff --git a/.templates/leetcode/json_old/implement_trie_prefix_tree.json b/.templates/leetcode/json_old/implement_trie_prefix_tree.json deleted file mode 100644 index 90ed349..0000000 --- a/.templates/leetcode/json_old/implement_trie_prefix_tree.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "implement_trie_prefix_tree", - "solution_class_name": "Trie(DictTree[str])", - "problem_number": "208", - "problem_title": "Implement Trie (Prefix Tree)", - "difficulty": "Medium", - "topics": "Hash Table, String, Design, Trie", - "tags": ["grind-75"], - "readme_description": "A **trie** (pronounced as \"try\") or **prefix tree** is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker.\n\nImplement the Trie class:\n\n- `Trie()` Initializes the trie object.\n- `void insert(String word)` Inserts the string `word` into the trie.\n- `boolean search(String word)` Returns `true` if the string `word` is in the trie (i.e., was inserted before), and `false` otherwise.\n- `boolean startsWith(String prefix)` Returns `true` if there is a previously inserted string `word` that has the prefix `prefix`, and `false` otherwise.", - "readme_examples": [ - { - "content": "```\nInput\n[\"Trie\", \"insert\", \"search\", \"search\", \"startsWith\", \"insert\", \"search\"]\n[[], [\"apple\"], [\"apple\"], [\"app\"], [\"app\"], [\"app\"], [\"app\"]]\nOutput\n[null, null, true, false, true, null, true]\n```\n\n**Explanation:**\n```python\ntrie = Trie()\ntrie.insert(\"apple\")\ntrie.search(\"apple\") # return True\ntrie.search(\"app\") # return False\ntrie.starts_with(\"app\") # return True\ntrie.insert(\"app\")\ntrie.search(\"app\") # return True\n```" - } - ], - "readme_constraints": "- `1 <= word.length, prefix.length <= 2000`\n- `word` and `prefix` consist only of lowercase English letters.\n- At most `3 * 10^4` calls **in total** will be made to `insert`, `search`, and `starts_with`.", - "readme_additional": "", - "solution_imports": "from leetcode_py.data_structures import DictTree, RecursiveDict", - "solution_methods": [ - { - "name": "__init__", - "parameters": "", - "return_type": "None", - "dummy_return": "super().__init__()\n self.root: RecursiveDict[str] = {}" - }, - { "name": "insert", "parameters": "word: str", "return_type": "None", "dummy_return": "" }, - { "name": "search", "parameters": "word: str", "return_type": "bool", "dummy_return": "False" }, - { - "name": "starts_with", - "parameters": "prefix: str", - "return_type": "bool", - "dummy_return": "False" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Trie", - "test_class_name": "ImplementTriePrefixTree", - "test_helper_methods": [], - "test_methods": [ - { - "name": "test_trie_operations", - "parametrize": "operations, inputs, expected", - "parametrize_typed": "operations: list[str], inputs: list[list[str]], expected: list[bool | None]", - "test_cases": "[(['Trie', 'insert', 'search', 'search', 'starts_with', 'insert', 'search'], [[], ['apple'], ['apple'], ['app'], ['app'], ['app'], ['app']], [None, None, True, False, True, None, True]), (['Trie', 'insert', 'insert', 'search', 'search', 'starts_with', 'starts_with'], [[], ['hello'], ['world'], ['hello'], ['hi'], ['hel'], ['wor']], [None, None, None, True, False, True, True]), (['Trie', 'insert', 'insert', 'search', 'search', 'starts_with', 'starts_with'], [[], ['a'], ['aa'], ['a'], ['aa'], ['a'], ['aa']], [None, None, None, True, True, True, True]), (['Trie', 'insert', 'search', 'starts_with', 'insert', 'search', 'starts_with'], [[], ['test'], ['testing'], ['test'], ['testing'], ['testing'], ['test']], [None, None, False, True, None, True, True]), (['Trie', 'search', 'starts_with'], [[], ['empty'], ['empty']], [None, False, False])]", - "body": "trie: Trie | None = None\nresults: list[bool | None] = []\nfor i, op in enumerate(operations):\n if op == 'Trie':\n trie = Trie()\n results.append(None)\n elif op == 'insert' and trie is not None:\n trie.insert(inputs[i][0])\n results.append(None)\n elif op == 'search' and trie is not None:\n results.append(trie.search(inputs[i][0]))\n elif op == 'starts_with' and trie is not None:\n results.append(trie.starts_with(inputs[i][0]))\nassert results == expected" - } - ], - "playground_imports": "from solution import Trie", - "playground_test_case": "# Example test case\noperations = ['Trie', 'insert', 'search', 'search', 'starts_with', 'insert', 'search']\ninputs = [[], ['apple'], ['apple'], ['app'], ['app'], ['app'], ['app']]\nexpected = [None, None, True, False, True, None, True]", - "playground_execution": "trie = None\nresults: list[bool | None] = []\nfor i, op in enumerate(operations):\n if op == 'Trie':\n trie = Trie()\n results.append(None)\n elif op == 'insert' and trie is not None:\n trie.insert(inputs[i][0])\n results.append(None)\n elif op == 'search' and trie is not None:\n results.append(trie.search(inputs[i][0]))\n elif op == 'starts_with' and trie is not None:\n results.append(trie.starts_with(inputs[i][0]))\nresults", - "playground_assertion": "assert results == expected" -} diff --git a/.templates/leetcode/json_old/insert_interval.json b/.templates/leetcode/json_old/insert_interval.json deleted file mode 100644 index 7ffa1ca..0000000 --- a/.templates/leetcode/json_old/insert_interval.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "insert_interval", - "solution_class_name": "Solution", - "problem_number": "57", - "problem_title": "Insert Interval", - "difficulty": "Medium", - "topics": "Array", - "tags": ["grind-75"], - "readme_description": "You are given an array of non-overlapping intervals `intervals` where `intervals[i] = [starti, endi]` represent the start and the end of the ith interval and `intervals` is sorted in ascending order by `starti`. You are also given an interval `newInterval = [start, end]` that represents the start and end of another interval.\n\nInsert `newInterval` into `intervals` such that `intervals` is still sorted in ascending order by `starti` and `intervals` still does not have any overlapping intervals (merge overlapping intervals if necessary).\n\nReturn `intervals` after the insertion.", - "readme_examples": [ - { - "content": "```\nInput: intervals = [[1,3],[6,9]], newInterval = [2,5]\nOutput: [[1,5],[6,9]]\n```" - }, - { - "content": "```\nInput: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]\nOutput: [[1,2],[3,10],[12,16]]\nExplanation: Because the new interval [4,8] overlaps with [3,5],[6,7],[8,10].\n```" - } - ], - "readme_constraints": "- 0 <= intervals.length <= 10^4\n- intervals[i].length == 2\n- 0 <= starti <= endi <= 10^5\n- intervals is sorted by starti in ascending order\n- newInterval.length == 2\n- 0 <= start <= end <= 10^5", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "insert", - "parameters": "intervals: list[list[int]], new_interval: list[int]", - "return_type": "list[list[int]]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "InsertInterval", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_insert", - "parametrize": "intervals, new_interval, expected", - "parametrize_typed": "intervals: list[list[int]], new_interval: list[int], expected: list[list[int]]", - "test_cases": "[([[1,3],[6,9]], [2,5], [[1,5],[6,9]]), ([[1,2],[3,5],[6,7],[8,10],[12,16]], [4,8], [[1,2],[3,10],[12,16]])]", - "body": "result = self.solution.insert(intervals, new_interval)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nintervals = [[1,3],[6,9]]\nnew_interval = [2,5]\nexpected = [[1,5],[6,9]]", - "playground_execution": "result = Solution().insert(intervals, new_interval)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/invert_binary_tree.json b/.templates/leetcode/json_old/invert_binary_tree.json deleted file mode 100644 index 8e1f324..0000000 --- a/.templates/leetcode/json_old/invert_binary_tree.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "problem_name": "invert_binary_tree", - "solution_class_name": "Solution", - "problem_number": "226", - "problem_title": "Invert Binary Tree", - "difficulty": "Easy", - "topics": "Tree, Depth-First Search, Breadth-First Search, Binary Tree", - "tags": ["grind-75"], - "readme_description": "Given the `root` of a binary tree, invert the tree, and return its root.", - "readme_examples": [ - { "content": "```\nInput: root = [4,2,7,1,3,6,9]\nOutput: [4,7,2,9,6,3,1]\n```" }, - { "content": "```\nInput: root = [2,1,3]\nOutput: [2,3,1]\n```" }, - { "content": "```\nInput: root = []\nOutput: []\n```" } - ], - "readme_constraints": "- The number of nodes in the tree is in the range [0, 100]\n- -100 <= Node.val <= 100", - "readme_additional": "", - "solution_imports": "from leetcode_py import TreeNode", - "solution_methods": [ - { - "name": "invert_tree", - "parameters": "root: TreeNode[int] | None", - "return_type": "TreeNode[int] | None", - "dummy_return": "None" - } - ], - "test_imports": "import pytest\n\nfrom leetcode_py import TreeNode\nfrom leetcode_py.test_utils import logged_test\n\nfrom .solution import Solution", - "test_class_name": "InvertBinaryTree", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_invert_tree", - "parametrize": "root_list, expected_list", - "parametrize_typed": "root_list: list[int | None], expected_list: list[int | None]", - "test_cases": "[([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), ([2, 1, 3], [2, 3, 1]), ([], [])]", - "body": "root = TreeNode[int].from_list(root_list)\nexpected = TreeNode[int].from_list(expected_list)\nresult = self.solution.invert_tree(root)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution\n\nfrom leetcode_py import TreeNode", - "playground_test_case": "# Example test case\nroot_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\nroot = TreeNode[int].from_list(root_list)\nexpected = TreeNode[int].from_list([4, 7, 2, 9, 6, 3, 1])", - "playground_execution": "result = Solution().invert_tree(root)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/k_closest_points_to_origin.json b/.templates/leetcode/json_old/k_closest_points_to_origin.json deleted file mode 100644 index 25e7cfa..0000000 --- a/.templates/leetcode/json_old/k_closest_points_to_origin.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "k_closest_points_to_origin", - "solution_class_name": "Solution", - "problem_number": "973", - "problem_title": "K Closest Points to Origin", - "difficulty": "Medium", - "topics": "Array, Math, Divide and Conquer, Geometry, Sorting, Heap (Priority Queue), Quickselect", - "tags": ["grind-75"], - "readme_description": "Given an array of `points` where `points[i] = [xi, yi]` represents a point on the **X-Y** plane and an integer `k`, return the `k` closest points to the origin `(0, 0)`.\n\nThe distance between two points on the **X-Y** plane is the Euclidean distance (i.e., `\u221a(x1 - x2)\u00b2 + (y1 - y2)\u00b2`).\n\nYou may return the answer in **any order**. The answer is **guaranteed** to be **unique** (except for the order that it is in).", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2021/03/03/closestplane1.jpg)\n\n```\nInput: points = [[1,3],[-2,2]], k = 1\nOutput: [[-2,2]]\n```\n**Explanation:** The distance between (1, 3) and the origin is sqrt(10). The distance between (-2, 2) and the origin is sqrt(8). Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin. We only want the closest k = 1 points from the origin, so the answer is just [[-2,2]]." - }, - { - "content": "```\nInput: points = [[3,3],[5,-1],[-2,4]], k = 2\nOutput: [[3,3],[-2,4]]\n```\n**Explanation:** The answer [[-2,4],[3,3]] would also be accepted." - } - ], - "readme_constraints": "- `1 <= k <= points.length <= 10^4`\n- `-10^4 <= xi, yi <= 10^4`", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "k_closest", - "parameters": "points: list[list[int]], k: int", - "return_type": "list[list[int]]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "KClosestPointsToOrigin", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_k_closest", - "parametrize": "points, k, expected", - "parametrize_typed": "points: list[list[int]], k: int, expected: list[list[int]]", - "test_cases": "[([[1, 3], [-2, 2]], 1, [[-2, 2]]), ([[3, 3], [5, -1], [-2, 4]], 2, [[3, 3], [-2, 4]]), ([[0, 1], [1, 0]], 2, [[0, 1], [1, 0]]), ([[1, 1], [1, 1], [1, 1]], 2, [[1, 1], [1, 1]])]", - "body": "result = self.solution.k_closest(points, k)\n# Sort both result and expected for comparison since order doesn't matter\nresult_sorted = sorted(result)\nexpected_sorted = sorted(expected)\nassert result_sorted == expected_sorted" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\npoints = [[1, 3], [-2, 2]]\nk = 1\nexpected = [[-2, 2]]", - "playground_execution": "result = Solution().k_closest(points, k)\nresult", - "playground_assertion": "assert sorted(result) == sorted(expected)" -} diff --git a/.templates/leetcode/json_old/kth_smallest_element_in_a_bst.json b/.templates/leetcode/json_old/kth_smallest_element_in_a_bst.json deleted file mode 100644 index 3239379..0000000 --- a/.templates/leetcode/json_old/kth_smallest_element_in_a_bst.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "kth_smallest_element_in_a_bst", - "solution_class_name": "Solution", - "problem_number": "230", - "problem_title": "Kth Smallest Element in a BST", - "difficulty": "Medium", - "topics": "Tree, Depth-First Search, Binary Search Tree, Binary Tree", - "tags": ["grind-75"], - "readme_description": "Given the `root` of a binary search tree, and an integer `k`, return the `k`th smallest value (1-indexed) of all the values of the nodes in the tree.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2021/01/28/kthtree1.jpg)\n\n```\nInput: root = [3,1,4,null,2], k = 1\nOutput: 1\n```" - }, - { - "content": "![Example 2](https://assets.leetcode.com/uploads/2021/01/28/kthtree2.jpg)\n\n```\nInput: root = [5,3,6,2,4,null,null,1], k = 3\nOutput: 3\n```" - } - ], - "readme_constraints": "- The number of nodes in the tree is `n`.\n- `1 <= k <= n <= 10^4`\n- `0 <= Node.val <= 10^4`", - "readme_additional": "**Follow up:** If the BST is modified often (i.e., we can do insert and delete operations) and you need to find the kth smallest frequently, how would you optimize?", - "solution_imports": "from leetcode_py import TreeNode", - "solution_methods": [ - { - "name": "kth_smallest", - "parameters": "root: TreeNode | None, k: int", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution", - "test_class_name": "KthSmallestElementInABst", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_kth_smallest", - "parametrize": "root_list, k, expected", - "parametrize_typed": "root_list: list[int | None], k: int, expected: int", - "test_cases": "[([3, 1, 4, None, 2], 1, 1), ([5, 3, 6, 2, 4, None, None, 1], 3, 3), ([1], 1, 1)]", - "body": "root = TreeNode.from_list(root_list)\nresult = self.solution.kth_smallest(root, k)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode", - "playground_test_case": "# Example test case\nroot_list = [3, 1, 4, None, 2]\nk = 1\nexpected = 1", - "playground_execution": "root = TreeNode.from_list(root_list)\nresult = Solution().kth_smallest(root, k)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/largest_rectangle_in_histogram.json b/.templates/leetcode/json_old/largest_rectangle_in_histogram.json deleted file mode 100644 index 8ebbd85..0000000 --- a/.templates/leetcode/json_old/largest_rectangle_in_histogram.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "largest_rectangle_in_histogram", - "solution_class_name": "Solution", - "problem_number": "84", - "problem_title": "Largest Rectangle in Histogram", - "difficulty": "Hard", - "topics": "Array, Stack, Monotonic Stack", - "tags": ["grind-75"], - "readme_description": "Given an array of integers `heights` representing the histogram's bar height where the width of each bar is `1`, return the area of the largest rectangle in the histogram.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2021/01/04/histogram.jpg)\n\n```\nInput: heights = [2,1,5,6,2,3]\nOutput: 10\n```\n**Explanation:** The above is a histogram where width of each bar is 1. The largest rectangle is shown in the red area, which has an area = 10 units." - }, - { - "content": "![Example 2](https://assets.leetcode.com/uploads/2021/01/04/histogram-1.jpg)\n\n```\nInput: heights = [2,4]\nOutput: 4\n```" - } - ], - "readme_constraints": "- `1 <= heights.length <= 10^5`\n- `0 <= heights[i] <= 10^4`", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "largest_rectangle_area", - "parameters": "heights: list[int]", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "LargestRectangleInHistogram", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_largest_rectangle_area", - "parametrize": "heights, expected", - "parametrize_typed": "heights: list[int], expected: int", - "test_cases": "[([2, 1, 5, 6, 2, 3], 10), ([2, 4], 4), ([1], 1), ([0], 0), ([1, 1], 2), ([0, 0, 0], 0), ([1, 2, 3, 4, 5], 9), ([5, 4, 3, 2, 1], 9), ([3, 3, 3, 3], 12), ([2, 1, 2], 3), ([1, 3, 1], 3), ([6, 7, 5, 2, 4, 5, 9, 3], 16), ([4, 2, 0, 3, 2, 5], 6), ([1, 2, 2, 1], 4), ([0, 9], 9), ([9, 0], 9), ([2, 1, 5, 6, 2, 3, 1, 5, 6, 2], 10), ([1, 8, 6, 2, 5, 4, 8, 3, 7], 16)]", - "body": "result = self.solution.largest_rectangle_area(heights)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nheights = [2, 1, 5, 6, 2, 3]\nexpected = 10", - "playground_execution": "result = Solution().largest_rectangle_area(heights)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/linked_list_cycle.json b/.templates/leetcode/json_old/linked_list_cycle.json deleted file mode 100644 index ae5dd54..0000000 --- a/.templates/leetcode/json_old/linked_list_cycle.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "problem_name": "linked_list_cycle", - "solution_class_name": "Solution", - "problem_number": "141", - "problem_title": "Linked List Cycle", - "difficulty": "Easy", - "topics": "Hash Table, Linked List, Two Pointers", - "tags": ["grind-75"], - "readme_description": "Given `head`, the head of a linked list, determine if the linked list has a cycle in it.\n\nThere is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the `next` pointer. Internally, `pos` is used to denote the index of the node that tail's `next` pointer is connected to. **Note that `pos` is not passed as a parameter**.\n\nReturn `true` *if there is a cycle in the linked list*. Otherwise, return `false`.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist.png)\n\n```\nInput: head = [3,2,0,-4], pos = 1\nOutput: true\n```\n**Explanation:** There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed)." - }, - { - "content": "![Example 2](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test2.png)\n\n```\nInput: head = [1,2], pos = 0\nOutput: true\n```\n**Explanation:** There is a cycle in the linked list, where the tail connects to the 0th node." - }, - { - "content": "![Example 3](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test3.png)\n\n```\nInput: head = [1], pos = -1\nOutput: false\n```\n**Explanation:** There is no cycle in the linked list." - } - ], - "readme_constraints": "- The number of the nodes in the list is in the range `[0, 10^4]`.\n- `-10^5 <= Node.val <= 10^5`\n- `pos` is `-1` or a **valid index** in the linked-list.", - "readme_additional": "**Follow up:** Can you solve it using `O(1)` (i.e. constant) memory?", - "solution_imports": "from leetcode_py import ListNode", - "solution_methods": [ - { - "name": "has_cycle", - "parameters": "head: ListNode[int] | None", - "return_type": "bool", - "dummy_return": "False" - } - ], - "test_imports": "import pytest\nfrom leetcode_py import ListNode\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "LinkedListCycle", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }, - { - "name": "create_cycle_list", - "parameters": "values: list[int], pos: int", - "body": "if not values:\n return None\n\nnodes = []\nhead = ListNode(values[0])\nnodes.append(head)\ncurrent = head\n\nfor i in range(1, len(values)):\n current.next = ListNode(values[i])\n current = current.next\n nodes.append(current)\n\nif pos != -1 and pos < len(nodes):\n current.next = nodes[pos]\n\nreturn head" - } - ], - "test_methods": [ - { - "name": "test_has_cycle", - "parametrize": "values, pos, expected", - "parametrize_typed": "values: list[int], pos: int, expected: bool", - "test_cases": "[([3, 2, 0, -4], 1, True), ([1, 2], 0, True), ([1], -1, False), ([], -1, False), ([1, 2, 3], -1, False), ([1, 2, 3, 4, 5], 0, True), ([1, 2, 3, 4, 5], 2, True), ([1, 2, 3, 4, 5], 4, True), ([1], 0, True), ([1, 2], 1, True), ([1, 2, 3, 4, 5, 6, 7, 8], 3, True), ([1, 2, 3, 4, 5, 6, 7, 8], -1, False), ([1, 2], -1, False), ([5, 10], 0, True), ([5, 10], 1, True), ([0], -1, False), ([-1, -2, -3], 1, True), ([100, 200, 300], 0, True)]", - "body": "head = self.create_cycle_list(values, pos)\nresult = self.solution.has_cycle(head)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "import os\nimport sys\nsys.path.append(os.path.join(os.getcwd(), \\\"..\\\"))\nfrom linked_list_cycle.tests import TestLinkedListCycle\n\n# Example test case\nvalues = [3, 2, 0, -4]\npos = 1\nexpected = True", - "playground_execution": "head = TestLinkedListCycle().create_cycle_list(values, pos)\nresult = Solution().has_cycle(head)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/longest_palindrome.json b/.templates/leetcode/json_old/longest_palindrome.json deleted file mode 100644 index 9561580..0000000 --- a/.templates/leetcode/json_old/longest_palindrome.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "longest_palindrome", - "solution_class_name": "Solution", - "problem_number": "409", - "problem_title": "Longest Palindrome", - "difficulty": "Easy", - "topics": "Hash Table, String, Greedy", - "tags": ["grind-75"], - "readme_description": "Given a string `s` which consists of lowercase or uppercase letters, return the length of the longest palindrome that can be built with those letters.\n\nLetters are case sensitive, for example, \"Aa\" is not considered a palindrome.", - "readme_examples": [ - { - "content": "```\nInput: s = \"abccccdd\"\nOutput: 7\n```\n**Explanation:** One longest palindrome that can be built is \"dccaccd\", whose length is 7." - }, - { - "content": "```\nInput: s = \"a\"\nOutput: 1\n```\n**Explanation:** The longest palindrome that can be built is \"a\", whose length is 1." - } - ], - "readme_constraints": "- `1 <= s.length <= 2000`\n- `s` consists of lowercase and/or uppercase English letters only.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "longest_palindrome", - "parameters": "s: str", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "LongestPalindrome", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_longest_palindrome", - "parametrize": "s, expected", - "parametrize_typed": "s: str, expected: int", - "test_cases": "[('abccccdd', 7), ('a', 1), ('Aa', 1), ('aabbcc', 6)]", - "body": "result = self.solution.longest_palindrome(s)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ns = 'abccccdd'\nexpected = 7", - "playground_execution": "result = Solution().longest_palindrome(s)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/longest_palindromic_substring.json b/.templates/leetcode/json_old/longest_palindromic_substring.json deleted file mode 100644 index 8ee3e31..0000000 --- a/.templates/leetcode/json_old/longest_palindromic_substring.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "problem_name": "longest_palindromic_substring", - "solution_class_name": "Solution", - "problem_number": "5", - "problem_title": "Longest Palindromic Substring", - "difficulty": "Medium", - "topics": "Two Pointers, String, Dynamic Programming", - "tags": ["grind-75"], - "readme_description": "Given a string `s`, return the longest palindromic substring in `s`.", - "readme_examples": [ - { - "content": "```\nInput: s = \"babad\"\nOutput: \"bab\"\n```\n**Explanation:** \"aba\" is also a valid answer." - }, - { "content": "```\nInput: s = \"cbbd\"\nOutput: \"bb\"\n```" } - ], - "readme_constraints": "- `1 <= s.length <= 1000`\n- `s` consist of only digits and English letters.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "longest_palindrome", - "parameters": "s: str", - "return_type": "str", - "dummy_return": "\"\"" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "LongestPalindromicSubstring", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_longest_palindrome", - "parametrize": "s, expected", - "parametrize_typed": "s: str, expected: set[str]", - "test_cases": "[('babad', {'bab', 'aba'}), ('cbbd', {'bb'}), ('a', {'a'}), ('ac', {'a', 'c'}), ('racecar', {'racecar'}), ('abcdef', {'a', 'b', 'c', 'd', 'e', 'f'}), ('aabbaa', {'aabbaa'}), ('abacabad', {'abacaba'}), ('noon', {'noon'}), ('abccba', {'abccba'}), ('', {''}), ('aa', {'aa'}), ('aba', {'aba'}), ('abcba', {'abcba'}), ('forgeeksskeegfor', {'geeksskeeg'}), ('bananas', {'anana'}), ('abcdefghijklmnopqrstuvwxyz', {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'})]", - "body": "result = self.solution.longest_palindrome(s)\nassert result in expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ns = 'babad'\nexpected = {'bab', 'aba'}", - "playground_execution": "result = Solution().longest_palindrome(s)\nresult", - "playground_assertion": "assert result in expected" -} diff --git a/.templates/leetcode/json_old/longest_substring_without_repeating_characters.json b/.templates/leetcode/json_old/longest_substring_without_repeating_characters.json deleted file mode 100644 index c1c9b9c..0000000 --- a/.templates/leetcode/json_old/longest_substring_without_repeating_characters.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "longest_substring_without_repeating_characters", - "solution_class_name": "Solution", - "problem_number": "3", - "problem_title": "Longest Substring Without Repeating Characters", - "difficulty": "Medium", - "topics": "Hash Table, String, Sliding Window", - "tags": ["grind-75"], - "readme_description": "Given a string `s`, find the length of the **longest** **substring** without duplicate characters.", - "readme_examples": [ - { - "content": "```\nInput: s = \"abcabcbb\"\nOutput: 3\n```\n**Explanation:** The answer is \"abc\", with the length of 3." - }, - { - "content": "```\nInput: s = \"bbbbb\"\nOutput: 1\n```\n**Explanation:** The answer is \"b\", with the length of 1." - }, - { - "content": "```\nInput: s = \"pwwkew\"\nOutput: 3\n```\n**Explanation:** The answer is \"wke\", with the length of 3.\nNotice that the answer must be a substring, \"pwke\" is a subsequence and not a substring." - } - ], - "readme_constraints": "- 0 <= s.length <= 5 * 10^4\n- s consists of English letters, digits, symbols and spaces.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "length_of_longest_substring", - "parameters": "s: str", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "LongestSubstringWithoutRepeatingCharacters", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_length_of_longest_substring", - "parametrize": "s, expected", - "parametrize_typed": "s: str, expected: int", - "test_cases": "[('abcabcbb', 3), ('bbbbb', 1), ('pwwkew', 3), ('', 0), ('a', 1), ('au', 2), ('dvdf', 3)]", - "body": "result = self.solution.length_of_longest_substring(s)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ns = 'abcabcbb'\nexpected = 3", - "playground_execution": "result = Solution().length_of_longest_substring(s)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/lowest_common_ancestor_of_a_binary_search_tree.json b/.templates/leetcode/json_old/lowest_common_ancestor_of_a_binary_search_tree.json deleted file mode 100644 index f697b50..0000000 --- a/.templates/leetcode/json_old/lowest_common_ancestor_of_a_binary_search_tree.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "problem_name": "lowest_common_ancestor_of_a_binary_search_tree", - "solution_class_name": "Solution", - "problem_number": "235", - "problem_title": "Lowest Common Ancestor of a Binary Search Tree", - "difficulty": "Medium", - "topics": "Tree, Depth-First Search, Binary Search Tree, Binary Tree", - "tags": ["grind-75"], - "readme_description": "Given a binary search tree (BST), find the lowest common ancestor (LCA) node of two given nodes in the BST.\n\nAccording to the definition of LCA on Wikipedia: \"The lowest common ancestor is defined between two nodes `p` and `q` as the lowest node in `T` that has both `p` and `q` as descendants (where we allow **a node to be a descendant of itself**).\"", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2018/12/14/binarysearchtree_improved.png)\n\n```\nInput: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8\nOutput: 6\n```\n**Explanation:** The LCA of nodes 2 and 8 is 6." - }, - { - "content": "![Example 2](https://assets.leetcode.com/uploads/2018/12/14/binarysearchtree_improved.png)\n\n```\nInput: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4\nOutput: 2\n```\n**Explanation:** The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition." - }, - { "content": "```\nInput: root = [2,1], p = 2, q = 1\nOutput: 2\n```" } - ], - "readme_constraints": "- The number of nodes in the tree is in the range `[2, 10^5]`.\n- `-10^9 <= Node.val <= 10^9`\n- All `Node.val` are **unique**.\n- `p != q`\n- `p` and `q` will exist in the BST.", - "readme_additional": "", - "solution_imports": "from leetcode_py import TreeNode", - "solution_methods": [ - { - "name": "lowest_common_ancestor", - "parameters": "root: TreeNode[int] | None, p: TreeNode[int], q: TreeNode[int]", - "return_type": "TreeNode[int] | None", - "dummy_return": "None" - } - ], - "test_imports": "import pytest\nfrom leetcode_py import TreeNode\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "LowestCommonAncestorOfABinarySearchTree", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_lowest_common_ancestor", - "parametrize": "root_list, p_val, q_val, expected_val", - "parametrize_typed": "root_list: list[int | None], p_val: int, q_val: int, expected_val: int", - "test_cases": "[([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 8, 6), ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 4, 2), ([2, 1], 2, 1, 2), ([2, 1], 1, 2, 2), ([6, 2, 8, 0, 4, 7, 9], 0, 4, 2), ([6, 2, 8, 0, 4, 7, 9], 7, 9, 8)]", - "body": "root = TreeNode[int].from_list(root_list)\nassert root is not None\np = root.find_node(p_val)\nq = root.find_node(q_val)\nassert p is not None and q is not None\nresult = self.solution.lowest_common_ancestor(root, p, q)\nassert result is not None\nassert result.val == expected_val" - } - ], - "playground_imports": "from leetcode_py import TreeNode\nfrom solution import Solution", - "playground_test_case": "# Example test case\nroot_list = [6, 2, 8, 0, 4, 7, 9, None, None, 3, 5]\np_val = 2\nq_val = 8\nexpected_val = 6", - "playground_execution": "root = TreeNode[int].from_list(root_list)\nassert root is not None\np = root.find_node(p_val)\nq = root.find_node(q_val)\nassert p is not None and q is not None\nresult = Solution().lowest_common_ancestor(root, p, q)\nresult.val if result else None", - "playground_assertion": "assert result and result.val == expected_val" -} diff --git a/.templates/leetcode/json_old/lowest_common_ancestor_of_a_binary_tree.json b/.templates/leetcode/json_old/lowest_common_ancestor_of_a_binary_tree.json deleted file mode 100644 index 2e19705..0000000 --- a/.templates/leetcode/json_old/lowest_common_ancestor_of_a_binary_tree.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "problem_name": "lowest_common_ancestor_of_a_binary_tree", - "solution_class_name": "Solution", - "problem_number": "236", - "problem_title": "Lowest Common Ancestor of a Binary Tree", - "difficulty": "Medium", - "topics": "Tree, Depth-First Search, Binary Tree", - "tags": ["grind-75"], - "readme_description": "Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree.\n\nAccording to the definition of LCA on Wikipedia: \"The lowest common ancestor is defined between two nodes `p` and `q` as the lowest node in `T` that has both `p` and `q` as descendants (where we allow **a node to be a descendant of itself**).\"", - "readme_examples": [ - { - "content": "\"\"\n\n```\nInput: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1\nOutput: 3\nExplanation: The LCA of nodes 5 and 1 is 3.\n```" - }, - { - "content": "\"\"\n\n```\nInput: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4\nOutput: 5\nExplanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.\n```" - }, - { "content": "```\nInput: root = [1,2], p = 1, q = 2\nOutput: 1\n```" } - ], - "readme_constraints": "- The number of nodes in the tree is in the range [2, 10^5].\n- -10^9 <= Node.val <= 10^9\n- All Node.val are unique.\n- p != q\n- p and q will exist in the tree.", - "readme_additional": "", - "solution_imports": "from leetcode_py import TreeNode", - "solution_methods": [ - { - "name": "lowest_common_ancestor", - "parameters": "root: TreeNode, p: TreeNode, q: TreeNode", - "return_type": "TreeNode", - "dummy_return": "root" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution", - "test_class_name": "LowestCommonAncestorOfABinaryTree", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_lowest_common_ancestor", - "parametrize": "root_list, p_val, q_val, expected_val", - "parametrize_typed": "root_list: list[int | None], p_val: int, q_val: int, expected_val: int", - "test_cases": "[([3,5,1,6,2,0,8,None,None,7,4], 5, 1, 3), ([3,5,1,6,2,0,8,None,None,7,4], 5, 4, 5), ([1,2], 1, 2, 1)]", - "body": "root = TreeNode.from_list(root_list)\nassert root is not None\np = root.find_node(p_val)\nq = root.find_node(q_val)\nassert p is not None and q is not None\nresult = self.solution.lowest_common_ancestor(root, p, q)\nassert result is not None\nassert result.val == expected_val" - } - ], - "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode", - "playground_test_case": "# Example test case\nroot_list = [3,5,1,6,2,0,8,None,None,7,4]\nroot = TreeNode.from_list(root_list)\nassert root is not None\np = root.find_node(5)\nq = root.find_node(1)\nexpected_val = 3", - "playground_execution": "result = Solution().lowest_common_ancestor(root, p, q)\nresult.val", - "playground_assertion": "assert result.val == expected_val" -} diff --git a/.templates/leetcode/json_old/lru_cache.json b/.templates/leetcode/json_old/lru_cache.json deleted file mode 100644 index 3fe70f1..0000000 --- a/.templates/leetcode/json_old/lru_cache.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "problem_name": "lru_cache", - "solution_class_name": "LRUCache", - "problem_number": "146", - "problem_title": "LRU Cache", - "difficulty": "Medium", - "topics": "Hash Table, Linked List, Design, Doubly-Linked List", - "tags": ["grind-75"], - "readme_description": "Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.\n\nImplement the `LRUCache` class:\n\n- `LRUCache(int capacity)` Initialize the LRU cache with positive size capacity\n- `int get(int key)` Return the value of the key if the key exists, otherwise return -1\n- `void put(int key, int value)` Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key\n\nThe functions `get` and `put` must each run in `O(1)` average time complexity.", - "readme_examples": [ - { - "content": "```\nInput\n[\"LRUCache\", \"put\", \"put\", \"get\", \"put\", \"get\", \"put\", \"get\", \"get\", \"get\"]\n[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\nOutput\n[null, null, null, 1, null, -1, null, -1, 3, 4]\n\nExplanation\nLRUCache lRUCache = new LRUCache(2);\nlRUCache.put(1, 1); // cache is {1=1}\nlRUCache.put(2, 2); // cache is {1=1, 2=2}\nlRUCache.get(1); // return 1\nlRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}\nlRUCache.get(2); // returns -1 (not found)\nlRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}\nlRUCache.get(1); // return -1 (not found)\nlRUCache.get(3); // return 3\nlRUCache.get(4); // return 4\n```" - } - ], - "readme_constraints": "- 1 <= capacity <= 3000\n- 0 <= key <= 10^4\n- 0 <= value <= 10^5\n- At most 2 * 10^5 calls will be made to get and put", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { "name": "__init__", "parameters": "capacity: int", "return_type": "None", "dummy_return": "" }, - { "name": "get", "parameters": "key: int", "return_type": "int", "dummy_return": "-1" }, - { - "name": "put", - "parameters": "key: int, value: int", - "return_type": "None", - "dummy_return": "" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import LRUCache", - "test_class_name": "LRUCache", - "test_helper_methods": [], - "test_methods": [ - { - "name": "test_lru_cache", - "parametrize": "operations, inputs, expected", - "parametrize_typed": "operations: list[str], inputs: list[list[int]], expected: list[int | None]", - "test_cases": "[([\"LRUCache\", \"put\", \"put\", \"get\", \"put\", \"get\", \"put\", \"get\", \"get\", \"get\"], [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]], [None, None, None, 1, None, -1, None, -1, 3, 4])]", - "body": "cache: LRUCache | None = None\nresults: list[int | None] = []\nfor i, op in enumerate(operations):\n if op == \"LRUCache\":\n cache = LRUCache(inputs[i][0])\n results.append(None)\n elif op == \"get\" and cache is not None:\n results.append(cache.get(inputs[i][0]))\n elif op == \"put\" and cache is not None:\n cache.put(inputs[i][0], inputs[i][1])\n results.append(None)\nassert results == expected" - } - ], - "playground_imports": "from solution import LRUCache", - "playground_test_case": "# Example test case\noperations = ['LRUCache', 'put', 'put', 'get', 'put', 'get', 'put', 'get', 'get', 'get']\ninputs = [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\nexpected = [None, None, None, 1, None, -1, None, -1, 3, 4]", - "playground_execution": "cache = None\nresults: list[int | None] = []\nfor i, op in enumerate(operations):\n if op == 'LRUCache':\n cache = LRUCache(inputs[i][0])\n results.append(None)\n elif op == 'get' and cache is not None:\n results.append(cache.get(inputs[i][0]))\n elif op == 'put' and cache is not None:\n cache.put(inputs[i][0], inputs[i][1])\n results.append(None)\nresults", - "playground_assertion": "assert results == expected" -} diff --git a/.templates/leetcode/json_old/majority_element.json b/.templates/leetcode/json_old/majority_element.json deleted file mode 100644 index 135a1cf..0000000 --- a/.templates/leetcode/json_old/majority_element.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "problem_name": "majority_element", - "solution_class_name": "Solution", - "problem_number": "169", - "problem_title": "Majority Element", - "difficulty": "Easy", - "topics": "Array, Hash Table, Divide and Conquer, Sorting, Counting", - "tags": ["grind-75"], - "readme_description": "Given an array `nums` of size `n`, return the majority element.\n\nThe majority element is the element that appears more than `\u230an / 2\u230b` times. You may assume that the majority element always exists in the array.", - "readme_examples": [ - { "content": "```\nInput: nums = [3,2,3]\nOutput: 3\n```" }, - { "content": "```\nInput: nums = [2,2,1,1,1,2,2]\nOutput: 2\n```" } - ], - "readme_constraints": "- n == nums.length\n- 1 <= n <= 5 * 10^4\n- -10^9 <= nums[i] <= 10^9", - "readme_additional": "**Follow-up:** Could you solve the problem in linear time and in O(1) space?", - "solution_imports": "", - "solution_methods": [ - { - "name": "majority_element", - "parameters": "nums: list[int]", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "MajorityElement", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_majority_element", - "parametrize": "nums, expected", - "parametrize_typed": "nums: list[int], expected: int", - "test_cases": "[([3,2,3], 3), ([2,2,1,1,1,2,2], 2), ([1], 1), ([1,1,2], 1), ([2,2,2,1,1], 2)]", - "body": "result = self.solution.majority_element(nums)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnums = [3,2,3]\nexpected = 3", - "playground_execution": "result = Solution().majority_element(nums)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/maximum_depth_of_binary_tree.json b/.templates/leetcode/json_old/maximum_depth_of_binary_tree.json deleted file mode 100644 index d701c4f..0000000 --- a/.templates/leetcode/json_old/maximum_depth_of_binary_tree.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "problem_name": "maximum_depth_of_binary_tree", - "solution_class_name": "Solution", - "problem_number": "104", - "problem_title": "Maximum Depth of Binary Tree", - "difficulty": "Easy", - "topics": "Tree, Depth-First Search, Breadth-First Search, Binary Tree", - "tags": ["grind-75"], - "readme_description": "Given the `root` of a binary tree, return *its maximum depth*.\n\nA binary tree's **maximum depth** is the number of nodes along the longest path from the root node down to the farthest leaf node.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2020/11/26/tmp-tree.jpg)\n\n```\nInput: root = [3,9,20,null,null,15,7]\nOutput: 3\n```" - }, - { "content": "```\nInput: root = [1,null,2]\nOutput: 2\n```" } - ], - "readme_constraints": "- The number of nodes in the tree is in the range `[0, 10^4]`.\n- `-100 <= Node.val <= 100`", - "readme_additional": "", - "solution_imports": "from leetcode_py import TreeNode", - "solution_methods": [ - { - "name": "max_depth", - "parameters": "root: TreeNode[int] | None", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution", - "test_class_name": "MaximumDepthOfBinaryTree", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_max_depth", - "parametrize": "root_list, expected", - "parametrize_typed": "root_list: list[int | None], expected: int", - "test_cases": "[([3, 9, 20, None, None, 15, 7], 3), ([1, None, 2], 2), ([], 0)]", - "body": "root = TreeNode.from_list(root_list)\nresult = self.solution.max_depth(root)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode", - "playground_test_case": "# Example test case\nroot_list = [3, 9, 20, None, None, 15, 7]\nexpected = 3", - "playground_execution": "root = TreeNode.from_list(root_list)\nresult = Solution().max_depth(root)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/maximum_profit_in_job_scheduling.json b/.templates/leetcode/json_old/maximum_profit_in_job_scheduling.json deleted file mode 100644 index 53b2f7c..0000000 --- a/.templates/leetcode/json_old/maximum_profit_in_job_scheduling.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "maximum_profit_in_job_scheduling", - "solution_class_name": "Solution", - "problem_number": "1235", - "problem_title": "Maximum Profit in Job Scheduling", - "difficulty": "Hard", - "topics": "Array, Binary Search, Dynamic Programming, Sorting", - "tags": ["grind-75"], - "readme_description": "We have `n` jobs, where every job is scheduled to be done from `startTime[i]` to `endTime[i]`, obtaining a profit of `profit[i]`.\n\nYou're given the `startTime`, `endTime` and `profit` arrays, return the maximum profit you can take such that there are no two jobs in the subset with overlapping time range.\n\nIf you choose a job that ends at time `X` you will be able to start another job that starts at time `X`.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2019/10/10/sample1_1584.png)\n\n```\nInput: startTime = [1,2,3,3], endTime = [3,4,5,6], profit = [50,10,40,70]\nOutput: 120\n```\n**Explanation:** The subset chosen is the first and fourth job. Time range [1-3]+[3-6] , we get profit of 120 = 50 + 70." - }, - { - "content": "![Example 2](https://assets.leetcode.com/uploads/2019/10/10/sample22_1584.png)\n\n```\nInput: startTime = [1,2,3,4,6], endTime = [3,5,10,6,9], profit = [20,20,100,70,60]\nOutput: 150\n```\n**Explanation:** The subset chosen is the first, fourth and fifth job. Profit obtained 150 = 20 + 70 + 60." - }, - { - "content": "![Example 3](https://assets.leetcode.com/uploads/2019/10/10/sample3_1584.png)\n\n```\nInput: startTime = [1,1,1], endTime = [2,3,4], profit = [5,6,4]\nOutput: 6\n```" - } - ], - "readme_constraints": "- `1 <= startTime.length == endTime.length == profit.length <= 5 * 10^4`\n- `1 <= startTime[i] < endTime[i] <= 10^9`\n- `1 <= profit[i] <= 10^4`", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "job_scheduling", - "parameters": "start_time: list[int], end_time: list[int], profit: list[int]", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "MaximumProfitInJobScheduling", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_job_scheduling", - "parametrize": "start_time, end_time, profit, expected", - "parametrize_typed": "start_time: list[int], end_time: list[int], profit: list[int], expected: int", - "test_cases": "[([1, 2, 3, 3], [3, 4, 5, 6], [50, 10, 40, 70], 120), ([1, 2, 3, 4, 6], [3, 5, 10, 6, 9], [20, 20, 100, 70, 60], 150), ([1, 1, 1], [2, 3, 4], [5, 6, 4], 6)]", - "body": "result = self.solution.job_scheduling(start_time, end_time, profit)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nstart_time = [1, 2, 3, 3]\nend_time = [3, 4, 5, 6]\nprofit = [50, 10, 40, 70]\nexpected = 120", - "playground_execution": "result = Solution().job_scheduling(start_time, end_time, profit)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/maximum_subarray.json b/.templates/leetcode/json_old/maximum_subarray.json deleted file mode 100644 index 50da7c2..0000000 --- a/.templates/leetcode/json_old/maximum_subarray.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "maximum_subarray", - "solution_class_name": "Solution", - "problem_number": "53", - "problem_title": "Maximum Subarray", - "difficulty": "Medium", - "topics": "Array, Divide and Conquer, Dynamic Programming", - "tags": ["grind-75"], - "readme_description": "Given an integer array `nums`, find the subarray with the largest sum, and return its sum.", - "readme_examples": [ - { - "content": "```\nInput: nums = [-2,1,-3,4,-1,2,1,-5,4]\nOutput: 6\n```\n**Explanation:** The subarray [4,-1,2,1] has the largest sum 6." - }, - { - "content": "```\nInput: nums = [1]\nOutput: 1\n```\n**Explanation:** The subarray [1] has the largest sum 1." - }, - { - "content": "```\nInput: nums = [5,4,-1,7,8]\nOutput: 23\n```\n**Explanation:** The subarray [5,4,-1,7,8] has the largest sum 23." - } - ], - "readme_constraints": "- `1 <= nums.length <= 10^5`\n- `-10^4 <= nums[i] <= 10^4`", - "readme_additional": "**Follow up:** If you have figured out the `O(n)` solution, try coding another solution using the **divide and conquer** approach, which is more subtle.", - "solution_imports": "", - "solution_methods": [ - { - "name": "max_sub_array", - "parameters": "nums: list[int]", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "MaximumSubarray", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_max_sub_array", - "parametrize": "nums, expected", - "parametrize_typed": "nums: list[int], expected: int", - "test_cases": "[([-2, 1, -3, 4, -1, 2, 1, -5, 4], 6), ([1], 1), ([5, 4, -1, 7, 8], 23), ([-1], -1), ([-2, -1], -1), ([1, 2, 3, 4, 5], 15), ([-5, -2, -8, -1], -1)]", - "body": "result = self.solution.max_sub_array(nums)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]\nexpected = 6", - "playground_execution": "result = Solution().max_sub_array(nums)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/merge_intervals.json b/.templates/leetcode/json_old/merge_intervals.json deleted file mode 100644 index 071e1bf..0000000 --- a/.templates/leetcode/json_old/merge_intervals.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "merge_intervals", - "solution_class_name": "Solution", - "problem_number": "56", - "problem_title": "Merge Intervals", - "difficulty": "Medium", - "topics": "Array, Sorting", - "tags": ["grind-75"], - "readme_description": "Given an array of `intervals` where `intervals[i] = [starti, endi]`, merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.", - "readme_examples": [ - { - "content": "```\nInput: intervals = [[1,3],[2,6],[8,10],[15,18]]\nOutput: [[1,6],[8,10],[15,18]]\n```\n**Explanation:** Since intervals [1,3] and [2,6] overlap, merge them into [1,6]." - }, - { - "content": "```\nInput: intervals = [[1,4],[4,5]]\nOutput: [[1,5]]\n```\n**Explanation:** Intervals [1,4] and [4,5] are considered overlapping." - }, - { - "content": "```\nInput: intervals = [[4,7],[1,4]]\nOutput: [[1,7]]\n```\n**Explanation:** Intervals [1,4] and [4,7] are considered overlapping." - } - ], - "readme_constraints": "- `1 <= intervals.length <= 10^4`\n- `intervals[i].length == 2`\n- `0 <= starti <= endi <= 10^4`", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "merge", - "parameters": "intervals: list[list[int]]", - "return_type": "list[list[int]]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "MergeIntervals", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_merge", - "parametrize": "intervals, expected", - "parametrize_typed": "intervals: list[list[int]], expected: list[list[int]]", - "test_cases": "[([[1,3],[2,6],[8,10],[15,18]], [[1,6],[8,10],[15,18]]), ([[1,4],[4,5]], [[1,5]]), ([[4,7],[1,4]], [[1,7]])]", - "body": "result = self.solution.merge(intervals)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nintervals = [[1,3],[2,6],[8,10],[15,18]]\nexpected = [[1,6],[8,10],[15,18]]", - "playground_execution": "result = Solution().merge(intervals)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/merge_k_sorted_lists.json b/.templates/leetcode/json_old/merge_k_sorted_lists.json deleted file mode 100644 index f5a1e19..0000000 --- a/.templates/leetcode/json_old/merge_k_sorted_lists.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "problem_name": "merge_k_sorted_lists", - "solution_class_name": "Solution", - "problem_number": "23", - "problem_title": "Merge k Sorted Lists", - "difficulty": "Hard", - "topics": "Linked List, Divide and Conquer, Heap (Priority Queue), Merge Sort", - "tags": ["grind-75"], - "readme_description": "You are given an array of `k` linked-lists `lists`, each linked-list is sorted in ascending order.\n\n*Merge all the linked-lists into one sorted linked-list and return it.*", - "readme_examples": [ - { - "content": "```\nInput: lists = [[1,4,5],[1,3,4],[2,6]]\nOutput: [1,1,2,3,4,4,5,6]\n```\n**Explanation:** The linked-lists are:\n```\n[\n 1->4->5,\n 1->3->4,\n 2->6\n]\n```\nmerging them into one sorted linked list:\n```\n1->1->2->3->4->4->5->6\n```" - }, - { "content": "```\nInput: lists = []\nOutput: []\n```" }, - { "content": "```\nInput: lists = [[]]\nOutput: []\n```" } - ], - "readme_constraints": "- `k == lists.length`\n- `0 <= k <= 10^4`\n- `0 <= lists[i].length <= 500`\n- `-10^4 <= lists[i][j] <= 10^4`\n- `lists[i]` is sorted in ascending order.\n- The sum of `lists[i].length` will not exceed `10^4`.", - "readme_additional": "", - "solution_imports": "from leetcode_py import ListNode", - "solution_methods": [ - { - "name": "merge_k_lists", - "parameters": "lists: list[ListNode | None]", - "return_type": "ListNode | None", - "dummy_return": "None" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import ListNode\nfrom .solution import Solution", - "test_class_name": "MergeKSortedLists", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_merge_k_lists", - "parametrize": "lists_data, expected_data", - "parametrize_typed": "lists_data: list[list[int]], expected_data: list[int]", - "test_cases": "[([[1, 4, 5], [1, 3, 4], [2, 6]], [1, 1, 2, 3, 4, 4, 5, 6]), ([], []), ([[]], []), ([[1]], [1]), ([[1, 2], [3, 4]], [1, 2, 3, 4]), ([[5], [1, 3], [2, 4, 6]], [1, 2, 3, 4, 5, 6]), ([[-1, 0, 1], [-2, 2]], [-2, -1, 0, 1, 2]), ([[1, 1, 1], [2, 2, 2]], [1, 1, 1, 2, 2, 2]), ([[]], []), ([[], [1], []], [1])]", - "body": "lists = [ListNode.from_list(lst) for lst in lists_data]\nresult = self.solution.merge_k_lists(lists)\nexpected = ListNode.from_list(expected_data)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution\nfrom leetcode_py import ListNode", - "playground_test_case": "# Example test case\nlists_data = [[1, 4, 5], [1, 3, 4], [2, 6]]\nlists = [ListNode.from_list(lst) for lst in lists_data]\nexpected_data = [1, 1, 2, 3, 4, 4, 5, 6]", - "playground_execution": "result = Solution().merge_k_lists(lists)\nListNode.to_list(result) if result else []", - "playground_assertion": "expected = ListNode.from_list(expected_data)\nassert result == expected" -} diff --git a/.templates/leetcode/json_old/merge_two_sorted_lists.json b/.templates/leetcode/json_old/merge_two_sorted_lists.json deleted file mode 100644 index f00c2a1..0000000 --- a/.templates/leetcode/json_old/merge_two_sorted_lists.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "problem_name": "merge_two_sorted_lists", - "solution_class_name": "Solution", - "problem_number": "21", - "problem_title": "Merge Two Sorted Lists", - "difficulty": "Easy", - "topics": "Linked List, Recursion", - "tags": ["grind-75"], - "readme_description": "You are given the heads of two sorted linked lists `list1` and `list2`.\n\nMerge the two lists into one **sorted** list. The list should be made by splicing together the nodes of the first two lists.\n\nReturn *the head of the merged linked list*.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2020/10/03/merge_ex1.jpg)\n\n```\nInput: list1 = [1,2,4], list2 = [1,3,4]\nOutput: [1,1,2,3,4,4]\n```" - }, - { "content": "```\nInput: list1 = [], list2 = []\nOutput: []\n```" }, - { "content": "```\nInput: list1 = [], list2 = [0]\nOutput: [0]\n```" } - ], - "readme_constraints": "- The number of nodes in both lists is in the range `[0, 50]`.\n- `-100 <= Node.val <= 100`\n- Both `list1` and `list2` are sorted in **non-decreasing** order.", - "readme_additional": "", - "solution_imports": "from leetcode_py import ListNode", - "solution_methods": [ - { - "name": "merge_two_lists", - "parameters": "list1: ListNode | None, list2: ListNode | None", - "return_type": "ListNode | None", - "dummy_return": "None" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import ListNode\nfrom .solution import Solution", - "test_class_name": "MergeTwoSortedLists", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_merge_two_lists", - "parametrize": "list1_vals, list2_vals, expected_vals", - "parametrize_typed": "list1_vals: list[int], list2_vals: list[int], expected_vals: list[int]", - "test_cases": "[([1, 2, 4], [1, 3, 4], [1, 1, 2, 3, 4, 4]), ([], [], []), ([], [0], [0]), ([1], [2], [1, 2]), ([2], [1], [1, 2])]", - "body": "list1 = ListNode.from_list(list1_vals)\nlist2 = ListNode.from_list(list2_vals)\nexpected = ListNode.from_list(expected_vals)\nresult = self.solution.merge_two_lists(list1, list2)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution\nfrom leetcode_py import ListNode", - "playground_test_case": "# Example test case\nlist1_vals = [1, 2, 4]\nlist2_vals = [1, 3, 4]\nexpected_vals = [1, 1, 2, 3, 4, 4]", - "playground_execution": "list1 = ListNode.from_list(list1_vals)\nlist2 = ListNode.from_list(list2_vals)\nresult = Solution().merge_two_lists(list1, list2)\nresult", - "playground_assertion": "expected = ListNode.from_list(expected_vals)\nassert result == expected" -} diff --git a/.templates/leetcode/json_old/middle_of_the_linked_list.json b/.templates/leetcode/json_old/middle_of_the_linked_list.json deleted file mode 100644 index ac2deb4..0000000 --- a/.templates/leetcode/json_old/middle_of_the_linked_list.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "middle_of_the_linked_list", - "solution_class_name": "Solution", - "problem_number": "876", - "problem_title": "Middle of the Linked List", - "difficulty": "Easy", - "topics": "Linked List, Two Pointers", - "tags": ["grind-75"], - "readme_description": "Given the `head` of a singly linked list, return *the middle node of the linked list*.\n\nIf there are two middle nodes, return **the second middle** node.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2021/07/23/lc-midlist1.jpg)\n\n```\nInput: head = [1,2,3,4,5]\nOutput: [3,4,5]\n```\n**Explanation:** The middle node of the list is node 3." - }, - { - "content": "![Example 2](https://assets.leetcode.com/uploads/2021/07/23/lc-midlist2.jpg)\n\n```\nInput: head = [1,2,3,4,5,6]\nOutput: [4,5,6]\n```\n**Explanation:** Since the list has two middle nodes with values 3 and 4, we return the second one." - } - ], - "readme_constraints": "- The number of nodes in the list is in the range `[1, 100]`.\n- `1 <= Node.val <= 100`", - "readme_additional": "", - "solution_imports": "from leetcode_py import ListNode", - "solution_methods": [ - { - "name": "middle_node", - "parameters": "head: ListNode | None", - "return_type": "ListNode | None", - "dummy_return": "None" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import ListNode\nfrom .solution import Solution", - "test_class_name": "MiddleOfTheLinkedList", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_middle_node", - "parametrize": "head_list, expected_list", - "parametrize_typed": "head_list: list[int], expected_list: list[int]", - "test_cases": "[([1, 2, 3, 4, 5], [3, 4, 5]), ([1, 2, 3, 4, 5, 6], [4, 5, 6]), ([1], [1]), ([1, 2], [2])]", - "body": "head = ListNode.from_list(head_list)\nexpected = ListNode.from_list(expected_list)\nresult = self.solution.middle_node(head)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution\nfrom leetcode_py import ListNode", - "playground_test_case": "# Example test case\nhead_list = [1, 2, 3, 4, 5]\nexpected_list = [3, 4, 5]", - "playground_execution": "head = ListNode.from_list(head_list)\nresult = Solution().middle_node(head)\nresult", - "playground_assertion": "expected = ListNode.from_list(expected_list)\nassert result == expected" -} diff --git a/.templates/leetcode/json_old/min_stack.json b/.templates/leetcode/json_old/min_stack.json deleted file mode 100644 index 1ed8390..0000000 --- a/.templates/leetcode/json_old/min_stack.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "problem_name": "min_stack", - "solution_class_name": "MinStack", - "problem_number": "155", - "problem_title": "Min Stack", - "difficulty": "Medium", - "topics": "Stack, Design", - "tags": ["grind-75"], - "readme_description": "Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.\n\nImplement the `MinStack` class:\n\n- `MinStack()` initializes the stack object.\n- `void push(int val)` pushes the element `val` onto the stack.\n- `void pop()` removes the element on the top of the stack.\n- `int top()` gets the top element of the stack.\n- `int getMin()` retrieves the minimum element in the stack.\n\nYou must implement a solution with `O(1)` time complexity for each function.", - "readme_examples": [ - { - "content": "```\nInput\n[\"MinStack\",\"push\",\"push\",\"push\",\"getMin\",\"pop\",\"top\",\"getMin\"]\n[[],[-2],[0],[-3],[],[],[],[]]\n\nOutput\n[null,null,null,null,-3,null,0,-2]\n```\n**Explanation:**\n```\nMinStack minStack = new MinStack();\nminStack.push(-2);\nminStack.push(0);\nminStack.push(-3);\nminStack.getMin(); // return -3\nminStack.pop();\nminStack.top(); // return 0\nminStack.getMin(); // return -2\n```" - } - ], - "readme_constraints": "- `-2^31 <= val <= 2^31 - 1`\n- Methods `pop`, `top` and `getMin` operations will always be called on **non-empty** stacks.\n- At most `3 * 10^4` calls will be made to `push`, `pop`, `top`, and `getMin`.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { "name": "__init__", "parameters": "", "return_type": "None", "dummy_return": "" }, - { "name": "push", "parameters": "val: int", "return_type": "None", "dummy_return": "" }, - { "name": "pop", "parameters": "", "return_type": "None", "dummy_return": "" }, - { "name": "top", "parameters": "", "return_type": "int", "dummy_return": "0" }, - { "name": "get_min", "parameters": "", "return_type": "int", "dummy_return": "0" } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import MinStack", - "test_class_name": "MinStack", - "test_helper_methods": [], - "test_methods": [ - { - "name": "test_min_stack", - "parametrize": "operations, inputs, expected", - "parametrize_typed": "operations: list[str], inputs: list[list[int]], expected: list[int | None]", - "test_cases": "[([\"MinStack\", \"push\", \"push\", \"push\", \"getMin\", \"pop\", \"top\", \"getMin\"], [[], [-2], [0], [-3], [], [], [], []], [None, None, None, None, -3, None, 0, -2]), ([\"MinStack\", \"push\", \"top\", \"getMin\", \"pop\"], [[], [5], [], [], []], [None, None, 5, 5, None]), ([\"MinStack\", \"push\", \"push\", \"push\", \"getMin\", \"pop\", \"getMin\", \"pop\", \"getMin\"], [[], [1], [1], [2], [], [], [], [], []], [None, None, None, None, 1, None, 1, None, 1]), ([\"MinStack\", \"push\", \"push\", \"getMin\", \"push\", \"getMin\", \"pop\", \"getMin\"], [[], [3], [1], [], [0], [], [], []], [None, None, None, 1, None, 0, None, 1])]", - "body": "stack: MinStack | None = None\nresults: list[int | None] = []\nfor i, op in enumerate(operations):\n if op == \"MinStack\":\n stack = MinStack()\n results.append(None)\n elif op == \"push\" and stack is not None:\n stack.push(inputs[i][0])\n results.append(None)\n elif op == \"pop\" and stack is not None:\n stack.pop()\n results.append(None)\n elif op == \"top\" and stack is not None:\n results.append(stack.top())\n elif op == \"getMin\" and stack is not None:\n results.append(stack.get_min())\nassert results == expected" - } - ], - "playground_imports": "from solution import MinStack", - "playground_test_case": "# Example test case\noperations = ['MinStack', 'push', 'push', 'push', 'getMin', 'pop', 'top', 'getMin']\ninputs = [[], [-2], [0], [-3], [], [], [], []]", - "playground_execution": "stack = None\nresults: list[int | None] = []\nfor i, op in enumerate(operations):\n if op == 'MinStack':\n stack = MinStack()\n results.append(None)\n elif op == 'push' and stack is not None:\n stack.push(inputs[i][0])\n results.append(None)\n elif op == 'pop' and stack is not None:\n stack.pop()\n results.append(None)\n elif op == 'top' and stack is not None:\n results.append(stack.top())\n elif op == 'getMin' and stack is not None:\n results.append(stack.get_min())\nresults", - "playground_assertion": "expected = [None, None, None, None, -3, None, 0, -2]\nassert results == expected" -} diff --git a/.templates/leetcode/json_old/minimum_height_trees.json b/.templates/leetcode/json_old/minimum_height_trees.json deleted file mode 100644 index 86669a2..0000000 --- a/.templates/leetcode/json_old/minimum_height_trees.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "minimum_height_trees", - "solution_class_name": "Solution", - "problem_number": "310", - "problem_title": "Minimum Height Trees", - "difficulty": "Medium", - "topics": "Depth-First Search, Breadth-First Search, Graph, Topological Sort", - "tags": ["grind-75"], - "readme_description": "A tree is an undirected graph in which any two vertices are connected by *exactly* one path. In other words, any connected graph without simple cycles is a tree.\n\nGiven a tree of `n` nodes labelled from `0` to `n - 1`, and an array of `n - 1` `edges` where `edges[i] = [ai, bi]` indicates that there is an undirected edge between the two nodes `ai` and `bi` in the tree, you can choose any node of the tree as the root. When you select a node `x` as the root, the result tree has height `h`. Among all possible rooted trees, those with minimum height (i.e. `min(h)`) are called **minimum height trees** (MHTs).\n\nReturn *a list of all **MHTs'** root labels*. You can return the answer in **any order**.\n\nThe **height** of a rooted tree is the number of edges on the longest downward path between the root and a leaf.", - "readme_examples": [ - { - "content": "\"\"\n\n```\nInput: n = 4, edges = [[1,0],[1,2],[1,3]]\nOutput: [1]\nExplanation: As shown, the height of the tree is 1 when the root is the node with label 1 which is the only MHT.\n```" - }, - { - "content": "\"\"\n\n```\nInput: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]\nOutput: [3,4]\n```" - } - ], - "readme_constraints": "- `1 <= n <= 2 * 10^4`\n- `edges.length == n - 1`\n- `0 <= ai, bi < n`\n- `ai != bi`\n- All the pairs `(ai, bi)` are distinct.\n- The given input is **guaranteed** to be a tree and there will be **no repeated** edges.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "find_min_height_trees", - "parameters": "n: int, edges: list[list[int]]", - "return_type": "list[int]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "MinimumHeightTrees", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_find_min_height_trees", - "parametrize": "n, edges, expected", - "parametrize_typed": "n: int, edges: list[list[int]], expected: list[int]", - "test_cases": "[(4, [[1,0],[1,2],[1,3]], [1]), (6, [[3,0],[3,1],[3,2],[3,4],[5,4]], [3,4]), (1, [], [0])]", - "body": "result = self.solution.find_min_height_trees(n, edges)\nassert sorted(result) == sorted(expected)" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nn = 4\nedges = [[1,0],[1,2],[1,3]]\nexpected = [1]", - "playground_execution": "result = Solution().find_min_height_trees(n, edges)\nresult", - "playground_assertion": "assert sorted(result) == sorted(expected)" -} diff --git a/.templates/leetcode/json_old/minimum_window_substring.json b/.templates/leetcode/json_old/minimum_window_substring.json deleted file mode 100644 index a4d151b..0000000 --- a/.templates/leetcode/json_old/minimum_window_substring.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "minimum_window_substring", - "solution_class_name": "Solution", - "problem_number": "76", - "problem_title": "Minimum Window Substring", - "difficulty": "Hard", - "topics": "Hash Table, String, Sliding Window", - "tags": ["grind-75"], - "readme_description": "Given two strings `s` and `t` of lengths `m` and `n` respectively, return the **minimum window substring** of `s` such that every character in `t` (including duplicates) is included in the window. If there is no such substring, return the empty string `\"\"`.\n\nThe testcases will be generated such that the answer is unique.", - "readme_examples": [ - { - "content": "```\nInput: s = \"ADOBECODEBANC\", t = \"ABC\"\nOutput: \"BANC\"\n```\n**Explanation:** The minimum window substring \"BANC\" includes 'A', 'B', and 'C' from string t." - }, - { - "content": "```\nInput: s = \"a\", t = \"a\"\nOutput: \"a\"\n```\n**Explanation:** The entire string s is the minimum window." - }, - { - "content": "```\nInput: s = \"a\", t = \"aa\"\nOutput: \"\"\n```\n**Explanation:** Both 'a's from t must be included in the window. Since the largest window of s only has one 'a', return empty string." - } - ], - "readme_constraints": "- `m == s.length`\n- `n == t.length`\n- `1 <= m, n <= 10^5`\n- `s` and `t` consist of uppercase and lowercase English letters.", - "readme_additional": "**Follow up:** Could you find an algorithm that runs in `O(m + n)` time?", - "solution_imports": "", - "solution_methods": [ - { - "name": "min_window", - "parameters": "s: str, t: str", - "return_type": "str", - "dummy_return": "\"\"" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "MinimumWindowSubstring", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_min_window", - "parametrize": "s, t, expected", - "parametrize_typed": "s: str, t: str, expected: str", - "test_cases": "[(\"ADOBECODEBANC\", \"ABC\", \"BANC\"), (\"a\", \"a\", \"a\"), (\"a\", \"aa\", \"\")]", - "body": "result = self.solution.min_window(s, t)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ns = \\\"ADOBECODEBANC\\\"\nt = \\\"ABC\\\"\nexpected = \\\"BANC\\\"", - "playground_execution": "result = Solution().min_window(s, t)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/number_of_islands.json b/.templates/leetcode/json_old/number_of_islands.json deleted file mode 100644 index 9a08c09..0000000 --- a/.templates/leetcode/json_old/number_of_islands.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "number_of_islands", - "solution_class_name": "Solution", - "problem_number": "200", - "problem_title": "Number of Islands", - "difficulty": "Medium", - "topics": "Array, Depth-First Search, Breadth-First Search, Union Find, Matrix", - "tags": ["grind-75"], - "readme_description": "Given an `m x n` 2D binary grid `grid` which represents a map of `'1'`s (land) and `'0'`s (water), return *the number of islands*.\n\nAn **island** is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.", - "readme_examples": [ - { - "content": "```\nInput: grid = [\n [\"1\",\"1\",\"1\",\"1\",\"0\"],\n [\"1\",\"1\",\"0\",\"1\",\"0\"],\n [\"1\",\"1\",\"0\",\"0\",\"0\"],\n [\"0\",\"0\",\"0\",\"0\",\"0\"]\n]\nOutput: 1\n```" - }, - { - "content": "```\nInput: grid = [\n [\"1\",\"1\",\"0\",\"0\",\"0\"],\n [\"1\",\"1\",\"0\",\"0\",\"0\"],\n [\"0\",\"0\",\"1\",\"0\",\"0\"],\n [\"0\",\"0\",\"0\",\"1\",\"1\"]\n]\nOutput: 3\n```" - } - ], - "readme_constraints": "- `m == grid.length`\n- `n == grid[i].length`\n- `1 <= m, n <= 300`\n- `grid[i][j]` is `'0'` or `'1'`.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "num_islands", - "parameters": "grid: list[list[str]]", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "NumberOfIslands", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_num_islands", - "parametrize": "grid, expected", - "parametrize_typed": "grid: list[list[str]], expected: int", - "test_cases": "[([['1','1','1','1','0'],['1','1','0','1','0'],['1','1','0','0','0'],['0','0','0','0','0']], 1), ([['1','1','0','0','0'],['1','1','0','0','0'],['0','0','1','0','0'],['0','0','0','1','1']], 3), ([['1']], 1), ([['0']], 0), ([['1','0','1'],['0','1','0'],['1','0','1']], 5)]", - "body": "result = self.solution.num_islands(grid)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ngrid = [\n ['1','1','1','1','0'],\n ['1','1','0','1','0'],\n ['1','1','0','0','0'],\n ['0','0','0','0','0']\n]\nexpected = 1", - "playground_execution": "result = Solution().num_islands(grid)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/partition_equal_subset_sum.json b/.templates/leetcode/json_old/partition_equal_subset_sum.json deleted file mode 100644 index 4f91312..0000000 --- a/.templates/leetcode/json_old/partition_equal_subset_sum.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "partition_equal_subset_sum", - "solution_class_name": "Solution", - "problem_number": "416", - "problem_title": "Partition Equal Subset Sum", - "difficulty": "Medium", - "topics": "Array, Dynamic Programming", - "tags": ["grind-75"], - "readme_description": "Given an integer array `nums`, return `true` if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or `false` otherwise.", - "readme_examples": [ - { - "content": "```\nInput: nums = [1,5,11,5]\nOutput: true\n```\n**Explanation:** The array can be partitioned as [1, 5, 5] and [11]." - }, - { - "content": "```\nInput: nums = [1,2,3,5]\nOutput: false\n```\n**Explanation:** The array cannot be partitioned into equal sum subsets." - } - ], - "readme_constraints": "- 1 <= nums.length <= 200\n- 1 <= nums[i] <= 100", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "can_partition", - "parameters": "nums: list[int]", - "return_type": "bool", - "dummy_return": "False" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "PartitionEqualSubsetSum", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_can_partition", - "parametrize": "nums, expected", - "parametrize_typed": "nums: list[int], expected: bool", - "test_cases": "[([1, 5, 11, 5], True), ([1, 2, 3, 5], False), ([1, 1], True), ([1], False), ([2, 2, 1, 1], True)]", - "body": "result = self.solution.can_partition(nums)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnums = [1, 5, 11, 5]\nexpected = True", - "playground_execution": "result = Solution().can_partition(nums)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/permutations.json b/.templates/leetcode/json_old/permutations.json deleted file mode 100644 index cc4f436..0000000 --- a/.templates/leetcode/json_old/permutations.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "problem_name": "permutations", - "solution_class_name": "Solution", - "problem_number": "46", - "problem_title": "Permutations", - "difficulty": "Medium", - "topics": "Array, Backtracking", - "tags": ["grind-75"], - "readme_description": "Given an array `nums` of distinct integers, return all the possible permutations. You can return the answer in any order.", - "readme_examples": [ - { - "content": "```\nInput: nums = [1,2,3]\nOutput: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]\n```" - }, - { "content": "```\nInput: nums = [0,1]\nOutput: [[0,1],[1,0]]\n```" }, - { "content": "```\nInput: nums = [1]\nOutput: [[1]]\n```" } - ], - "readme_constraints": "- 1 <= nums.length <= 6\n- -10 <= nums[i] <= 10\n- All the integers of nums are unique.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "permute", - "parameters": "nums: list[int]", - "return_type": "list[list[int]]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "TestPermutations", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_permute", - "parametrize": "nums, expected", - "parametrize_typed": "nums: list[int], expected: list[list[int]]", - "test_cases": "[([1, 2, 3], [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]), ([0, 1], [[0, 1], [1, 0]]), ([1], [[1]])]", - "body": "result = self.solution.permute(nums)\n # Sort both result and expected for comparison since order doesn't matter\n result_sorted = [sorted(perm) for perm in result]\n expected_sorted = [sorted(perm) for perm in expected]\n result_sorted.sort()\n expected_sorted.sort()\n assert len(result) == len(expected)\n assert result_sorted == expected_sorted" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnums = [1, 2, 3]\nexpected = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]", - "playground_execution": "result = Solution().permute(nums)\nresult", - "playground_assertion": "# Check that we have the right number of permutations\nassert len(result) == len(expected)\n# Sort for comparison since order doesn't matter\nresult_sorted = [sorted(perm) for perm in result]\nexpected_sorted = [sorted(perm) for perm in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted" -} diff --git a/.templates/leetcode/json_old/product_of_array_except_self.json b/.templates/leetcode/json_old/product_of_array_except_self.json deleted file mode 100644 index 1df2c43..0000000 --- a/.templates/leetcode/json_old/product_of_array_except_self.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "problem_name": "product_of_array_except_self", - "solution_class_name": "Solution", - "problem_number": "238", - "problem_title": "Product of Array Except Self", - "difficulty": "Medium", - "topics": "Array, Prefix Sum", - "tags": ["grind-75"], - "readme_description": "Given an integer array `nums`, return an array `answer` such that `answer[i]` is equal to the product of all the elements of `nums` except `nums[i]`.\n\nThe product of any prefix or suffix of `nums` is guaranteed to fit in a 32-bit integer.\n\nYou must write an algorithm that runs in O(n) time and without using the division operation.", - "readme_examples": [ - { "content": "```\nInput: nums = [1,2,3,4]\nOutput: [24,12,8,6]\n```" }, - { "content": "```\nInput: nums = [-1,1,0,-3,3]\nOutput: [0,0,9,0,0]\n```" } - ], - "readme_constraints": "- 2 <= nums.length <= 10^5\n- -30 <= nums[i] <= 30\n- The input is generated such that answer[i] is guaranteed to fit in a 32-bit integer.", - "readme_additional": "**Follow up:** Can you solve the problem in O(1) extra space complexity? (The output array does not count as extra space for space complexity analysis.)", - "solution_imports": "", - "solution_methods": [ - { - "name": "product_except_self", - "parameters": "nums: list[int]", - "return_type": "list[int]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "ProductOfArrayExceptSelf", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_product_except_self", - "parametrize": "nums, expected", - "parametrize_typed": "nums: list[int], expected: list[int]", - "test_cases": "[([1, 2, 3, 4], [24, 12, 8, 6]), ([-1, 1, 0, -3, 3], [0, 0, 9, 0, 0]), ([2, 3, 4, 5], [60, 40, 30, 24])]", - "body": "result = self.solution.product_except_self(nums)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnums = [1, 2, 3, 4]\nexpected = [24, 12, 8, 6]", - "playground_execution": "result = Solution().product_except_self(nums)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/ransom_note.json b/.templates/leetcode/json_old/ransom_note.json deleted file mode 100644 index 013a6da..0000000 --- a/.templates/leetcode/json_old/ransom_note.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "problem_name": "ransom_note", - "solution_class_name": "Solution", - "problem_number": "383", - "problem_title": "Ransom Note", - "difficulty": "Easy", - "topics": "Hash Table, String, Counting", - "tags": ["grind-75"], - "readme_description": "Given two strings `ransomNote` and `magazine`, return `true` if `ransomNote` can be constructed by using the letters from `magazine` and `false` otherwise.\n\nEach letter in `magazine` can only be used once in `ransomNote`.", - "readme_examples": [ - { "content": "```\nInput: ransomNote = \"a\", magazine = \"b\"\nOutput: false\n```" }, - { "content": "```\nInput: ransomNote = \"aa\", magazine = \"ab\"\nOutput: false\n```" }, - { "content": "```\nInput: ransomNote = \"aa\", magazine = \"aab\"\nOutput: true\n```" } - ], - "readme_constraints": "- 1 <= ransomNote.length, magazine.length <= 10^5\n- ransomNote and magazine consist of lowercase English letters.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "can_construct", - "parameters": "ransom_note: str, magazine: str", - "return_type": "bool", - "dummy_return": "False" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "RansomNote", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_can_construct", - "parametrize": "ransom_note, magazine, expected", - "parametrize_typed": "ransom_note: str, magazine: str, expected: bool", - "test_cases": "[('a', 'b', False), ('aa', 'ab', False), ('aa', 'aab', True), ('aab', 'baa', True)]", - "body": "result = self.solution.can_construct(ransom_note, magazine)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nransom_note = 'aa'\nmagazine = 'aab'\nexpected = True", - "playground_execution": "result = Solution().can_construct(ransom_note, magazine)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/reverse_linked_list.json b/.templates/leetcode/json_old/reverse_linked_list.json deleted file mode 100644 index 2c036c2..0000000 --- a/.templates/leetcode/json_old/reverse_linked_list.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "problem_name": "reverse_linked_list", - "solution_class_name": "Solution", - "problem_number": "206", - "problem_title": "Reverse Linked List", - "difficulty": "Easy", - "topics": "Linked List, Recursion", - "tags": ["grind-75"], - "readme_description": "Given the `head` of a singly linked list, reverse the list, and return the reversed list.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2021/02/19/rev1ex1.jpg)\n\n```\nInput: head = [1,2,3,4,5]\nOutput: [5,4,3,2,1]\n```" - }, - { - "content": "![Example 2](https://assets.leetcode.com/uploads/2021/02/19/rev1ex2.jpg)\n\n```\nInput: head = [1,2]\nOutput: [2,1]\n```" - }, - { "content": "```\nInput: head = []\nOutput: []\n```" } - ], - "readme_constraints": "- The number of nodes in the list is the range `[0, 5000]`.\n- `-5000 <= Node.val <= 5000`", - "readme_additional": "**Follow up:** A linked list can be reversed either iteratively or recursively. Could you implement both?", - "solution_imports": "from leetcode_py import ListNode", - "solution_methods": [ - { - "name": "reverse_list", - "parameters": "head: ListNode[int] | None", - "return_type": "ListNode[int] | None", - "dummy_return": "None" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import ListNode\nfrom .solution import Solution", - "test_class_name": "ReverseLinkedList", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_reverse_list", - "parametrize": "head_list, expected_list", - "parametrize_typed": "head_list: list[int], expected_list: list[int]", - "test_cases": "[([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]), ([1, 2], [2, 1]), ([1], [1]), ([], []), ([1, 2, 3], [3, 2, 1]), ([1, 2, 3, 4], [4, 3, 2, 1]), ([-1, -2, -3], [-3, -2, -1]), ([0], [0]), ([5000, -5000], [-5000, 5000]), ([1, 1, 1], [1, 1, 1])]", - "body": "head = ListNode.from_list(head_list)\nexpected = ListNode.from_list(expected_list)\nresult = self.solution.reverse_list(head)\nassert result == expected" - } - ], - "playground_imports": "from leetcode_py import ListNode\nfrom solution import Solution", - "playground_test_case": "# Example test case\nhead_list = [1, 2, 3, 4, 5]\nexpected_list = [5, 4, 3, 2, 1]\nhead = ListNode.from_list(head_list)\nexpected = ListNode.from_list(expected_list)", - "playground_execution": "result = Solution().reverse_list(head)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/reverse_linked_list_ii.json b/.templates/leetcode/json_old/reverse_linked_list_ii.json deleted file mode 100644 index 4eeb724..0000000 --- a/.templates/leetcode/json_old/reverse_linked_list_ii.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "problem_name": "reverse_linked_list_ii", - "solution_class_name": "Solution", - "problem_number": "92", - "problem_title": "Reverse Linked List II", - "difficulty": "Medium", - "topics": "Linked List", - "tags": [], - "readme_description": "Given the `head` of a singly linked list and two integers `left` and `right` where `left <= right`, reverse the nodes of the list from position `left` to position `right`, and return the reversed list.", - "readme_examples": [ - { "content": "```\nInput: head = [1,2,3,4,5], left = 2, right = 4\nOutput: [1,4,3,2,5]\n```" }, - { "content": "```\nInput: head = [5], left = 1, right = 1\nOutput: [5]\n```" } - ], - "readme_constraints": "- The number of nodes in the list is n\n- 1 <= n <= 500\n- -500 <= Node.val <= 500\n- 1 <= left <= right <= n", - "readme_additional": "**Follow up:** Could you do it in one pass?", - "solution_imports": "from leetcode_py import ListNode", - "solution_methods": [ - { - "name": "reverse_between", - "parameters": "head: ListNode[int] | None, left: int, right: int", - "return_type": "ListNode[int] | None", - "dummy_return": "None" - } - ], - "test_imports": "import pytest\n\nfrom leetcode_py import ListNode\nfrom leetcode_py.test_utils import logged_test\n\nfrom .solution import Solution", - "test_class_name": "ReverseLinkedListII", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_reverse_between", - "parametrize": "head_list, left, right, expected_list", - "parametrize_typed": "head_list: list[int], left: int, right: int, expected_list: list[int]", - "test_cases": "[([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]), ([5], 1, 1, [5])]", - "body": "head = ListNode[int].from_list(head_list)\nexpected = ListNode[int].from_list(expected_list)\nresult = self.solution.reverse_between(head, left, right)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution\n\nfrom leetcode_py import ListNode", - "playground_test_case": "# Example test case\nhead_list = [1, 2, 3, 4, 5]\nhead = ListNode[int].from_list(head_list)\nleft, right = 2, 4\nexpected = ListNode[int].from_list([1, 4, 3, 2, 5])", - "playground_execution": "result = Solution().reverse_between(head, left, right)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/rotting_oranges.json b/.templates/leetcode/json_old/rotting_oranges.json deleted file mode 100644 index 2467648..0000000 --- a/.templates/leetcode/json_old/rotting_oranges.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "rotting_oranges", - "solution_class_name": "Solution", - "problem_number": "994", - "problem_title": "Rotting Oranges", - "difficulty": "Medium", - "topics": "Array, Breadth-First Search, Matrix", - "tags": ["grind-75"], - "readme_description": "You are given an `m x n` `grid` where each cell can have one of three values:\n\n- `0` representing an empty cell,\n- `1` representing a fresh orange, or\n- `2` representing a rotten orange.\n\nEvery minute, any fresh orange that is **4-directionally adjacent** to a rotten orange becomes rotten.\n\nReturn *the minimum number of minutes that must elapse until no cell has a fresh orange*. If *this is impossible, return* `-1`.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2019/02/16/oranges.png)\n\n```\nInput: grid = [[2,1,1],[1,1,0],[0,1,1]]\nOutput: 4\n```" - }, - { - "content": "```\nInput: grid = [[2,1,1],[0,1,1],[1,0,1]]\nOutput: -1\n```\n**Explanation:** The orange in the bottom left corner (row 2, column 0) is never rotten, because rotting only happens 4-directionally." - }, - { - "content": "```\nInput: grid = [[0,2]]\nOutput: 0\n```\n**Explanation:** Since there are already no fresh oranges at minute 0, the answer is just 0." - } - ], - "readme_constraints": "- `m == grid.length`\n- `n == grid[i].length`\n- `1 <= m, n <= 10`\n- `grid[i][j]` is `0`, `1`, or `2`.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "oranges_rotting", - "parameters": "grid: list[list[int]]", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "TestRottingOranges", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_oranges_rotting", - "parametrize": "grid, expected", - "parametrize_typed": "grid: list[list[int]], expected: int", - "test_cases": "[([[2, 1, 1], [1, 1, 0], [0, 1, 1]], 4), ([[2, 1, 1], [0, 1, 1], [1, 0, 1]], -1), ([[0, 2]], 0), ([[0]], 0), ([[1]], -1), ([[2]], 0), ([[1, 2]], 1), ([[2, 1]], 1), ([[0, 1, 2]], 1), ([[2, 2], [1, 1], [0, 0]], 1), ([[2, 1, 1], [1, 1, 1], [0, 1, 2]], 2)]", - "body": "result = self.solution.oranges_rotting(grid)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ngrid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]\nexpected = 4", - "playground_execution": "result = Solution().oranges_rotting(grid)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/search_in_rotated_sorted_array.json b/.templates/leetcode/json_old/search_in_rotated_sorted_array.json deleted file mode 100644 index 1235416..0000000 --- a/.templates/leetcode/json_old/search_in_rotated_sorted_array.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "problem_name": "search_in_rotated_sorted_array", - "solution_class_name": "Solution", - "problem_number": "33", - "problem_title": "Search in Rotated Sorted Array", - "difficulty": "Medium", - "topics": "Array, Binary Search", - "tags": ["grind-75"], - "readme_description": "There is an integer array `nums` sorted in ascending order (with **distinct** values).\n\nPrior to being passed to your function, `nums` is **possibly left rotated** at an unknown index `k` (`1 <= k < nums.length`) such that the resulting array is `[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]` (**0-indexed**). For example, `[0,1,2,4,5,6,7]` might be left rotated by 3 indices and become `[4,5,6,7,0,1,2]`.\n\nGiven the array `nums` **after** the possible rotation and an integer `target`, return *the index of* `target` *if it is in* `nums`*, or* `-1` *if it is not in* `nums`.\n\nYou must write an algorithm with `O(log n)` runtime complexity.", - "readme_examples": [ - { "content": "```\nInput: nums = [4,5,6,7,0,1,2], target = 0\nOutput: 4\n```" }, - { "content": "```\nInput: nums = [4,5,6,7,0,1,2], target = 3\nOutput: -1\n```" }, - { "content": "```\nInput: nums = [1], target = 0\nOutput: -1\n```" } - ], - "readme_constraints": "- `1 <= nums.length <= 5000`\n- `-10^4 <= nums[i] <= 10^4`\n- All values of `nums` are **unique**.\n- `nums` is an ascending array that is possibly rotated.\n- `-10^4 <= target <= 10^4`", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "search", - "parameters": "nums: list[int], target: int", - "return_type": "int", - "dummy_return": "-1" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "SearchInRotatedSortedArray", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_search", - "parametrize": "nums, target, expected", - "parametrize_typed": "nums: list[int], target: int, expected: int", - "test_cases": "[([4, 5, 6, 7, 0, 1, 2], 0, 4), ([4, 5, 6, 7, 0, 1, 2], 3, -1), ([1], 0, -1), ([1], 1, 0), ([3, 1], 1, 1)]", - "body": "result = self.solution.search(nums, target)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnums = [4, 5, 6, 7, 0, 1, 2]\ntarget = 0\nexpected = 4", - "playground_execution": "result = Solution().search(nums, target)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/serialize_and_deserialize_binary_tree.json b/.templates/leetcode/json_old/serialize_and_deserialize_binary_tree.json deleted file mode 100644 index 36ae36f..0000000 --- a/.templates/leetcode/json_old/serialize_and_deserialize_binary_tree.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "problem_name": "serialize_and_deserialize_binary_tree", - "solution_class_name": "Codec", - "problem_number": "297", - "problem_title": "Serialize and Deserialize Binary Tree", - "difficulty": "Hard", - "topics": "String, Tree, Depth-First Search, Breadth-First Search, Design, Binary Tree", - "tags": ["grind-75"], - "readme_description": "Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment.\n\nDesign an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.\n\n**Clarification:** The input/output format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2020/09/15/serdeser.jpg)\n\n```\nInput: root = [1,2,3,null,null,4,5]\nOutput: [1,2,3,null,null,4,5]\n```" - }, - { "content": "```\nInput: root = []\nOutput: []\n```" } - ], - "readme_constraints": "- The number of nodes in the tree is in the range [0, 10^4].\n- -1000 <= Node.val <= 1000", - "readme_additional": "", - "solution_imports": "from leetcode_py import TreeNode", - "solution_methods": [ - { "name": "__init__", "parameters": "", "return_type": "", "dummy_return": "" }, - { - "name": "serialize", - "parameters": "root: TreeNode | None", - "return_type": "str", - "dummy_return": "''" - }, - { - "name": "deserialize", - "parameters": "data: str", - "return_type": "TreeNode | None", - "dummy_return": "None" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Codec", - "test_class_name": "SerializeAndDeserializeBinaryTree", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.codec = Codec()" } - ], - "test_methods": [ - { - "name": "test_serialize_deserialize", - "parametrize": "root_list", - "parametrize_typed": "root_list: list[int | None]", - "test_cases": "[([1, 2, 3, None, None, 4, 5]), ([]), ([1]), ([1, 2]), ([1, None, 2]), ([1, 2, 3, 4, 5, 6, 7]), ([5, 2, 3, None, None, 2, 4, 3, 1])]", - "body": "root = TreeNode.from_list(root_list) if root_list else None\nserialized = self.codec.serialize(root)\ndeserialized = self.codec.deserialize(serialized)\nif root is None:\n assert deserialized is None\nelse:\n assert deserialized is not None\n assert deserialized.to_list() == root.to_list()" - } - ], - "playground_imports": "from solution import Codec\nfrom leetcode_py import TreeNode", - "playground_test_case": "# Example test case\nroot_list = [1, 2, 3, None, None, 4, 5]\nroot = TreeNode.from_list(root_list) if root_list else None", - "playground_execution": "codec = Codec()\nserialized = codec.serialize(root)\ndeserialized = codec.deserialize(serialized)\nprint(f'Original: {root.to_list() if root else None}')\nprint(f'Serialized: {serialized}')\nprint(f'Deserialized: {deserialized.to_list() if deserialized else None}')\ndeserialized", - "playground_assertion": "if root is None:\n assert deserialized is None\nelse:\n assert deserialized is not None\n assert deserialized.to_list() == root.to_list()" -} diff --git a/.templates/leetcode/json_old/sort_colors.json b/.templates/leetcode/json_old/sort_colors.json deleted file mode 100644 index 0cca3e8..0000000 --- a/.templates/leetcode/json_old/sort_colors.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "problem_name": "sort_colors", - "solution_class_name": "Solution", - "problem_number": "75", - "problem_title": "Sort Colors", - "difficulty": "Medium", - "topics": "Array, Two Pointers, Sorting", - "tags": ["grind-75"], - "readme_description": "Given an array `nums` with `n` objects colored red, white, or blue, sort them **in-place** so that objects of the same color are adjacent, with the colors in the order red, white, and blue.\n\nWe will use the integers `0`, `1`, and `2` to represent the color red, white, and blue, respectively.\n\nYou must solve this problem without using the library's sort function.", - "readme_examples": [ - { "content": "```\nInput: nums = [2,0,2,1,1,0]\nOutput: [0,0,1,1,2,2]\n```" }, - { "content": "```\nInput: nums = [2,0,1]\nOutput: [0,1,2]\n```" } - ], - "readme_constraints": "- `n == nums.length`\n- `1 <= n <= 300`\n- `nums[i]` is either `0`, `1`, or `2`.", - "readme_additional": "**Follow up:** Could you come up with a one-pass algorithm using only constant extra space?", - "solution_imports": "", - "solution_methods": [ - { - "name": "sort_colors", - "parameters": "nums: list[int]", - "return_type": "None", - "dummy_return": "" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "SortColors", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_sort_colors", - "parametrize": "nums, expected", - "parametrize_typed": "nums: list[int], expected: list[int]", - "test_cases": "[([2, 0, 2, 1, 1, 0], [0, 0, 1, 1, 2, 2]), ([2, 0, 1], [0, 1, 2]), ([0], [0]), ([1], [1]), ([2], [2]), ([0, 1, 2], [0, 1, 2])]", - "body": "nums_copy = nums.copy()\nself.solution.sort_colors(nums_copy)\nassert nums_copy == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnums = [2, 0, 2, 1, 1, 0]\nexpected = [0, 0, 1, 1, 2, 2]", - "playground_execution": "nums_copy = nums.copy()\nSolution().sort_colors(nums_copy)\nnums_copy", - "playground_assertion": "assert nums_copy == expected" -} diff --git a/.templates/leetcode/json_old/spiral_matrix.json b/.templates/leetcode/json_old/spiral_matrix.json deleted file mode 100644 index 35efad1..0000000 --- a/.templates/leetcode/json_old/spiral_matrix.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "spiral_matrix", - "solution_class_name": "Solution", - "problem_number": "54", - "problem_title": "Spiral Matrix", - "difficulty": "Medium", - "topics": "Array, Matrix, Simulation", - "tags": ["grind-75"], - "readme_description": "Given an `m x n` matrix, return all elements of the matrix in spiral order.", - "readme_examples": [ - { - "content": "\"\"\n\n```\nInput: matrix = [[1,2,3],[4,5,6],[7,8,9]]\nOutput: [1,2,3,6,9,8,7,4,5]\n```" - }, - { - "content": "\"\"\n\n```\nInput: matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]\nOutput: [1,2,3,4,8,12,11,10,9,5,6,7]\n```" - } - ], - "readme_constraints": "- m == matrix.length\n- n == matrix[i].length\n- 1 <= m, n <= 10\n- -100 <= matrix[i][j] <= 100", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "spiral_order", - "parameters": "matrix: list[list[int]]", - "return_type": "list[int]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "SpiralMatrix", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_spiral_order", - "parametrize": "matrix, expected", - "parametrize_typed": "matrix: list[list[int]], expected: list[int]", - "test_cases": "[([[1,2,3],[4,5,6],[7,8,9]], [1,2,3,6,9,8,7,4,5]), ([[1,2,3,4],[5,6,7,8],[9,10,11,12]], [1,2,3,4,8,12,11,10,9,5,6,7])]", - "body": "result = self.solution.spiral_order(matrix)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nmatrix = [[1,2,3],[4,5,6],[7,8,9]]\nexpected = [1,2,3,6,9,8,7,4,5]", - "playground_execution": "result = Solution().spiral_order(matrix)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/string_to_integer_atoi.json b/.templates/leetcode/json_old/string_to_integer_atoi.json deleted file mode 100644 index 5e54670..0000000 --- a/.templates/leetcode/json_old/string_to_integer_atoi.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "problem_name": "string_to_integer_atoi", - "solution_class_name": "Solution", - "problem_number": "8", - "problem_title": "String to Integer (atoi)", - "difficulty": "Medium", - "topics": "String", - "tags": ["grind-75"], - "readme_description": "Implement the `my_atoi(string s)` function, which converts a string to a 32-bit signed integer.\n\nThe algorithm for `my_atoi(string s)` is as follows:\n\n1. **Whitespace**: Ignore any leading whitespace (` `).\n2. **Signedness**: Determine the sign by checking if the next character is `-` or `+`, assuming positivity if neither present.\n3. **Conversion**: Read the integer by skipping leading zeros until a non-digit character is encountered or the end of the string is reached. If no digits were read, then the result is 0.\n4. **Rounding**: If the integer is out of the 32-bit signed integer range `[-2^31, 2^31 - 1]`, then round the integer to remain in the range. Specifically, integers less than `-2^31` should be rounded to `-2^31`, and integers greater than `2^31 - 1` should be rounded to `2^31 - 1`.\n\nReturn the integer as the final result.", - "readme_examples": [ - { - "content": "```\nInput: s = \"42\"\nOutput: 42\n```\n**Explanation:**\n```\nThe underlined characters are what is read in and the caret is the current reader position.\nStep 1: \"42\" (no characters read because there is no leading whitespace)\n ^\nStep 2: \"42\" (no characters read because there is neither a '-' nor '+')\n ^\nStep 3: \"42\" (\"42\" is read in)\n ^\n```" - }, - { - "content": "```\nInput: s = \" -042\"\nOutput: -42\n```\n**Explanation:**\n```\nStep 1: \" -042\" (leading whitespace is read and ignored)\n ^\nStep 2: \" -042\" ('-' is read, so the result should be negative)\n ^\nStep 3: \" -042\" (\"042\" is read in, leading zeros ignored in the result)\n ^\n```" - }, - { - "content": "```\nInput: s = \"1337c0d3\"\nOutput: 1337\n```\n**Explanation:**\n```\nStep 1: \"1337c0d3\" (no characters read because there is no leading whitespace)\n ^\nStep 2: \"1337c0d3\" (no characters read because there is neither a '-' nor '+')\n ^\nStep 3: \"1337c0d3\" (\"1337\" is read in; reading stops because the next character is a non-digit)\n ^\n```" - }, - { - "content": "```\nInput: s = \"0-1\"\nOutput: 0\n```\n**Explanation:**\n```\nStep 1: \"0-1\" (no characters read because there is no leading whitespace)\n ^\nStep 2: \"0-1\" (no characters read because there is neither a '-' nor '+')\n ^\nStep 3: \"0-1\" (\"0\" is read in; reading stops because the next character is a non-digit)\n ^\n```" - }, - { - "content": "```\nInput: s = \"words and 987\"\nOutput: 0\n```\n**Explanation:** Reading stops at the first non-digit character 'w'." - } - ], - "readme_constraints": "- `0 <= s.length <= 200`\n- `s` consists of English letters (lower-case and upper-case), digits (0-9), ` `, `+`, `-`, and `.`.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { "name": "my_atoi", "parameters": "s: str", "return_type": "int", "dummy_return": "0" } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "StringToIntegerAtoi", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_my_atoi", - "parametrize": "s, expected", - "parametrize_typed": "s: str, expected: int", - "test_cases": "[('42', 42), (' -042', -42), ('1337c0d3', 1337), ('0-1', 0), ('words and 987', 0), ('', 0), (' ', 0), ('+1', 1), ('-1', -1), ('2147483647', 2147483647), ('-2147483648', -2147483648), ('2147483648', 2147483647), ('-2147483649', -2147483648)]", - "body": "result = self.solution.my_atoi(s)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ns = '42'\nexpected = 42", - "playground_execution": "result = Solution().my_atoi(s)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/task_scheduler.json b/.templates/leetcode/json_old/task_scheduler.json deleted file mode 100644 index aa7481b..0000000 --- a/.templates/leetcode/json_old/task_scheduler.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "task_scheduler", - "solution_class_name": "Solution", - "problem_number": "621", - "problem_title": "Task Scheduler", - "difficulty": "Medium", - "topics": "Array, Hash Table, Greedy, Sorting, Heap (Priority Queue), Counting", - "tags": ["grind-75"], - "readme_description": "You are given an array of CPU `tasks`, each labeled with a letter from A to Z, and a number `n`. Each CPU interval can be idle or allow the completion of one task. Tasks can be completed in any order, but there's a constraint: there has to be a gap of **at least** `n` intervals between two tasks with the same label.\n\nReturn the **minimum** number of CPU intervals required to complete all tasks.", - "readme_examples": [ - { - "content": "```\nInput: tasks = [\"A\",\"A\",\"A\",\"B\",\"B\",\"B\"], n = 2\nOutput: 8\n```\n**Explanation:** A possible sequence is: A -> B -> idle -> A -> B -> idle -> A -> B.\n\nAfter completing task A, you must wait two intervals before doing A again. The same applies to task B. In the 3rd interval, neither A nor B can be done, so you idle. By the 4th interval, you can do A again as 2 intervals have passed." - }, - { - "content": "```\nInput: tasks = [\"A\",\"C\",\"A\",\"B\",\"D\",\"B\"], n = 1\nOutput: 6\n```\n**Explanation:** A possible sequence is: A -> B -> C -> D -> A -> B.\n\nWith a cooling interval of 1, you can repeat a task after just one other task." - }, - { - "content": "```\nInput: tasks = [\"A\",\"A\",\"A\", \"B\",\"B\",\"B\"], n = 3\nOutput: 10\n```\n**Explanation:** A possible sequence is: A -> B -> idle -> idle -> A -> B -> idle -> idle -> A -> B.\n\nThere are only two types of tasks, A and B, which need to be separated by 3 intervals. This leads to idling twice between repetitions of these tasks." - } - ], - "readme_constraints": "- `1 <= tasks.length <= 10^4`\n- `tasks[i]` is an uppercase English letter.\n- `0 <= n <= 100`", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "least_interval", - "parameters": "tasks: list[str], n: int", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "TaskScheduler", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_least_interval", - "parametrize": "tasks, n, expected", - "parametrize_typed": "tasks: list[str], n: int, expected: int", - "test_cases": "[([\"A\", \"A\", \"A\", \"B\", \"B\", \"B\"], 2, 8), ([\"A\", \"C\", \"A\", \"B\", \"D\", \"B\"], 1, 6), ([\"A\", \"A\", \"A\", \"B\", \"B\", \"B\"], 3, 10)]", - "body": "result = self.solution.least_interval(tasks, n)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ntasks = [\\\"A\\\", \\\"A\\\", \\\"A\\\", \\\"B\\\", \\\"B\\\", \\\"B\\\"]\nn = 2\nexpected = 8", - "playground_execution": "result = Solution().least_interval(tasks, n)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/three_sum.json b/.templates/leetcode/json_old/three_sum.json deleted file mode 100644 index 3c161a0..0000000 --- a/.templates/leetcode/json_old/three_sum.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "three_sum", - "solution_class_name": "Solution", - "problem_number": "15", - "problem_title": "3Sum", - "difficulty": "Medium", - "topics": "Array, Two Pointers, Sorting", - "tags": ["grind-75"], - "readme_description": "Given an integer array `nums`, return all the triplets `[nums[i], nums[j], nums[k]]` such that `i != j`, `i != k`, and `j != k`, and `nums[i] + nums[j] + nums[k] == 0`.\n\nNotice that the solution set must not contain duplicate triplets.", - "readme_examples": [ - { - "content": "```\nInput: nums = [-1,0,1,2,-1,-4]\nOutput: [[-1,-1,2],[-1,0,1]]\n```\n**Explanation:** \nnums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0.\nnums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0.\nnums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0.\nThe distinct triplets are [-1,0,1] and [-1,-1,2].\nNotice that the order of the output and the order of the triplets does not matter." - }, - { - "content": "```\nInput: nums = [0,1,1]\nOutput: []\n```\n**Explanation:** The only possible triplet does not sum up to 0." - }, - { - "content": "```\nInput: nums = [0,0,0]\nOutput: [[0,0,0]]\n```\n**Explanation:** The only possible triplet sums up to 0." - } - ], - "readme_constraints": "- 3 <= nums.length <= 3000\n- -10^5 <= nums[i] <= 10^5", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "three_sum", - "parameters": "nums: list[int]", - "return_type": "list[list[int]]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "ThreeSum", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_three_sum", - "parametrize": "nums, expected", - "parametrize_typed": "nums: list[int], expected: list[list[int]]", - "test_cases": "[([-1, 0, 1, 2, -1, -4], [[-1, -1, 2], [-1, 0, 1]]), ([0, 1, 1], []), ([0, 0, 0], [[0, 0, 0]]), ([-1, 0, 1], [[-1, 0, 1]]), ([1, 2, -2, -1], []), ([-2, 0, 1, 1, 2], [[-2, 0, 2], [-2, 1, 1]])]", - "body": "result = self.solution.three_sum(nums)\n# Sort both result and expected for comparison since order doesn't matter\nresult_sorted = [sorted(triplet) for triplet in result]\nexpected_sorted = [sorted(triplet) for triplet in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nnums = [-1, 0, 1, 2, -1, -4]\nexpected = [[-1, -1, 2], [-1, 0, 1]]", - "playground_execution": "result = Solution().three_sum(nums)\nresult", - "playground_assertion": "# Sort for comparison since order doesn't matter\nresult_sorted = [sorted(triplet) for triplet in result]\nexpected_sorted = [sorted(triplet) for triplet in expected]\nresult_sorted.sort()\nexpected_sorted.sort()\nassert result_sorted == expected_sorted" -} diff --git a/.templates/leetcode/json_old/time_based_key_value_store.json b/.templates/leetcode/json_old/time_based_key_value_store.json deleted file mode 100644 index 11ace03..0000000 --- a/.templates/leetcode/json_old/time_based_key_value_store.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "problem_name": "time_based_key_value_store", - "solution_class_name": "TimeMap", - "problem_number": "981", - "problem_title": "Time Based Key-Value Store", - "difficulty": "Medium", - "topics": "Hash Table, String, Binary Search, Design", - "tags": ["grind-75"], - "readme_description": "Design a time-based key-value data structure that can store multiple values for the same key at different time stamps and retrieve the key's value at a certain timestamp.\n\nImplement the `TimeMap` class:\n\n- `TimeMap()` Initializes the object of the data structure.\n- `void set(String key, String value, int timestamp)` Stores the key `key` with the value `value` at the given time `timestamp`.\n- `String get(String key, int timestamp)` Returns a value such that `set` was called previously, with `timestamp_prev <= timestamp`. If there are multiple such values, it returns the value associated with the largest `timestamp_prev`. If there are no values, it returns `\"\"`.", - "readme_examples": [ - { - "content": "```\nInput\n[\"TimeMap\", \"set\", \"get\", \"get\", \"set\", \"get\", \"get\"]\n[[], [\"foo\", \"bar\", 1], [\"foo\", 1], [\"foo\", 3], [\"foo\", \"bar2\", 4], [\"foo\", 4], [\"foo\", 5]]\nOutput\n[null, null, \"bar\", \"bar\", null, \"bar2\", \"bar2\"]\n```\n\n**Explanation:**\n```\nTimeMap timeMap = new TimeMap();\ntimeMap.set(\"foo\", \"bar\", 1); // store the key \"foo\" and value \"bar\" along with timestamp = 1.\ntimeMap.get(\"foo\", 1); // return \"bar\"\ntimeMap.get(\"foo\", 3); // return \"bar\", since there is no value corresponding to foo at timestamp 3 and timestamp 2, then the only value is at timestamp 1 is \"bar\".\ntimeMap.set(\"foo\", \"bar2\", 4); // store the key \"foo\" and value \"bar2\" along with timestamp = 4.\ntimeMap.get(\"foo\", 4); // return \"bar2\"\ntimeMap.get(\"foo\", 5); // return \"bar2\"\n```" - } - ], - "readme_constraints": "- `1 <= key.length, value.length <= 100`\n- `key` and `value` consist of lowercase English letters and digits.\n- `1 <= timestamp <= 10^7`\n- All the timestamps `timestamp` of `set` are strictly increasing.\n- At most `2 * 10^5` calls will be made to `set` and `get`.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { "name": "__init__", "parameters": "", "return_type": "None", "dummy_return": "" }, - { - "name": "set", - "parameters": "key: str, value: str, timestamp: int", - "return_type": "None", - "dummy_return": "" - }, - { - "name": "get", - "parameters": "key: str, timestamp: int", - "return_type": "str", - "dummy_return": "\"\"" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import TimeMap", - "test_class_name": "TimeBasedKeyValueStore", - "test_helper_methods": [], - "test_methods": [ - { - "name": "test_time_map_operations", - "parametrize": "operations, inputs, expected", - "parametrize_typed": "operations: list[str], inputs: list[list], expected: list", - "test_cases": "[(['TimeMap', 'set', 'get', 'get', 'set', 'get', 'get'], [[], ['foo', 'bar', 1], ['foo', 1], ['foo', 3], ['foo', 'bar2', 4], ['foo', 4], ['foo', 5]], [None, None, 'bar', 'bar', None, 'bar2', 'bar2'])]", - "body": "time_map: TimeMap | None = None\nresult: list[str | None] = []\nfor i, op in enumerate(operations):\n if op == 'TimeMap':\n time_map = TimeMap()\n result.append(None)\n elif op == 'set' and time_map is not None:\n time_map.set(*inputs[i])\n result.append(None)\n elif op == 'get' and time_map is not None:\n result.append(time_map.get(*inputs[i]))\nassert result == expected" - } - ], - "playground_imports": "from solution import TimeMap", - "playground_test_case": "# Example test case\ntime_map = TimeMap()\ntime_map.set('foo', 'bar', 1)\nresult1 = time_map.get('foo', 1)\nresult2 = time_map.get('foo', 3)\ntime_map.set('foo', 'bar2', 4)\nresult3 = time_map.get('foo', 4)\nresult4 = time_map.get('foo', 5)", - "playground_execution": "print(f'get(foo, 1): {result1}')\nprint(f'get(foo, 3): {result2}')\nprint(f'get(foo, 4): {result3}')\nprint(f'get(foo, 5): {result4}')", - "playground_assertion": "assert result1 == 'bar'\nassert result2 == 'bar'\nassert result3 == 'bar2'\nassert result4 == 'bar2'" -} diff --git a/.templates/leetcode/json_old/trapping_rain_water.json b/.templates/leetcode/json_old/trapping_rain_water.json deleted file mode 100644 index dfdb0bb..0000000 --- a/.templates/leetcode/json_old/trapping_rain_water.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "problem_name": "trapping_rain_water", - "solution_class_name": "Solution", - "problem_number": "42", - "problem_title": "Trapping Rain Water", - "difficulty": "Hard", - "topics": "Array, Two Pointers, Dynamic Programming, Stack, Monotonic Stack", - "tags": ["grind-75"], - "readme_description": "Given `n` non-negative integers representing an elevation map where the width of each bar is `1`, compute how much water it can trap after raining.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2018/10/22/rainwatertrap.png)\n\n```\nInput: height = [0,1,0,2,1,0,1,3,2,1,2,1]\nOutput: 6\n```\n**Explanation:** The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped." - }, - { "content": "```\nInput: height = [4,2,0,3,2,5]\nOutput: 9\n```" } - ], - "readme_constraints": "- `n == height.length`\n- `1 <= n <= 2 * 10^4`\n- `0 <= height[i] <= 10^5`", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { "name": "trap", "parameters": "height: list[int]", "return_type": "int", "dummy_return": "0" } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "TrappingRainWater", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_trap", - "parametrize": "height, expected", - "parametrize_typed": "height: list[int], expected: int", - "test_cases": "[([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6), ([4, 2, 0, 3, 2, 5], 9), ([3, 0, 2, 0, 4], 7), ([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6)]", - "body": "result = self.solution.trap(height)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nheight = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]\nexpected = 6", - "playground_execution": "result = Solution().trap(height)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/valid_anagram.json b/.templates/leetcode/json_old/valid_anagram.json deleted file mode 100644 index 32a2ab1..0000000 --- a/.templates/leetcode/json_old/valid_anagram.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "problem_name": "valid_anagram", - "solution_class_name": "Solution", - "problem_number": "242", - "problem_title": "Valid Anagram", - "difficulty": "Easy", - "topics": "Hash Table, String, Sorting", - "tags": ["grind-75"], - "readme_description": "Given two strings `s` and `t`, return `true` if `t` is an anagram of `s`, and `false` otherwise.", - "readme_examples": [ - { "content": "```\nInput: s = \"anagram\", t = \"nagaram\"\nOutput: true\n```" }, - { "content": "```\nInput: s = \"rat\", t = \"car\"\nOutput: false\n```" } - ], - "readme_constraints": "- 1 <= s.length, t.length <= 5 * 10^4\n- s and t consist of lowercase English letters.", - "readme_additional": "**Follow up:** What if the inputs contain Unicode characters? How would you adapt your solution to such a case?", - "solution_imports": "", - "solution_methods": [ - { - "name": "is_anagram", - "parameters": "s: str, t: str", - "return_type": "bool", - "dummy_return": "False" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "ValidAnagram", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_is_anagram", - "parametrize": "s, t, expected", - "parametrize_typed": "s: str, t: str, expected: bool", - "test_cases": "[('anagram', 'nagaram', True), ('rat', 'car', False), ('listen', 'silent', True), ('hello', 'bello', False), ('', '', True), ('a', 'a', True), ('a', 'b', False), ('ab', 'ba', True)]", - "body": "result = self.solution.is_anagram(s, t)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ns = 'anagram'\nt = 'nagaram'\nexpected = True", - "playground_execution": "result = Solution().is_anagram(s, t)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/valid_palindrome.json b/.templates/leetcode/json_old/valid_palindrome.json deleted file mode 100644 index cb4b861..0000000 --- a/.templates/leetcode/json_old/valid_palindrome.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "valid_palindrome", - "solution_class_name": "Solution", - "problem_number": "125", - "problem_title": "Valid Palindrome", - "difficulty": "Easy", - "topics": "Two Pointers, String", - "tags": ["grind-75"], - "readme_description": "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.\n\nGiven a string `s`, return `true` if it is a **palindrome**, or `false` otherwise.", - "readme_examples": [ - { - "content": "```\nInput: s = \"A man, a plan, a canal: Panama\"\nOutput: true\n```\n**Explanation:** \"amanaplanacanalpanama\" is a palindrome." - }, - { - "content": "```\nInput: s = \"race a car\"\nOutput: false\n```\n**Explanation:** \"raceacar\" is not a palindrome." - }, - { - "content": "```\nInput: s = \" \"\nOutput: true\n```\n**Explanation:** s is an empty string \"\" after removing non-alphanumeric characters. Since an empty string reads the same forward and backward, it is a palindrome." - } - ], - "readme_constraints": "- `1 <= s.length <= 2 * 10^5`\n- `s` consists only of printable ASCII characters.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "is_palindrome", - "parameters": "s: str", - "return_type": "bool", - "dummy_return": "False" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "ValidPalindrome", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_is_palindrome", - "parametrize": "s, expected", - "parametrize_typed": "s: str, expected: bool", - "test_cases": "[(\"A man, a plan, a canal: Panama\", True), (\"race a car\", False), (\" \", True), (\"\", True), (\"a\", True), (\"Madam\", True), (\"No 'x' in Nixon\", True), (\"Mr. Owl ate my metal worm\", True)]", - "body": "result = self.solution.is_palindrome(s)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ns = \"A man, a plan, a canal: Panama\"\nexpected = True", - "playground_execution": "result = Solution().is_palindrome(s)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/valid_parentheses.json b/.templates/leetcode/json_old/valid_parentheses.json deleted file mode 100644 index 5029661..0000000 --- a/.templates/leetcode/json_old/valid_parentheses.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "problem_name": "valid_parentheses", - "solution_class_name": "Solution", - "problem_number": "20", - "problem_title": "Valid Parentheses", - "difficulty": "Easy", - "topics": "String, Stack", - "tags": ["grind-75"], - "readme_description": "Given a string `s` containing just the characters `'('`, `')'`, `'{'`, `'}'`, `'['` and `']'`, determine if the input string is valid.\n\nAn input string is valid if:\n\n1. Open brackets must be closed by the same type of brackets.\n2. Open brackets must be closed in the correct order.\n3. Every close bracket has a corresponding open bracket of the same type.", - "readme_examples": [ - { "content": "```\nInput: s = \"()\"\nOutput: true\n```" }, - { "content": "```\nInput: s = \"()[]{}\"\nOutput: true\n```" }, - { "content": "```\nInput: s = \"(]\"\nOutput: false\n```" }, - { "content": "```\nInput: s = \"([])\"\nOutput: true\n```" }, - { "content": "```\nInput: s = \"([)]\"\nOutput: false\n```" } - ], - "readme_constraints": "- `1 <= s.length <= 10^4`\n- `s` consists of parentheses only `'()[]{}'`.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { "name": "is_valid", "parameters": "s: str", "return_type": "bool", "dummy_return": "False" } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "ValidParentheses", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_is_valid", - "parametrize": "s, expected", - "parametrize_typed": "s: str, expected: bool", - "test_cases": "[('()', True), ('()[]{}', True), ('(]', False), ('([])', True), ('([)]', False), ('', True), ('(', False), (')', False), ('{[()]}', True), ('{[(])}', False)]", - "body": "result = self.solution.is_valid(s)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ns = '()'\nexpected = True", - "playground_execution": "result = Solution().is_valid(s)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/validate_binary_search_tree.json b/.templates/leetcode/json_old/validate_binary_search_tree.json deleted file mode 100644 index fd834b8..0000000 --- a/.templates/leetcode/json_old/validate_binary_search_tree.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "validate_binary_search_tree", - "solution_class_name": "Solution", - "problem_number": "98", - "problem_title": "Validate Binary Search Tree", - "difficulty": "Medium", - "topics": "Tree, Depth-First Search, Binary Search Tree, Binary Tree", - "tags": ["grind-75"], - "readme_description": "Given the `root` of a binary tree, determine if it is a valid binary search tree (BST).\n\nA **valid BST** is defined as follows:\n\n- The left subtree of a node contains only nodes with keys **strictly less than** the node's key.\n- The right subtree of a node contains only nodes with keys **strictly greater than** the node's key.\n- Both the left and right subtrees must also be binary search trees.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2020/12/01/tree1.jpg)\n\n```\nInput: root = [2,1,3]\nOutput: true\n```" - }, - { - "content": "![Example 2](https://assets.leetcode.com/uploads/2020/12/01/tree2.jpg)\n\n```\nInput: root = [5,1,4,null,null,3,6]\nOutput: false\n```\n**Explanation:** The root node's value is 5 but its right child's value is 4." - } - ], - "readme_constraints": "- The number of nodes in the tree is in the range `[1, 10^4]`.\n- `-2^31 <= Node.val <= 2^31 - 1`", - "readme_additional": "", - "solution_imports": "from leetcode_py import TreeNode", - "solution_methods": [ - { - "name": "is_valid_bst", - "parameters": "root: TreeNode[int] | None", - "return_type": "bool", - "dummy_return": "False" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom leetcode_py import TreeNode\nfrom .solution import Solution", - "test_class_name": "ValidateBinarySearchTree", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_is_valid_bst", - "parametrize": "root_list, expected", - "parametrize_typed": "root_list: list[int | None], expected: bool", - "test_cases": "[([2, 1, 3], True), ([5, 1, 4, None, None, 3, 6], False), ([2, 1, 3], True), ([1], True), ([1, 1], False)]", - "body": "root = TreeNode.from_list(root_list)\nresult = self.solution.is_valid_bst(root)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution\nfrom leetcode_py import TreeNode", - "playground_test_case": "# Example test case\nroot_list: list[int | None] = [2, 1, 3]\nexpected = True", - "playground_execution": "root = TreeNode.from_list(root_list)\nresult = Solution().is_valid_bst(root)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/word_break.json b/.templates/leetcode/json_old/word_break.json deleted file mode 100644 index 83332be..0000000 --- a/.templates/leetcode/json_old/word_break.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "problem_name": "word_break", - "solution_class_name": "Solution", - "problem_number": "139", - "problem_title": "Word Break", - "difficulty": "Medium", - "topics": "Array, Hash Table, String, Dynamic Programming, Trie, Memoization", - "tags": ["grind-75"], - "readme_description": "Given a string `s` and a dictionary of strings `wordDict`, return `true` if `s` can be segmented into a space-separated sequence of one or more dictionary words.\n\n**Note** that the same word in the dictionary may be reused multiple times in the segmentation.", - "readme_examples": [ - { - "content": "```\nInput: s = \"leetcode\", wordDict = [\"leet\",\"code\"]\nOutput: true\n```\n**Explanation:** Return true because \"leetcode\" can be segmented as \"leet code\"." - }, - { - "content": "```\nInput: s = \"applepenapple\", wordDict = [\"apple\",\"pen\"]\nOutput: true\n```\n**Explanation:** Return true because \"applepenapple\" can be segmented as \"apple pen apple\".\nNote that you are allowed to reuse a dictionary word." - }, - { - "content": "```\nInput: s = \"catsandog\", wordDict = [\"cats\",\"dog\",\"sand\",\"and\",\"cat\"]\nOutput: false\n```" - } - ], - "readme_constraints": "- `1 <= s.length <= 300`\n- `1 <= wordDict.length <= 1000`\n- `1 <= wordDict[i].length <= 20`\n- `s` and `wordDict[i]` consist of only lowercase English letters.\n- All the strings of `wordDict` are **unique**.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "word_break", - "parameters": "s: str, word_dict: list[str]", - "return_type": "bool", - "dummy_return": "False" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "WordBreak", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_word_break", - "parametrize": "s, word_dict, expected", - "parametrize_typed": "s: str, word_dict: list[str], expected: bool", - "test_cases": "[('leetcode', ['leet', 'code'], True), ('applepenapple', ['apple', 'pen'], True), ('catsandog', ['cats', 'dog', 'sand', 'and', 'cat'], False), ('', [], True), ('a', ['a'], True), ('ab', ['a', 'b'], True), ('abcd', ['a', 'abc', 'd'], True)]", - "body": "result = self.solution.word_break(s, word_dict)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\ns = 'leetcode'\nword_dict = ['leet', 'code']\nexpected = True", - "playground_execution": "result = Solution().word_break(s, word_dict)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/word_ladder.json b/.templates/leetcode/json_old/word_ladder.json deleted file mode 100644 index 0e417b3..0000000 --- a/.templates/leetcode/json_old/word_ladder.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "word_ladder", - "solution_class_name": "Solution", - "problem_number": "127", - "problem_title": "Word Ladder", - "difficulty": "Hard", - "topics": "Hash Table, String, Breadth-First Search", - "tags": ["grind-75"], - "readme_description": "A **transformation sequence** from word `beginWord` to word `endWord` using a dictionary `wordList` is a sequence of words `beginWord -> s1 -> s2 -> ... -> sk` such that:\n\n- Every adjacent pair of words differs by a single letter.\n- Every `si` for `1 <= i <= k` is in `wordList`. Note that `beginWord` does not need to be in `wordList`.\n- `sk == endWord`\n\nGiven two words, `beginWord` and `endWord`, and a dictionary `wordList`, return the **number of words** in the **shortest transformation sequence** from `beginWord` to `endWord`, or `0` if no such sequence exists.", - "readme_examples": [ - { - "content": "```\nInput: beginWord = \"hit\", endWord = \"cog\", wordList = [\"hot\",\"dot\",\"dog\",\"lot\",\"log\",\"cog\"]\nOutput: 5\n```\n**Explanation:** One shortest transformation sequence is \"hit\" -> \"hot\" -> \"dot\" -> \"dog\" -> \"cog\", which is 5 words long." - }, - { - "content": "```\nInput: beginWord = \"hit\", endWord = \"cog\", wordList = [\"hot\",\"dot\",\"dog\",\"lot\",\"log\"]\nOutput: 0\n```\n**Explanation:** The endWord \"cog\" is not in wordList, therefore there is no valid transformation sequence." - } - ], - "readme_constraints": "- 1 <= beginWord.length <= 10\n- endWord.length == beginWord.length\n- 1 <= wordList.length <= 5000\n- wordList[i].length == beginWord.length\n- beginWord, endWord, and wordList[i] consist of lowercase English letters.\n- beginWord != endWord\n- All the words in wordList are unique.", - "readme_additional": "", - "solution_imports": "", - "solution_methods": [ - { - "name": "ladder_length", - "parameters": "begin_word: str, end_word: str, word_list: list[str]", - "return_type": "int", - "dummy_return": "0" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "WordLadder", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_ladder_length", - "parametrize": "begin_word, end_word, word_list, expected", - "parametrize_typed": "begin_word: str, end_word: str, word_list: list[str], expected: int", - "test_cases": "[('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log', 'cog'], 5), ('hit', 'cog', ['hot', 'dot', 'dog', 'lot', 'log'], 0), ('a', 'c', ['a', 'b', 'c'], 2)]", - "body": "result = self.solution.ladder_length(begin_word, end_word, word_list)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nbegin_word = 'hit'\nend_word = 'cog'\nword_list = ['hot', 'dot', 'dog', 'lot', 'log', 'cog']\nexpected = 5", - "playground_execution": "result = Solution().ladder_length(begin_word, end_word, word_list)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/.templates/leetcode/json_old/zero_one_matrix.json b/.templates/leetcode/json_old/zero_one_matrix.json deleted file mode 100644 index fd537af..0000000 --- a/.templates/leetcode/json_old/zero_one_matrix.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "problem_name": "zero_one_matrix", - "solution_class_name": "Solution", - "problem_number": "542", - "problem_title": "01 Matrix", - "difficulty": "Medium", - "topics": "Array, Dynamic Programming, Breadth-First Search, Matrix", - "tags": ["grind-75"], - "readme_description": "Given an `m x n` binary matrix `mat`, return the distance of the nearest `0` for each cell.\n\nThe distance between two cells sharing a common edge is `1`.", - "readme_examples": [ - { - "content": "![Example 1](https://assets.leetcode.com/uploads/2021/04/24/01-1-grid.jpg)\n\n```\nInput: mat = [[0,0,0],[0,1,0],[0,0,0]]\nOutput: [[0,0,0],[0,1,0],[0,0,0]]\n```" - }, - { - "content": "![Example 2](https://assets.leetcode.com/uploads/2021/04/24/01-2-grid.jpg)\n\n```\nInput: mat = [[0,0,0],[0,1,0],[1,1,1]]\nOutput: [[0,0,0],[0,1,0],[1,2,1]]\n```" - } - ], - "readme_constraints": "- `m == mat.length`\n- `n == mat[i].length`\n- `1 <= m, n <= 10^4`\n- `1 <= m * n <= 10^4`\n- `mat[i][j]` is either `0` or `1`\n- There is at least one `0` in `mat`", - "readme_additional": "**Note:** This question is the same as 1765: [Map of Highest Peak](https://leetcode.com/problems/map-of-highest-peak/)", - "solution_imports": "", - "solution_methods": [ - { - "name": "update_matrix", - "parameters": "mat: list[list[int]]", - "return_type": "list[list[int]]", - "dummy_return": "[]" - } - ], - "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution", - "test_class_name": "ZeroOneMatrix", - "test_helper_methods": [ - { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" } - ], - "test_methods": [ - { - "name": "test_update_matrix", - "parametrize": "mat, expected", - "parametrize_typed": "mat: list[list[int]], expected: list[list[int]]", - "test_cases": "[([[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]]), ([[0, 0, 0], [0, 1, 0], [1, 1, 1]], [[0, 0, 0], [0, 1, 0], [1, 2, 1]])]", - "body": "result = self.solution.update_matrix(mat)\nassert result == expected" - } - ], - "playground_imports": "from solution import Solution", - "playground_test_case": "# Example test case\nmat = [[0, 0, 0], [0, 1, 0], [1, 1, 1]]\nexpected = [[0, 0, 0], [0, 1, 0], [1, 2, 1]]", - "playground_execution": "result = Solution().update_matrix(mat)\nresult", - "playground_assertion": "assert result == expected" -} diff --git a/Makefile b/Makefile index 67018b2..9daca2b 100644 --- a/Makefile +++ b/Makefile @@ -46,15 +46,13 @@ lint: npx prettier --write "**/*.{ts,tsx,css,json,yaml,yml,md}" $(call lint_target,.) - test: - poetry run pytest leetcode/ leetcode_old/ leetcode_ideal/ tests/ \ + poetry run pytest leetcode/ tests/ \ -v --cov=leetcode --cov=leetcode_py \ --cov-report=term-missing \ --cov-report=xml \ --ignore=.templates \ - --ignore=leetcode/__pycache__ \ - $(shell if [ -d leetcode_ideal ]; then for dir in leetcode_ideal/*/; do problem=$$(basename "$$dir"); if [ -d "leetcode/$$problem" ]; then echo "--ignore=leetcode_ideal/$$problem"; fi; done; fi) + --ignore=leetcode/__pycache__ p-test: @echo "Testing problem: $(PROBLEM)" diff --git a/leetcode_ideal/clone_graph/README.md b/leetcode_ideal/clone_graph/README.md deleted file mode 100644 index eeda5d5..0000000 --- a/leetcode_ideal/clone_graph/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# Clone Graph - -**Difficulty:** Medium -**Topics:** Hash Table, Depth-First Search, Breadth-First Search, Graph -**Tags:** grind-75 - -**LeetCode:** [Problem 133](https://leetcode.com/problems/clone-graph/description/) - -## Problem Description - -Given a reference of a node in a **connected** undirected graph. - -Return a **deep copy** (clone) of the graph. - -Each node in the graph contains a value (`int`) and a list (`List[Node]`) of its neighbors. - -``` -class Node { - public int val; - public List neighbors; -} -``` - -**Test case format:** - -For simplicity, each node's value is the same as the node's index (1-indexed). For example, the first node with `val == 1`, the second node with `val == 2`, and so on. The graph is represented in the test case using an adjacency list. - -**An adjacency list** is a collection of unordered **lists** used to represent a finite graph. Each list describes the set of neighbors of a node in the graph. - -The given node will always be the first node with `val = 1`. You must return the **copy of the given node** as a reference to the cloned graph. - -## Examples - -### Example 1: - - - -``` -Input: adjList = [[2,4],[1,3],[2,4],[1,3]] -Output: [[2,4],[1,3],[2,4],[1,3]] -Explanation: There are 4 nodes in the graph. -1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4). -2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3). -3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4). -4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3). -``` - -### Example 2: - - - -``` -Input: adjList = [[]] -Output: [[]] -Explanation: Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors. -``` - -### Example 3: - -``` -Input: adjList = [] -Output: [] -Explanation: This an empty graph, it does not have any nodes. -``` - -## Constraints - -- The number of nodes in the graph is in the range `[0, 100]`. -- `1 <= Node.val <= 100` -- `Node.val` is unique for each node. -- There are no repeated edges and no self-loops in the graph. -- The Graph is connected and all nodes can be visited starting from the given node. diff --git a/leetcode_ideal/clone_graph/__init__.py b/leetcode_ideal/clone_graph/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_ideal/clone_graph/helpers.py b/leetcode_ideal/clone_graph/helpers.py deleted file mode 100644 index 82ade9d..0000000 --- a/leetcode_ideal/clone_graph/helpers.py +++ /dev/null @@ -1,22 +0,0 @@ -from collections.abc import Sequence - -from leetcode_py import GraphNode - - -def create_graph(adj_list: Sequence[Sequence[int]]) -> GraphNode | None: - return GraphNode.from_adjacency_list(list(list(inner) for inner in adj_list)) - - -def run_clone_graph(solution_class: type, adj_list: Sequence[Sequence[int]]) -> GraphNode | None: - node = create_graph(adj_list) - return solution_class().clone_graph(node) - - -def assert_clone_graph(result: GraphNode | None, expected: GraphNode | None) -> bool: - if result is None and expected is None: - assert True - elif result is not None and expected is not None: - assert result.is_clone(expected) - else: - assert False - return True diff --git a/leetcode_ideal/clone_graph/playground.ipynb b/leetcode_ideal/clone_graph/playground.ipynb deleted file mode 100644 index a6c7d64..0000000 --- a/leetcode_ideal/clone_graph/playground.ipynb +++ /dev/null @@ -1,149 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from helpers import assert_clone_graph, create_graph, run_clone_graph\n", - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "adj_list = [[2, 4], [1, 3], [2, 4], [1, 3]]\n", - "expected = create_graph(adj_list)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "2\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "1--2\n", - "\n", - "\n", - "\n", - "\n", - "4\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "1--4\n", - "\n", - "\n", - "\n", - "\n", - "3\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "2--3\n", - "\n", - "\n", - "\n", - "\n", - "3--4\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "GraphNode({1: [2, 4], 2: [1, 3], 3: [2, 4], 4: [1, 3]})" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = run_clone_graph(Solution, adj_list)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assert_clone_graph(result, expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_ideal/clone_graph/solution.py b/leetcode_ideal/clone_graph/solution.py deleted file mode 100644 index bd53c0b..0000000 --- a/leetcode_ideal/clone_graph/solution.py +++ /dev/null @@ -1,73 +0,0 @@ -from collections import deque - -from leetcode_py import GraphNode - - -class Solution: - # Time: O(V + E) - # Space: O(V) - def clone_graph(self, node: GraphNode | None) -> GraphNode | None: - if node is None: - return None - - def dfs(node: GraphNode, visited: dict[int, GraphNode]): - if node.val in visited: - return visited[node.val] - - clone = GraphNode(node.val) - visited[node.val] = clone - - for neighbor in node.neighbors: - clone.neighbors.append(dfs(neighbor, visited)) - - return clone - - return dfs(node, visited={}) - - -class SolutionDFS: - # DFS Iterative - # Time: O(V + E) - # Space: O(V) - def clone_graph(self, node: GraphNode | None) -> GraphNode | None: - if node is None: - return None - - stack = [node] - visited = {node.val: GraphNode(node.val)} - - while stack: - current = stack.pop() - clone = visited[current.val] - - for neighbor in current.neighbors: - if neighbor.val not in visited: - visited[neighbor.val] = GraphNode(neighbor.val) - stack.append(neighbor) - clone.neighbors.append(visited[neighbor.val]) - - return visited[node.val] - - -class SolutionBFS: - # BFS - # Time: O(V + E) - # Space: O(V) - def clone_graph(self, node: GraphNode | None) -> GraphNode | None: - if node is None: - return None - - queue = deque([node]) - visited = {node.val: GraphNode(node.val)} - - while queue: - current = queue.popleft() - clone = visited[current.val] - - for neighbor in current.neighbors: - if neighbor.val not in visited: - visited[neighbor.val] = GraphNode(neighbor.val) - queue.append(neighbor) - clone.neighbors.append(visited[neighbor.val]) - - return visited[node.val] diff --git a/leetcode_ideal/clone_graph/test_solution.py b/leetcode_ideal/clone_graph/test_solution.py deleted file mode 100644 index 969fd08..0000000 --- a/leetcode_ideal/clone_graph/test_solution.py +++ /dev/null @@ -1,24 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .helpers import assert_clone_graph, create_graph, run_clone_graph -from .solution import Solution, SolutionBFS, SolutionDFS - - -class TestCloneGraph: - - @logged_test - @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS]) - @pytest.mark.parametrize( - "adj_list", - [ - [[2, 4], [1, 3], [2, 4], [1, 3]], - [[]], - [], - ], - ) - def test_clone_graph(self, adj_list: list[list[int]], solution_class: type): - result = run_clone_graph(solution_class, adj_list) - expected = create_graph(adj_list) - assert_clone_graph(result, expected) diff --git a/leetcode_ideal/contains_duplicate/README.md b/leetcode_ideal/contains_duplicate/README.md deleted file mode 100644 index c09c400..0000000 --- a/leetcode_ideal/contains_duplicate/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Contains Duplicate - -**Difficulty:** Easy -**Topics:** Array, Hash Table, Sorting -**Tags:** grind-75 - -**LeetCode:** [Problem 217](https://leetcode.com/problems/contains-duplicate/description/) - -## Problem Description - -Given an integer array `nums`, return `true` if any value appears **at least twice** in the array, and return `false` if every element is distinct. - -## Examples - -### Example 1: - -``` -Input: nums = [1,2,3,1] -Output: true -``` - -**Explanation:** The element 1 occurs at the indices 0 and 3. - -### Example 2: - -``` -Input: nums = [1,2,3,4] -Output: false -``` - -**Explanation:** All elements are distinct. - -### Example 3: - -``` -Input: nums = [1,1,1,3,3,4,3,2,4,2] -Output: true -``` - -## Constraints - -- 1 <= nums.length <= 10^5 -- -10^9 <= nums[i] <= 10^9 diff --git a/leetcode_ideal/contains_duplicate/__init__.py b/leetcode_ideal/contains_duplicate/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_ideal/contains_duplicate/helpers.py b/leetcode_ideal/contains_duplicate/helpers.py deleted file mode 100644 index 44b4503..0000000 --- a/leetcode_ideal/contains_duplicate/helpers.py +++ /dev/null @@ -1,7 +0,0 @@ -def run_contains_duplicate(solution_class: type, nums: list[int]) -> bool: - return solution_class().contains_duplicate(nums) - - -def assert_contains_duplicate(result: bool, expected: bool) -> bool: - assert result == expected - return True diff --git a/leetcode_ideal/contains_duplicate/playground.ipynb b/leetcode_ideal/contains_duplicate/playground.ipynb deleted file mode 100644 index 4c451ba..0000000 --- a/leetcode_ideal/contains_duplicate/playground.ipynb +++ /dev/null @@ -1,91 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from helpers import assert_contains_duplicate, run_contains_duplicate\n", - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "nums = [1, 2, 3, 1]\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = run_contains_duplicate(Solution, nums)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assert_contains_duplicate(result, expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_ideal/contains_duplicate/solution.py b/leetcode_ideal/contains_duplicate/solution.py deleted file mode 100644 index b9bd400..0000000 --- a/leetcode_ideal/contains_duplicate/solution.py +++ /dev/null @@ -1,10 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(n) - def contains_duplicate(self, nums: list[int]) -> bool: - seen = set() - for num in nums: - if num in seen: - return True - seen.add(num) - return False diff --git a/leetcode_ideal/contains_duplicate/test_solution.py b/leetcode_ideal/contains_duplicate/test_solution.py deleted file mode 100644 index f2357a0..0000000 --- a/leetcode_ideal/contains_duplicate/test_solution.py +++ /dev/null @@ -1,31 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .helpers import assert_contains_duplicate, run_contains_duplicate -from .solution import Solution - - -class TestContainsDuplicate: - - @logged_test - @pytest.mark.parametrize( - "nums, expected", - [ - ([1, 2, 3, 1], True), - ([1, 2, 3, 4], False), - ([1, 1, 1, 3, 3, 4, 3, 2, 4, 2], True), - ([], False), - ([1], False), - ([1, 1], True), - ([-1, -2, -3, -1], True), - ([-1, -2, -3, -4], False), - ([0, 0], True), - ([1000000, 999999, 1000000], True), - (list(range(1000)), False), - ([1] * 1000, True), - ], - ) - def test_contains_duplicate(self, nums: list[int], expected: bool): - result = run_contains_duplicate(Solution, nums) - assert_contains_duplicate(result, expected) diff --git a/leetcode_ideal/first_bad_version/README.md b/leetcode_ideal/first_bad_version/README.md deleted file mode 100644 index a710eb0..0000000 --- a/leetcode_ideal/first_bad_version/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# First Bad Version - -**Difficulty:** Easy -**Topics:** Binary Search, Interactive -**Tags:** grind-75 - -**LeetCode:** [Problem 278](https://leetcode.com/problems/first-bad-version/description/) - -## Problem Description - -You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad. - -Suppose you have `n` versions `[1, 2, ..., n]` and you want to find out the first bad one, which causes all the following ones to be bad. - -You are given an API `bool isBadVersion(version)` which returns whether `version` is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API. - -## Examples - -### Example 1: - -``` -Input: n = 5, bad = 4 -Output: 4 -``` - -**Explanation:** - -``` -call isBadVersion(3) -> false -call isBadVersion(5) -> true -call isBadVersion(4) -> true -``` - -Then 4 is the first bad version. - -### Example 2: - -``` -Input: n = 1, bad = 1 -Output: 1 -``` - -## Constraints - -- 1 <= bad <= n <= 2^31 - 1 - -**Note:** The `isBadVersion` API is already defined for you. diff --git a/leetcode_ideal/first_bad_version/__init__.py b/leetcode_ideal/first_bad_version/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_ideal/first_bad_version/helpers.py b/leetcode_ideal/first_bad_version/helpers.py deleted file mode 100644 index 31dc58a..0000000 --- a/leetcode_ideal/first_bad_version/helpers.py +++ /dev/null @@ -1,8 +0,0 @@ -def run_first_bad_version(solution_class: type, n: int, bad: int) -> int: - solution = solution_class(bad) - return solution.first_bad_version(n) - - -def assert_first_bad_version(result: int, expected: int) -> bool: - assert result == expected - return True diff --git a/leetcode_ideal/first_bad_version/playground.ipynb b/leetcode_ideal/first_bad_version/playground.ipynb deleted file mode 100644 index b029826..0000000 --- a/leetcode_ideal/first_bad_version/playground.ipynb +++ /dev/null @@ -1,92 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from helpers import assert_first_bad_version, run_first_bad_version\n", - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "n = 5\n", - "bad = 4\n", - "expected = 4" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "4" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = run_first_bad_version(Solution, n, bad)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assert_first_bad_version(result, expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_ideal/first_bad_version/solution.py b/leetcode_ideal/first_bad_version/solution.py deleted file mode 100644 index 7fa3f49..0000000 --- a/leetcode_ideal/first_bad_version/solution.py +++ /dev/null @@ -1,45 +0,0 @@ -class Solution: - # TODO: template constraint - def __init__(self, first_bad): - self.is_bad_version = lambda version: version >= first_bad - - # Time: O(log n) - # Space: O(1) - def first_bad_version(self, n: int) -> int: - left = 1 - right = n - - while left < right: - mid = (left + right) // 2 - if self.is_bad_version(mid): - right = mid - else: - left = mid + 1 - - return right - - -# BISECT PATTERNS - General Binary Search -# Given: arr = [10,20,30,30,30,40,50], target = 30 -# 0 1 2 3 4 5 6 -# -# bisect_left: Find FIRST occurrence (leftmost insertion point) -# while left < right: -# if arr[mid] >= target: # >= keeps moving left -# right = mid -# Returns: 2 (index of first 30, value=30) -# [10,20,30,30,30,40,50] -# 0 1 2 3 4 5 6 -# ↑ index 2 -# -# bisect_right: Find position AFTER last occurrence -# while left < right: -# if arr[mid] > target: # > allows equal values -# right = mid -# Returns: 5 (index after last 30, value=40) -# [10,20,30,30,30,40,50] -# 0 1 2 3 4 5 6 -# ↑ index 5 -# -# Key difference: >= vs > in the condition -# This problem uses bisect_left pattern to find first bad version diff --git a/leetcode_ideal/first_bad_version/test_solution.py b/leetcode_ideal/first_bad_version/test_solution.py deleted file mode 100644 index ee779bd..0000000 --- a/leetcode_ideal/first_bad_version/test_solution.py +++ /dev/null @@ -1,18 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .helpers import assert_first_bad_version, run_first_bad_version -from .solution import Solution - - -class TestFirstBadVersion: - - @logged_test - @pytest.mark.parametrize( - "n, bad, expected", [(5, 4, 4), (1, 1, 1), (3, 1, 1), (10, 7, 7), (5, -1, 1)] - ) - def test_first_bad_version(self, n: int, bad: int, expected: int): - - result = run_first_bad_version(Solution, n, bad) - assert_first_bad_version(result, expected) diff --git a/leetcode_ideal/implement_trie_prefix_tree/README.md b/leetcode_ideal/implement_trie_prefix_tree/README.md deleted file mode 100644 index 7e0cd3c..0000000 --- a/leetcode_ideal/implement_trie_prefix_tree/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Implement Trie (Prefix Tree) - -**Difficulty:** Medium -**Topics:** Hash Table, String, Design, Trie -**Tags:** grind-75 - -**LeetCode:** [Problem 208](https://leetcode.com/problems/implement-trie-prefix-tree/description/) - -## Problem Description - -A **trie** (pronounced as "try") or **prefix tree** is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker. - -Implement the Trie class: - -- `Trie()` Initializes the trie object. -- `void insert(String word)` Inserts the string `word` into the trie. -- `boolean search(String word)` Returns `true` if the string `word` is in the trie (i.e., was inserted before), and `false` otherwise. -- `boolean startsWith(String prefix)` Returns `true` if there is a previously inserted string `word` that has the prefix `prefix`, and `false` otherwise. - -## Examples - -### Example 1: - -``` -Input -["Trie", "insert", "search", "search", "startsWith", "insert", "search"] -[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]] -Output -[null, null, true, false, true, null, true] -``` - -**Explanation:** - -```python -trie = Trie() -trie.insert("apple") -trie.search("apple") # return True -trie.search("app") # return False -trie.starts_with("app") # return True -trie.insert("app") -trie.search("app") # return True -``` - -## Constraints - -- `1 <= word.length, prefix.length <= 2000` -- `word` and `prefix` consist only of lowercase English letters. -- At most `3 * 10^4` calls **in total** will be made to `insert`, `search`, and `starts_with`. diff --git a/leetcode_ideal/implement_trie_prefix_tree/__init__.py b/leetcode_ideal/implement_trie_prefix_tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_ideal/implement_trie_prefix_tree/helpers.py b/leetcode_ideal/implement_trie_prefix_tree/helpers.py deleted file mode 100644 index 3c368ac..0000000 --- a/leetcode_ideal/implement_trie_prefix_tree/helpers.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Any - - -def run_trie( - solution_class: type, operations: list[str], inputs: list[list[str]] -) -> tuple[list[bool | None], Any]: - trie = None - results: list[bool | None] = [] - for i, op in enumerate(operations): - if op == "Trie": - trie = solution_class() - results.append(None) - elif op == "insert" and trie is not None: - trie.insert(inputs[i][0]) - results.append(None) - elif op == "search" and trie is not None: - results.append(trie.search(inputs[i][0])) - elif op == "starts_with" and trie is not None: - results.append(trie.starts_with(inputs[i][0])) - return results, trie - - -def assert_trie(result: list[bool | None], expected: list[bool | None]) -> bool: - assert result == expected - return True diff --git a/leetcode_ideal/implement_trie_prefix_tree/playground.ipynb b/leetcode_ideal/implement_trie_prefix_tree/playground.ipynb deleted file mode 100644 index 6b8493c..0000000 --- a/leetcode_ideal/implement_trie_prefix_tree/playground.ipynb +++ /dev/null @@ -1,204 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from helpers import assert_trie, run_trie\n", - "from solution import Trie" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "operations = [\"Trie\", \"insert\", \"search\", \"search\", \"starts_with\", \"insert\", \"search\"]\n", - "inputs = [[], [\"apple\"], [\"apple\"], [\"app\"], [\"app\"], [\"app\"], [\"app\"]]\n", - "expected = [None, None, True, False, True, None, True]" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Results: [None, None, True, False, True, None, True]\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root\n", - "\n", - "root\n", - "\n", - "\n", - "\n", - "root_0\n", - "\n", - "a\n", - "\n", - "\n", - "\n", - "root->root_0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root_0_0\n", - "\n", - "p\n", - "\n", - "\n", - "\n", - "root_0->root_0_0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root_0_0_0\n", - "\n", - "p\n", - "\n", - "\n", - "\n", - "root_0_0->root_0_0_0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root_0_0_0_0\n", - "\n", - "l\n", - "\n", - "\n", - "\n", - "root_0_0_0->root_0_0_0_0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root_0_0_0_leaf_1\n", - "\n", - "#: True\n", - "\n", - "\n", - "\n", - "root_0_0_0->root_0_0_0_leaf_1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root_0_0_0_0_0\n", - "\n", - "e\n", - "\n", - "\n", - "\n", - "root_0_0_0_0->root_0_0_0_0_0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root_0_0_0_0_0_leaf_0\n", - "\n", - "#: True\n", - "\n", - "\n", - "\n", - "root_0_0_0_0_0->root_0_0_0_0_0_leaf_0\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result, trie = run_trie(Trie, operations, inputs)\n", - "print(f\"Results: {result}\")\n", - "trie # Show the trie visualization" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "c8308208", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assert_trie(result, expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_ideal/implement_trie_prefix_tree/solution.py b/leetcode_ideal/implement_trie_prefix_tree/solution.py deleted file mode 100644 index 35ea50b..0000000 --- a/leetcode_ideal/implement_trie_prefix_tree/solution.py +++ /dev/null @@ -1,40 +0,0 @@ -from leetcode_py.data_structures import DictTree, RecursiveDict - - -class Trie(DictTree[str]): - END_OF_WORD = "#" - - # Time: O(1) - # Space: O(1) - def __init__(self) -> None: - self.root: RecursiveDict[str] = {} - - # Time: O(m) where m is word length - # Space: O(m) - def insert(self, word: str) -> None: - node = self.root - for char in word: - if char not in node: - node[char] = {} - node = node[char] - node[self.END_OF_WORD] = True - - # Time: O(m) where m is word length - # Space: O(1) - def search(self, word: str) -> bool: - node = self.root - for char in word: - if char not in node: - return False - node = node[char] - return self.END_OF_WORD in node - - # Time: O(m) where m is prefix length - # Space: O(1) - def starts_with(self, prefix: str) -> bool: - node = self.root - for char in prefix: - if char not in node: - return False - node = node[char] - return True diff --git a/leetcode_ideal/implement_trie_prefix_tree/test_solution.py b/leetcode_ideal/implement_trie_prefix_tree/test_solution.py deleted file mode 100644 index fabf0f1..0000000 --- a/leetcode_ideal/implement_trie_prefix_tree/test_solution.py +++ /dev/null @@ -1,41 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .helpers import assert_trie, run_trie -from .solution import Trie - - -class TestImplementTriePrefixTree: - @logged_test - @pytest.mark.parametrize( - "operations, inputs, expected", - [ - ( - ["Trie", "insert", "search", "search", "starts_with", "insert", "search"], - [[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]], - [None, None, True, False, True, None, True], - ), - ( - ["Trie", "insert", "insert", "search", "search", "starts_with", "starts_with"], - [[], ["hello"], ["world"], ["hello"], ["hi"], ["hel"], ["wor"]], - [None, None, None, True, False, True, True], - ), - ( - ["Trie", "insert", "insert", "search", "search", "starts_with", "starts_with"], - [[], ["a"], ["aa"], ["a"], ["aa"], ["a"], ["aa"]], - [None, None, None, True, True, True, True], - ), - ( - ["Trie", "insert", "search", "starts_with", "insert", "search", "starts_with"], - [[], ["test"], ["testing"], ["test"], ["testing"], ["testing"], ["test"]], - [None, None, False, True, None, True, True], - ), - (["Trie", "search", "starts_with"], [[], ["empty"], ["empty"]], [None, False, False]), - ], - ) - def test_trie_operations( - self, operations: list[str], inputs: list[list[str]], expected: list[bool | None] - ): - result, _ = run_trie(Trie, operations, inputs) - assert_trie(result, expected) diff --git a/leetcode_ideal/invert_binary_tree/README.md b/leetcode_ideal/invert_binary_tree/README.md deleted file mode 100644 index a2b7ded..0000000 --- a/leetcode_ideal/invert_binary_tree/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Invert Binary Tree - -**Difficulty:** Easy -**Topics:** Tree, Depth-First Search, Breadth-First Search, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 226](https://leetcode.com/problems/invert-binary-tree/description/) - -## Problem Description - -Given the `root` of a binary tree, invert the tree, and return its root. - -## Examples - -### Example 1: - -``` -Input: root = [4,2,7,1,3,6,9] -Output: [4,7,2,9,6,3,1] -``` - -### Example 2: - -``` -Input: root = [2,1,3] -Output: [2,3,1] -``` - -### Example 3: - -``` -Input: root = [] -Output: [] -``` - -## Constraints - -- The number of nodes in the tree is in the range [0, 100] -- -100 <= Node.val <= 100 diff --git a/leetcode_ideal/invert_binary_tree/__init__.py b/leetcode_ideal/invert_binary_tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_ideal/invert_binary_tree/helpers.py b/leetcode_ideal/invert_binary_tree/helpers.py deleted file mode 100644 index b917772..0000000 --- a/leetcode_ideal/invert_binary_tree/helpers.py +++ /dev/null @@ -1,15 +0,0 @@ -from leetcode_py import TreeNode - - -def create_tree(root_list: list[int | None]) -> TreeNode[int] | None: - return TreeNode[int].from_list(root_list) - - -def run_invert_tree(solution_class: type, root_list: list[int | None]) -> TreeNode[int] | None: - root = create_tree(root_list) - return solution_class().invert_tree(root) - - -def assert_invert_tree(result: TreeNode[int] | None, expected: TreeNode[int] | None) -> bool: - assert result == expected - return True diff --git a/leetcode_ideal/invert_binary_tree/playground.ipynb b/leetcode_ideal/invert_binary_tree/playground.ipynb deleted file mode 100644 index 9765d29..0000000 --- a/leetcode_ideal/invert_binary_tree/playground.ipynb +++ /dev/null @@ -1,185 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from helpers import assert_invert_tree, run_invert_tree\n", - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "from helpers import create_tree\n", - "\n", - "# Example test case\n", - "root_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\n", - "expected = create_tree([4, 7, 2, 9, 6, 3, 1])" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "7\n", - "\n", - "\n", - "\n", - "0->1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "4\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "0->4\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "2\n", - "\n", - "9\n", - "\n", - "\n", - "\n", - "1->2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "3\n", - "\n", - "6\n", - "\n", - "\n", - "\n", - "1->3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "5\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "4->5\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "6\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "4->6\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "TreeNode([4, 7, 2, 9, 6, 3, 1])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = run_invert_tree(Solution, root_list)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assert_invert_tree(result, expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_ideal/invert_binary_tree/solution.py b/leetcode_ideal/invert_binary_tree/solution.py deleted file mode 100644 index 1e11b18..0000000 --- a/leetcode_ideal/invert_binary_tree/solution.py +++ /dev/null @@ -1,59 +0,0 @@ -from collections import deque - -from leetcode_py import TreeNode - -# Note: "Fringe" is the general CS term for the data structure holding nodes to be explored. -# Stack (LIFO) → DFS, Queue (FIFO) → BFS, Priority Queue → A*/Best-first search - - -class Solution: - # DFS recursive - # Time: O(n) - # Space: O(h) where h is height of tree - def invert_tree(self, root: TreeNode[int] | None) -> TreeNode[int] | None: - if not root: - return None - - root.left, root.right = self.invert_tree(root.right), self.invert_tree(root.left) - return root - - -class SolutionDFS: - # DFS iterative - # Time: O(n) - # Space: O(h) where h is height of tree - def invert_tree(self, root: TreeNode[int] | None) -> TreeNode[int] | None: - if not root: - return None - - stack: list[TreeNode[int] | None] = [root] - while stack: - node = stack.pop() - if node is None: - continue - node.left, node.right = node.right, node.left - - stack.append(node.left) - stack.append(node.right) - - return root - - -class SolutionBFS: - # Time: O(n) - # Space: O(w) where w is maximum width of tree - def invert_tree(self, root: TreeNode[int] | None) -> TreeNode[int] | None: - if not root: - return None - - queue: deque[TreeNode[int] | None] = deque([root]) - while queue: - node = queue.popleft() - if node is None: - continue - node.left, node.right = node.right, node.left - - queue.append(node.left) - queue.append(node.right) - - return root diff --git a/leetcode_ideal/invert_binary_tree/test_solution.py b/leetcode_ideal/invert_binary_tree/test_solution.py deleted file mode 100644 index f3b4bee..0000000 --- a/leetcode_ideal/invert_binary_tree/test_solution.py +++ /dev/null @@ -1,25 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .helpers import assert_invert_tree, create_tree, run_invert_tree -from .solution import Solution, SolutionBFS, SolutionDFS - - -class TestInvertBinaryTree: - @logged_test - @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS]) - @pytest.mark.parametrize( - "root_list, expected_list", - [ - ([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), - ([2, 1, 3], [2, 3, 1]), - ([], []), - ], - ) - def test_invert_tree( - self, root_list: list[int | None], expected_list: list[int | None], solution_class: type - ): - result = run_invert_tree(solution_class, root_list) - expected = create_tree(expected_list) - assert_invert_tree(result, expected) diff --git a/leetcode_ideal/linked_list_cycle/README.md b/leetcode_ideal/linked_list_cycle/README.md deleted file mode 100644 index 4ec75c4..0000000 --- a/leetcode_ideal/linked_list_cycle/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Linked List Cycle - -**Difficulty:** Easy -**Topics:** Hash Table, Linked List, Two Pointers -**Tags:** grind-75 - -**LeetCode:** [Problem 141](https://leetcode.com/problems/linked-list-cycle/description/) - -## Problem Description - -Given `head`, the head of a linked list, determine if the linked list has a cycle in it. - -There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the `next` pointer. Internally, `pos` is used to denote the index of the node that tail's `next` pointer is connected to. **Note that `pos` is not passed as a parameter**. - -Return `true` _if there is a cycle in the linked list_. Otherwise, return `false`. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist.png) - -``` -Input: head = [3,2,0,-4], pos = 1 -Output: true -``` - -**Explanation:** There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed). - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test2.png) - -``` -Input: head = [1,2], pos = 0 -Output: true -``` - -**Explanation:** There is a cycle in the linked list, where the tail connects to the 0th node. - -### Example 3: - -![Example 3](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test3.png) - -``` -Input: head = [1], pos = -1 -Output: false -``` - -**Explanation:** There is no cycle in the linked list. - -## Constraints - -- The number of the nodes in the list is in the range `[0, 10^4]`. -- `-10^5 <= Node.val <= 10^5` -- `pos` is `-1` or a **valid index** in the linked-list. - -**Follow up:** Can you solve it using `O(1)` (i.e. constant) memory? diff --git a/leetcode_ideal/linked_list_cycle/__init__.py b/leetcode_ideal/linked_list_cycle/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_ideal/linked_list_cycle/helpers.py b/leetcode_ideal/linked_list_cycle/helpers.py deleted file mode 100644 index 0d4489a..0000000 --- a/leetcode_ideal/linked_list_cycle/helpers.py +++ /dev/null @@ -1,27 +0,0 @@ -from leetcode_py import ListNode - - -def create_cycle_list(values: list[int], pos: int) -> ListNode | None: - if not values: - return None - nodes = [] - head = ListNode(values[0]) - nodes.append(head) - current = head - for i in range(1, len(values)): - current.next = ListNode(values[i]) - current = current.next - nodes.append(current) - if pos != -1 and pos < len(nodes): - current.next = nodes[pos] - return head - - -def run_has_cycle(solution_class: type, values: list[int], pos: int) -> bool: - head = create_cycle_list(values, pos) - return solution_class().has_cycle(head) - - -def assert_has_cycle(result: bool, expected: bool) -> bool: - assert result == expected - return True diff --git a/leetcode_ideal/linked_list_cycle/playground.ipynb b/leetcode_ideal/linked_list_cycle/playground.ipynb deleted file mode 100644 index e8662cf..0000000 --- a/leetcode_ideal/linked_list_cycle/playground.ipynb +++ /dev/null @@ -1,92 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from helpers import assert_has_cycle, run_has_cycle\n", - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "values = [3, 2, 0, -4]\n", - "pos = 1\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = run_has_cycle(Solution, values, pos)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assert_has_cycle(result, expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_ideal/linked_list_cycle/solution.py b/leetcode_ideal/linked_list_cycle/solution.py deleted file mode 100644 index 6c3f838..0000000 --- a/leetcode_ideal/linked_list_cycle/solution.py +++ /dev/null @@ -1,18 +0,0 @@ -from leetcode_py import ListNode - - -class Solution: - # Time: O(n) - # Space: O(1) - def has_cycle(self, head: ListNode[int] | None) -> bool: - fast = head - slow = head - - while fast and fast.next: - assert slow is not None - fast = fast.next.next - slow = slow.next - if fast is slow: - return True - - return False diff --git a/leetcode_ideal/linked_list_cycle/test_solution.py b/leetcode_ideal/linked_list_cycle/test_solution.py deleted file mode 100644 index e6c1b2a..0000000 --- a/leetcode_ideal/linked_list_cycle/test_solution.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .helpers import assert_has_cycle, run_has_cycle -from .solution import Solution - - -class TestLinkedListCycle: - - @logged_test - @pytest.mark.parametrize( - "values, pos, expected", - [ - ([3, 2, 0, -4], 1, True), - ([1, 2], 0, True), - ([1], -1, False), - ([], -1, False), - ([1, 2, 3], -1, False), - ([1, 2, 3, 4, 5], 0, True), - ([1, 2, 3, 4, 5], 2, True), - ([1, 2, 3, 4, 5], 4, True), - ([1], 0, True), - ([1, 2], 1, True), - ([1, 2, 3, 4, 5, 6, 7, 8], 3, True), - ([1, 2, 3, 4, 5, 6, 7, 8], -1, False), - ([1, 2], -1, False), - ([5, 10], 0, True), - ([5, 10], 1, True), - ([0], -1, False), - ([-1, -2, -3], 1, True), - ([100, 200, 300], 0, True), - ], - ) - def test_has_cycle(self, values: list[int], pos: int, expected: bool): - - result = run_has_cycle(Solution, values, pos) - assert_has_cycle(result, expected) diff --git a/leetcode_ideal/lru_cache/README.md b/leetcode_ideal/lru_cache/README.md deleted file mode 100644 index 7037dbc..0000000 --- a/leetcode_ideal/lru_cache/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# LRU Cache - -**Difficulty:** Medium -**Topics:** Hash Table, Linked List, Design, Doubly-Linked List -**Tags:** grind-75 - -**LeetCode:** [Problem 146](https://leetcode.com/problems/lru-cache/description/) - -## Problem Description - -Design a data structure that follows the constraints of a Least Recently Used (LRU) cache. - -Implement the `LRUCache` class: - -- `LRUCache(int capacity)` Initialize the LRU cache with positive size capacity -- `int get(int key)` Return the value of the key if the key exists, otherwise return -1 -- `void put(int key, int value)` Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key - -The functions `get` and `put` must each run in `O(1)` average time complexity. - -## Examples - -### Example 1: - -``` -Input -["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"] -[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]] -Output -[null, null, null, 1, null, -1, null, -1, 3, 4] - -Explanation -LRUCache lRUCache = new LRUCache(2); -lRUCache.put(1, 1); // cache is {1=1} -lRUCache.put(2, 2); // cache is {1=1, 2=2} -lRUCache.get(1); // return 1 -lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3} -lRUCache.get(2); // returns -1 (not found) -lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3} -lRUCache.get(1); // return -1 (not found) -lRUCache.get(3); // return 3 -lRUCache.get(4); // return 4 -``` - -## Constraints - -- 1 <= capacity <= 3000 -- 0 <= key <= 10^4 -- 0 <= value <= 10^5 -- At most 2 \* 10^5 calls will be made to get and put diff --git a/leetcode_ideal/lru_cache/__init__.py b/leetcode_ideal/lru_cache/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_ideal/lru_cache/helpers.py b/leetcode_ideal/lru_cache/helpers.py deleted file mode 100644 index 8fdef5d..0000000 --- a/leetcode_ideal/lru_cache/helpers.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Any - - -def run_lru_cache( - solution_class: type, operations: list[str], inputs: list[list[int]] -) -> tuple[list[int | None], Any]: - cache = None - results: list[int | None] = [] - for i, op in enumerate(operations): - if op == "LRUCache": - cache = solution_class(inputs[i][0]) - results.append(None) - elif op == "get" and cache is not None: - results.append(cache.get(inputs[i][0])) - elif op == "put" and cache is not None: - cache.put(inputs[i][0], inputs[i][1]) - results.append(None) - return results, cache - - -def assert_lru_cache(result: list[int | None], expected: list[int | None]) -> bool: - assert result == expected - return True diff --git a/leetcode_ideal/lru_cache/playground.ipynb b/leetcode_ideal/lru_cache/playground.ipynb deleted file mode 100644 index 0d58702..0000000 --- a/leetcode_ideal/lru_cache/playground.ipynb +++ /dev/null @@ -1,100 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from helpers import assert_lru_cache, run_lru_cache\n", - "from solution import LRUCache" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "operations = [\"LRUCache\", \"put\", \"put\", \"get\", \"put\", \"get\", \"put\", \"get\", \"get\", \"get\"]\n", - "inputs = [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\n", - "expected = [None, None, None, 1, None, -1, None, -1, 3, 4]" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Results: [None, None, None, 1, None, -1, None, -1, 3, 4]\n" - ] - }, - { - "data": { - "text/plain": [ - "OrderedDict([(3, 3), (4, 4)])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result, cache = run_lru_cache(LRUCache, operations, inputs)\n", - "print(f\"Results: {result}\")\n", - "cache.cache" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assert_lru_cache(result, expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_ideal/lru_cache/solution.py b/leetcode_ideal/lru_cache/solution.py deleted file mode 100644 index ea09dcd..0000000 --- a/leetcode_ideal/lru_cache/solution.py +++ /dev/null @@ -1,126 +0,0 @@ -from collections import OrderedDict - -from leetcode_py.data_structures.doubly_list_node import DoublyListNode - - -class LRUCache: - # Space: O(capacity) - def __init__(self, capacity: int) -> None: - self.capacity = capacity - self.cache: OrderedDict[int, int] = OrderedDict() - - # Time: O(1) - # Space: O(1) - def get(self, key: int) -> int: - if key not in self.cache: - return -1 - - # Move to end (most recent) - self.cache.move_to_end(key) - return self.cache[key] - - # Time: O(1) - # Space: O(1) - def put(self, key: int, value: int) -> None: - if key in self.cache: - # Update existing and move to end - self.cache[key] = value - self.cache.move_to_end(key) - else: - # Add new - if len(self.cache) >= self.capacity: - # Remove LRU (first item) - self.cache.popitem(last=False) - - self.cache[key] = value - - -class CacheNode(DoublyListNode[int]): - def __init__(self, key: int = 0, val: int = 0) -> None: - super().__init__(val) - self.key = key - - -class LRUCacheWithDoublyList: - def __init__(self, capacity: int) -> None: - self.capacity = capacity - self.cache: dict[int, CacheNode] = {} - - # Dummy head and tail nodes - self.head = CacheNode() - self.tail = CacheNode() - self.head.next = self.tail - self.tail.prev = self.head - - def _add_node(self, node: CacheNode) -> None: - """Add node right after head""" - node.prev = self.head - node.next = self.head.next - if self.head.next: - self.head.next.prev = node - self.head.next = node - - def _remove_node(self, node: CacheNode) -> None: - """Remove node from list""" - if node.prev: - node.prev.next = node.next - if node.next: - node.next.prev = node.prev - - def _move_to_head(self, node: CacheNode) -> None: - """Move node to head (most recent)""" - self._remove_node(node) - self._add_node(node) - - def _pop_tail(self) -> CacheNode: - """Remove last node before tail""" - last_node = self.tail.prev - assert isinstance(last_node, CacheNode), "Expected CacheNode" - self._remove_node(last_node) - return last_node - - def get(self, key: int) -> int: - node = self.cache.get(key) - if not node: - return -1 - - # Move to head (most recent) - self._move_to_head(node) - return node.val - - def put(self, key: int, value: int) -> None: - node = self.cache.get(key) - - if node: - # Update existing - node.val = value - self._move_to_head(node) - else: - # Add new - new_node = CacheNode(key, value) - - if len(self.cache) >= self.capacity: - # Remove LRU - tail = self._pop_tail() - del self.cache[tail.key] - - self.cache[key] = new_node - self._add_node(new_node) - - -# LRU CACHE IMPLEMENTATION STRATEGIES -# -# Strategy 1: OrderedDict (LRUCache) -# - Uses Python's built-in OrderedDict -# - move_to_end() for O(1) reordering -# - popitem(last=False) for O(1) LRU removal -# - Simple and clean implementation -# -# Strategy 2: HashMap + Doubly Linked List (LRUCacheWithDoublyList) -# - HashMap for O(1) key lookup -# - Doubly linked list for O(1) insertion/deletion -# - Manual node management with dummy head/tail -# - More complex but shows underlying data structure -# -# Both achieve O(1) time complexity for get() and put() -# Space complexity: O(capacity) for both approaches diff --git a/leetcode_ideal/lru_cache/test_solution.py b/leetcode_ideal/lru_cache/test_solution.py deleted file mode 100644 index b0133f6..0000000 --- a/leetcode_ideal/lru_cache/test_solution.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .helpers import assert_lru_cache, run_lru_cache -from .solution import LRUCache, LRUCacheWithDoublyList - - -class TestLRUCache: - @logged_test - @pytest.mark.parametrize("solution_class", [LRUCache, LRUCacheWithDoublyList]) - @pytest.mark.parametrize( - "operations, inputs, expected", - [ - ( - ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"], - [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]], - [None, None, None, 1, None, -1, None, -1, 3, 4], - ), - ( - ["LRUCache", "get", "put", "get", "put", "put", "get", "get"], - [[2], [2], [2, 6], [1], [1, 5], [1, 2], [1], [2]], - [None, -1, None, -1, None, None, 2, 6], - ), - ( - ["LRUCache", "put", "get", "put", "get", "get"], - [[1], [2, 1], [2], [3, 2], [2], [3]], - [None, None, 1, None, -1, 2], - ), - ], - ) - def test_lru_cache( - self, - operations: list[str], - inputs: list[list[int]], - expected: list[int | None], - solution_class: type, - ): - result, _ = run_lru_cache(solution_class, operations, inputs) - assert_lru_cache(result, expected) diff --git a/leetcode_ideal/reverse_linked_list/README.md b/leetcode_ideal/reverse_linked_list/README.md deleted file mode 100644 index ae96ddd..0000000 --- a/leetcode_ideal/reverse_linked_list/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Reverse Linked List - -**Difficulty:** Easy -**Topics:** Linked List, Recursion -**Tags:** grind-75 - -**LeetCode:** [Problem 206](https://leetcode.com/problems/reverse-linked-list/description/) - -## Problem Description - -Given the `head` of a singly linked list, reverse the list, and return the reversed list. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2021/02/19/rev1ex1.jpg) - -``` -Input: head = [1,2,3,4,5] -Output: [5,4,3,2,1] -``` - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2021/02/19/rev1ex2.jpg) - -``` -Input: head = [1,2] -Output: [2,1] -``` - -### Example 3: - -``` -Input: head = [] -Output: [] -``` - -## Constraints - -- The number of nodes in the list is the range `[0, 5000]`. -- `-5000 <= Node.val <= 5000` - -**Follow up:** A linked list can be reversed either iteratively or recursively. Could you implement both? diff --git a/leetcode_ideal/reverse_linked_list/__init__.py b/leetcode_ideal/reverse_linked_list/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_ideal/reverse_linked_list/helpers.py b/leetcode_ideal/reverse_linked_list/helpers.py deleted file mode 100644 index eb7d7ba..0000000 --- a/leetcode_ideal/reverse_linked_list/helpers.py +++ /dev/null @@ -1,15 +0,0 @@ -from leetcode_py import ListNode - - -def create_list(head_list: list[int]) -> ListNode[int] | None: - return ListNode.from_list(list(head_list)) - - -def run_reverse_list(solution_class: type, head_list: list[int]) -> ListNode[int] | None: - head = create_list(head_list) - return solution_class().reverse_list(head) - - -def assert_reverse_list(result: ListNode[int] | None, expected: ListNode[int] | None) -> bool: - assert result == expected - return True diff --git a/leetcode_ideal/reverse_linked_list/playground.ipynb b/leetcode_ideal/reverse_linked_list/playground.ipynb deleted file mode 100644 index 32ced7a..0000000 --- a/leetcode_ideal/reverse_linked_list/playground.ipynb +++ /dev/null @@ -1,159 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from helpers import assert_reverse_list, create_list, run_reverse_list\n", - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "head_list = [1, 2, 3, 4, 5]\n", - "expected = create_list([5, 4, 3, 2, 1])" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_0\n", - "\n", - "5\n", - "\n", - "\n", - "\n", - "node_1\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "node_0->node_1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_2\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "node_1->node_2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_3\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "node_2->node_3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_4\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "node_3->node_4\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "ListNode([5, 4, 3, 2, 1])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = run_reverse_list(Solution, head_list)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assert_reverse_list(result, expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_ideal/reverse_linked_list/solution.py b/leetcode_ideal/reverse_linked_list/solution.py deleted file mode 100644 index 58a0cbe..0000000 --- a/leetcode_ideal/reverse_linked_list/solution.py +++ /dev/null @@ -1,46 +0,0 @@ -from leetcode_py import ListNode - - -class Solution: - # Time: O(n) - # Space: O(1) - def reverse_list(self, head: ListNode[int] | None) -> ListNode[int] | None: - if not head: - return None - - # Iterative approach using three pointers - # Example: [1,2,3] -> [3,2,1] - # - # Initial: prev curr - # None ↓ - # 1 -> 2 -> 3 -> None - # - prev: ListNode[int] | None = None - curr: ListNode[int] | None = head - - while curr: - # Store next node before breaking the link - next_node = curr.next - # - # prev curr next_node - # None ↓ ↓ - # 1 -> 2 -> 3 -> None - # - - # Reverse the current link - curr.next = prev - # None <- 1 2 -> 3 -> None - # prev curr next_node - # - - # Move pointers forward - prev = curr - curr = next_node - # 1 <- 2 3 -> None - # prev curr - # - - # 1 <- 2 <- 3 None - # prev curr - # prev now points to new head of reversed list - return prev diff --git a/leetcode_ideal/reverse_linked_list/test_solution.py b/leetcode_ideal/reverse_linked_list/test_solution.py deleted file mode 100644 index d23dcdf..0000000 --- a/leetcode_ideal/reverse_linked_list/test_solution.py +++ /dev/null @@ -1,29 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .helpers import assert_reverse_list, create_list, run_reverse_list -from .solution import Solution - - -class TestReverseLinkedList: - @logged_test - @pytest.mark.parametrize( - "head_list, expected_list", - [ - ([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]), - ([1, 2], [2, 1]), - ([1], [1]), - ([], []), - ([1, 2, 3], [3, 2, 1]), - ([1, 2, 3, 4], [4, 3, 2, 1]), - ([-1, -2, -3], [-3, -2, -1]), - ([0], [0]), - ([5000, -5000], [-5000, 5000]), - ([1, 1, 1], [1, 1, 1]), - ], - ) - def test_reverse_list(self, head_list: list[int], expected_list: list[int]): - result = run_reverse_list(Solution, head_list) - expected = create_list(expected_list) - assert_reverse_list(result, expected) diff --git a/leetcode_ideal/serialize_and_deserialize_binary_tree/README.md b/leetcode_ideal/serialize_and_deserialize_binary_tree/README.md deleted file mode 100644 index 8226e6c..0000000 --- a/leetcode_ideal/serialize_and_deserialize_binary_tree/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Serialize and Deserialize Binary Tree - -**Difficulty:** Hard -**Topics:** String, Tree, Depth-First Search, Breadth-First Search, Design, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 297](https://leetcode.com/problems/serialize-and-deserialize-binary-tree/description/) - -## Problem Description - -Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment. - -Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure. - -**Clarification:** The input/output format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2020/09/15/serdeser.jpg) - -``` -Input: root = [1,2,3,null,null,4,5] -Output: [1,2,3,null,null,4,5] -``` - -### Example 2: - -``` -Input: root = [] -Output: [] -``` - -## Constraints - -- The number of nodes in the tree is in the range [0, 10^4]. -- -1000 <= Node.val <= 1000 diff --git a/leetcode_ideal/serialize_and_deserialize_binary_tree/__init__.py b/leetcode_ideal/serialize_and_deserialize_binary_tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_ideal/serialize_and_deserialize_binary_tree/helpers.py b/leetcode_ideal/serialize_and_deserialize_binary_tree/helpers.py deleted file mode 100644 index faaecd6..0000000 --- a/leetcode_ideal/serialize_and_deserialize_binary_tree/helpers.py +++ /dev/null @@ -1,26 +0,0 @@ -from collections.abc import Sequence - -from leetcode_py import TreeNode - - -def create_tree(root_list: Sequence[int | None]) -> TreeNode[int] | None: - return TreeNode[int].from_list(list(root_list)) if root_list else None - - -def run_serialize_deserialize( - solution_class: type, root_list: Sequence[int | None] -) -> TreeNode[int] | None: - root = create_tree(root_list) - codec = solution_class() - serialized = codec.serialize(root) - return codec.deserialize(serialized) - - -def assert_serialize_deserialize(result: TreeNode[int] | None, expected: TreeNode[int] | None) -> bool: - if result is None and expected is None: - assert True - elif result is not None and expected is not None: - assert result.to_list() == expected.to_list() - else: - assert False - return True diff --git a/leetcode_ideal/serialize_and_deserialize_binary_tree/playground.ipynb b/leetcode_ideal/serialize_and_deserialize_binary_tree/playground.ipynb deleted file mode 100644 index 76648a2..0000000 --- a/leetcode_ideal/serialize_and_deserialize_binary_tree/playground.ipynb +++ /dev/null @@ -1,159 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from helpers import assert_serialize_deserialize, create_tree, run_serialize_deserialize\n", - "from solution import Codec" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "root_list = [1, 2, 3, None, None, 4, 5]\n", - "expected = create_tree(root_list)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "0->1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "2\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "0->2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "3\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "2->3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "4\n", - "\n", - "5\n", - "\n", - "\n", - "\n", - "2->4\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "TreeNode([1, 2, 3, None, None, 4, 5])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = run_serialize_deserialize(Codec, root_list)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assert_serialize_deserialize(result, expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_ideal/serialize_and_deserialize_binary_tree/solution.py b/leetcode_ideal/serialize_and_deserialize_binary_tree/solution.py deleted file mode 100644 index aeb3177..0000000 --- a/leetcode_ideal/serialize_and_deserialize_binary_tree/solution.py +++ /dev/null @@ -1,81 +0,0 @@ -from leetcode_py import TreeNode - - -class Codec: - # Preorder with Null Markers - # Time: O(n) - # Space: O(n) - def serialize(self, root: TreeNode | None) -> str: - vals = [] - - def dfs(node: TreeNode | None): - if not node: - vals.append("#") - return - vals.append(str(node.val)) - dfs(node.left) - dfs(node.right) - - dfs(root) - return ",".join(vals) - - # Time: O(n) - # Space: O(n) - def deserialize(self, data: str) -> TreeNode | None: - vals = iter(data.split(",")) - - def dfs(): - val = next(vals) - if val == "#": - return None - node = TreeNode(int(val)) - node.left = dfs() - node.right = dfs() - return node - - return dfs() - - -# Binary Tree Serialization Techniques - -# Example Tree: -# 1 -# / \ -# 2 3 -# / \ -# 4 5 - -# 1. Preorder with Null Markers (This Implementation) -# Visit: root → left → right, mark nulls with '#' -# Result: "1,2,#,#,3,4,#,#,5,#,#" -# Pros: Self-contained, unambiguous, O(n) reconstruction -# Cons: Longer string due to null markers - -# 2. Level-order (BFS) with Null Markers -# Visit level by level, mark nulls with '#' -# Result: "1,2,3,#,#,4,5" -# Pros: Simple format like preorder, level-by-level intuitive -# Cons: Still requires queue processing - -# 3. Postorder with Null Markers -# Visit: left → right → root -# Result: "#,#,2,#,#,4,#,#,5,3,1" -# Pros: Bottom-up reconstruction -# Cons: Less intuitive than preorder - -# 4. Inorder + Preorder (Two Arrays) -# Inorder: [2,1,4,3,5], Preorder: [1,2,3,4,5] -# Pros: Works for any binary tree structure -# Cons: Requires two arrays, only works with unique values - -# 5. Parenthetical Preorder -# Same traversal as #1 but with parentheses format: value(left)(right) -# Result: "1(2()())(3(4()())(5()()))" -# Pros: Human readable structure, shows nesting clearly -# Cons: Complex parsing, verbose - -# 6. Parenthetical Postorder -# Same traversal as #3 but with parentheses format: (left)(right)value -# Result: "(()()2)((()()4)(()()5)3)1" -# Pros: Bottom-up readable structure -# Cons: Even more complex parsing diff --git a/leetcode_ideal/serialize_and_deserialize_binary_tree/test_solution.py b/leetcode_ideal/serialize_and_deserialize_binary_tree/test_solution.py deleted file mode 100644 index d92718b..0000000 --- a/leetcode_ideal/serialize_and_deserialize_binary_tree/test_solution.py +++ /dev/null @@ -1,42 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .helpers import assert_serialize_deserialize, create_tree, run_serialize_deserialize -from .solution import Codec - - -class TestSerializeAndDeserializeBinaryTree: - @logged_test - @pytest.mark.parametrize( - "root_list", - [ - ([1, 2, 3, None, None, 4, 5]), - ([]), - ([1]), - ([1, 2]), - ([1, None, 2]), - ([1, 2, 3, 4, 5, 6, 7]), - ([5, 2, 3, None, None, 2, 4, 3, 1]), - ([0]), - ([-1]), - ([1000]), - ([-1000]), - ([1, 2, None, 3, None, 4, None]), - ([1, None, 2, None, 3, None, 4]), - ([-5, -3, -8, -2, -1, -7, -9]), - ([0, -1, 1, -2, None, None, 2]), - ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]), - ([1, None, 2, None, 3, None, 4, None, 5]), - ([1, 2, None, 3, None, 4, None, 5]), - ([5, 4, 7, 3, None, 2, None, -1, None, 9]), - ([1, 1, 1, 1, 1, 1, 1]), - ([2, 2, None, 2, None]), - ([10, 5, 15, None, 6, 12, 20, None, None, None, 13, 18, 25]), - ([50, 30, 70, 20, 40, 60, 80, 10, 25, 35, 45]), - ], - ) - def test_serialize_deserialize(self, root_list: list[int | None]): - result = run_serialize_deserialize(Codec, root_list) - expected = create_tree(root_list) - assert_serialize_deserialize(result, expected) diff --git a/leetcode_ideal/serialize_and_deserialize_binary_tree/tests.py b/leetcode_ideal/serialize_and_deserialize_binary_tree/tests.py deleted file mode 100644 index f1fb387..0000000 --- a/leetcode_ideal/serialize_and_deserialize_binary_tree/tests.py +++ /dev/null @@ -1,100 +0,0 @@ -import pytest - -from leetcode_py import TreeNode -from leetcode_py.test_utils import logged_test - -from .solution import Codec - - -class TestSerializeAndDeserializeBinaryTree: - def setup_method(self): - self.codec = Codec() - - @pytest.mark.parametrize( - "root_list", - [ - # Original test cases - ([1, 2, 3, None, None, 4, 5]), - ([]), - ([1]), - ([1, 2]), - ([1, None, 2]), - ([1, 2, 3, 4, 5, 6, 7]), - ([5, 2, 3, None, None, 2, 4, 3, 1]), - # Edge cases - ([0]), # Single node with value 0 - ([-1]), # Single node with negative value - ([1000]), # Single node with max value - ([-1000]), # Single node with min value - # Skewed trees - ([1, 2, None, 3, None, 4, None]), # Left skewed - ([1, None, 2, None, 3, None, 4]), # Right skewed - # Trees with negative values - ([-5, -3, -8, -2, -1, -7, -9]), - ([0, -1, 1, -2, None, None, 2]), - # Larger trees - ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]), - # Trees with mixed null patterns - ([1, None, 2, None, 3, None, 4, None, 5]), - ([1, 2, None, 3, None, 4, None, 5]), - ([5, 4, 7, 3, None, 2, None, -1, None, 9]), - # Duplicate values - ([1, 1, 1, 1, 1, 1, 1]), - ([2, 2, None, 2, None]), - # Complex asymmetric trees - ([10, 5, 15, None, 6, 12, 20, None, None, None, 13, 18, 25]), - ([50, 30, 70, 20, 40, 60, 80, 10, 25, 35, 45]), - ], - ) - @logged_test - def test_serialize_deserialize(self, root_list: list[int | None]): - root = TreeNode.from_list(root_list) if root_list else None - serialized = self.codec.serialize(root) - deserialized = self.codec.deserialize(serialized) - if root is None: - assert deserialized is None - else: - assert deserialized is not None - assert deserialized.to_list() == root.to_list() - - @logged_test - def test_multiple_serialize_deserialize_cycles(self): - """Test that multiple serialize/deserialize cycles preserve the tree""" - root_list = [1, 2, 3, None, None, 4, 5] - root = TreeNode.from_list(root_list) - - # Perform multiple cycles - current = root - for _ in range(3): - serialized = self.codec.serialize(current) - current = self.codec.deserialize(serialized) - - assert current is not None - assert current.to_list() == root_list - - @logged_test - def test_serialization_format(self): - """Test that serialization produces expected string format""" - # Simple tree: [1, 2, 3] - root = TreeNode.from_list([1, 2, 3]) - serialized = self.codec.serialize(root) - - # Should be preorder: root, left, right with # for null - assert serialized == "1,2,#,#,3,#,#" - - # Empty tree - serialized_empty = self.codec.serialize(None) - assert serialized_empty == "#" - - @logged_test - def test_deserialization_edge_cases(self): - """Test deserialization with various string inputs""" - # Single null - assert self.codec.deserialize("#") is None - - # Single node - single = self.codec.deserialize("42,#,#") - assert single is not None - assert single.val == 42 - assert single.left is None - assert single.right is None diff --git a/leetcode_ideal/valid_parentheses/README.md b/leetcode_ideal/valid_parentheses/README.md deleted file mode 100644 index 05d24f0..0000000 --- a/leetcode_ideal/valid_parentheses/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Valid Parentheses - -**Difficulty:** Easy -**Topics:** String, Stack -**Tags:** grind-75 - -**LeetCode:** [Problem 20](https://leetcode.com/problems/valid-parentheses/description/) - -## Problem Description - -Given a string `s` containing just the characters `'('`, `')'`, `'{'`, `'}'`, `'['` and `']'`, determine if the input string is valid. - -An input string is valid if: - -1. Open brackets must be closed by the same type of brackets. -2. Open brackets must be closed in the correct order. -3. Every close bracket has a corresponding open bracket of the same type. - -## Examples - -### Example 1: - -``` -Input: s = "()" -Output: true -``` - -### Example 2: - -``` -Input: s = "()[]{}" -Output: true -``` - -### Example 3: - -``` -Input: s = "(]" -Output: false -``` - -### Example 4: - -``` -Input: s = "([])" -Output: true -``` - -### Example 5: - -``` -Input: s = "([)]" -Output: false -``` - -## Constraints - -- `1 <= s.length <= 10^4` -- `s` consists of parentheses only `'()[]{}'`. diff --git a/leetcode_ideal/valid_parentheses/__init__.py b/leetcode_ideal/valid_parentheses/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_ideal/valid_parentheses/helpers.py b/leetcode_ideal/valid_parentheses/helpers.py deleted file mode 100644 index 449141e..0000000 --- a/leetcode_ideal/valid_parentheses/helpers.py +++ /dev/null @@ -1,7 +0,0 @@ -def run_is_valid(solution_class: type, s: str) -> bool: - return solution_class().is_valid(s) - - -def assert_is_valid(result: bool, expected: bool) -> bool: - assert result == expected - return True diff --git a/leetcode_ideal/valid_parentheses/playground.ipynb b/leetcode_ideal/valid_parentheses/playground.ipynb deleted file mode 100644 index 6672f1d..0000000 --- a/leetcode_ideal/valid_parentheses/playground.ipynb +++ /dev/null @@ -1,91 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from helpers import assert_is_valid, run_is_valid\n", - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "s = \"()\"\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = run_is_valid(Solution, s)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "assert_is_valid(result, expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_ideal/valid_parentheses/solution.py b/leetcode_ideal/valid_parentheses/solution.py deleted file mode 100644 index 779f734..0000000 --- a/leetcode_ideal/valid_parentheses/solution.py +++ /dev/null @@ -1,14 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(n) - def is_valid(self, s: str) -> bool: - stack = [] - pairs = {"(": ")", "[": "]", "{": "}"} - - for char in s: - if char in pairs: - stack.append(char) - elif not stack or pairs[stack.pop()] != char: - return False - - return not stack diff --git a/leetcode_ideal/valid_parentheses/test_solution.py b/leetcode_ideal/valid_parentheses/test_solution.py deleted file mode 100644 index 4481f8f..0000000 --- a/leetcode_ideal/valid_parentheses/test_solution.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .helpers import assert_is_valid, run_is_valid -from .solution import Solution - - -class TestValidParentheses: - @logged_test - @pytest.mark.parametrize( - "s, expected", - [ - ("()", True), - ("()[]{}", True), - ("(]", False), - ("([])", True), - ("([)]", False), - ("", True), - ("(", False), - (")", False), - ("{[()]}", True), - ("{[(])}", False), - ("(((", False), - (")))", False), - ("()()()", True), - ("({[]})", True), - ("({[}])", False), - ("[[[[[]]]]]", True), - ("[[[[[]]]]", False), - ("{{{{{}}}}}", True), - ("((((((((((", False), - ("))))))))))", False), - ("(){}[]", True), - ("([{}])", True), - ("([{]})", False), - ("(())", True), - ("(()", False), - ("())", False), - ], - ) - def test_is_valid(self, s: str, expected: bool): - result = run_is_valid(Solution, s) - assert_is_valid(result, expected) diff --git a/leetcode_old/__init__.py b/leetcode_old/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/accounts_merge/README.md b/leetcode_old/accounts_merge/README.md deleted file mode 100644 index 53a64a3..0000000 --- a/leetcode_old/accounts_merge/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Accounts Merge - -**Difficulty:** Medium -**Topics:** Array, Hash Table, String, Depth-First Search, Breadth-First Search, Union Find, Sorting -**Tags:** grind-75 - -**LeetCode:** [Problem 721](https://leetcode.com/problems/accounts-merge/description/) - -## Problem Description - -Given a list of `accounts` where each element `accounts[i]` is a list of strings, where the first element `accounts[i][0]` is a name, and the rest of the elements are **emails** representing emails of the account. - -Now, we would like to merge these accounts. Two accounts definitely belong to the same person if there is some common email to both accounts. Note that even if two accounts have the same name, they may belong to different people as people could have the same name. A person can have any number of accounts initially, but all of their accounts definitely have the same name. - -After merging the accounts, return the accounts in the following format: the first element of each account is the name, and the rest of the elements are emails **in sorted order**. The accounts themselves can be returned in **any order**. - -## Examples - -### Example 1: - -``` -Input: accounts = [["John","johnsmith@mail.com","john_newyork@mail.com"],["John","johnsmith@mail.com","john00@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]] -Output: [["John","john00@mail.com","john_newyork@mail.com","johnsmith@mail.com"],["Mary","mary@mail.com"],["John","johnnybravo@mail.com"]] -``` - -**Explanation:** The first and second John's are the same person as they have the common email "johnsmith@mail.com". The third John and Mary are different people as none of their email addresses are used by other accounts. - -### Example 2: - -``` -Input: accounts = [["Gabe","Gabe0@m.co","Gabe3@m.co","Gabe1@m.co"],["Kevin","Kevin3@m.co","Kevin5@m.co","Kevin0@m.co"],["Ethan","Ethan5@m.co","Ethan4@m.co","Ethan0@m.co"],["Hanzo","Hanzo3@m.co","Hanzo1@m.co","Hanzo0@m.co"],["Fern","Fern5@m.co","Fern1@m.co","Fern0@m.co"]] -Output: [["Ethan","Ethan0@m.co","Ethan4@m.co","Ethan5@m.co"],["Gabe","Gabe0@m.co","Gabe1@m.co","Gabe3@m.co"],["Hanzo","Hanzo0@m.co","Hanzo1@m.co","Hanzo3@m.co"],["Kevin","Kevin0@m.co","Kevin3@m.co","Kevin5@m.co"],["Fern","Fern0@m.co","Fern1@m.co","Fern5@m.co"]] -``` - -## Constraints - -- `1 <= accounts.length <= 1000` -- `2 <= accounts[i].length <= 10` -- `1 <= accounts[i][j].length <= 30` -- `accounts[i][0]` consists of English letters. -- `accounts[i][j] (for j > 0)` is a valid email. diff --git a/leetcode_old/accounts_merge/__init__.py b/leetcode_old/accounts_merge/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/accounts_merge/playground.ipynb b/leetcode_old/accounts_merge/playground.ipynb deleted file mode 100644 index 46a7e86..0000000 --- a/leetcode_old/accounts_merge/playground.ipynb +++ /dev/null @@ -1,93 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "accounts = [\n", - " [\"John\", \"johnsmith@mail.com\", \"john_newyork@mail.com\"],\n", - " [\"John\", \"johnsmith@mail.com\", \"john00@mail.com\"],\n", - " [\"Mary\", \"mary@mail.com\"],\n", - " [\"John\", \"johnnybravo@mail.com\"],\n", - "]\n", - "expected = [\n", - " [\"John\", \"john00@mail.com\", \"john_newyork@mail.com\", \"johnsmith@mail.com\"],\n", - " [\"Mary\", \"mary@mail.com\"],\n", - " [\"John\", \"johnnybravo@mail.com\"],\n", - "]" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[['John', 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com'],\n", - " ['Mary', 'mary@mail.com'],\n", - " ['John', 'johnnybravo@mail.com']]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().accounts_merge(accounts)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "# Sort for comparison since order doesn't matter\n", - "result_sorted = [sorted(account) for account in sorted(result)]\n", - "expected_sorted = [sorted(account) for account in sorted(expected)]\n", - "assert result_sorted == expected_sorted" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/accounts_merge/solution.py b/leetcode_old/accounts_merge/solution.py deleted file mode 100644 index dd59d46..0000000 --- a/leetcode_old/accounts_merge/solution.py +++ /dev/null @@ -1,34 +0,0 @@ -class Solution: - # Time: O(N * M) where N is accounts, M is max emails per account - # Space: O(N * M) - def accounts_merge(self, accounts: list[list[str]]) -> list[list[str]]: - email_to_accounts: dict[str, list[int]] = {} - - for i, account in enumerate(accounts): - for email in account[1:]: - if email not in email_to_accounts: - email_to_accounts[email] = [] - email_to_accounts[email].append(i) - - visited: set[int] = set() - result = [] - - def dfs(account_idx: int, emails: set[str]) -> None: - if account_idx in visited: - return - visited.add(account_idx) - - for email in accounts[account_idx][1:]: - emails.add(email) - for neighbor_idx in email_to_accounts[email]: - dfs(neighbor_idx, emails) - - for i in range(len(accounts)): - if i in visited: - continue - - emails: set[str] = set() - dfs(i, emails) - result.append([accounts[i][0]] + sorted(emails)) - - return result diff --git a/leetcode_old/accounts_merge/tests.py b/leetcode_old/accounts_merge/tests.py deleted file mode 100644 index a201047..0000000 --- a/leetcode_old/accounts_merge/tests.py +++ /dev/null @@ -1,112 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestAccountsMerge: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "accounts, expected", - [ - ( - [ - ["John", "johnsmith@mail.com", "john_newyork@mail.com"], - ["John", "johnsmith@mail.com", "john00@mail.com"], - ["Mary", "mary@mail.com"], - ["John", "johnnybravo@mail.com"], - ], - [ - ["John", "john00@mail.com", "john_newyork@mail.com", "johnsmith@mail.com"], - ["Mary", "mary@mail.com"], - ["John", "johnnybravo@mail.com"], - ], - ), - ( - [ - ["Gabe", "Gabe0@m.co", "Gabe3@m.co", "Gabe1@m.co"], - ["Kevin", "Kevin3@m.co", "Kevin5@m.co", "Kevin0@m.co"], - ["Ethan", "Ethan5@m.co", "Ethan4@m.co", "Ethan0@m.co"], - ["Hanzo", "Hanzo3@m.co", "Hanzo1@m.co", "Hanzo0@m.co"], - ["Fern", "Fern5@m.co", "Fern1@m.co", "Fern0@m.co"], - ], - [ - ["Ethan", "Ethan0@m.co", "Ethan4@m.co", "Ethan5@m.co"], - ["Gabe", "Gabe0@m.co", "Gabe1@m.co", "Gabe3@m.co"], - ["Hanzo", "Hanzo0@m.co", "Hanzo1@m.co", "Hanzo3@m.co"], - ["Kevin", "Kevin0@m.co", "Kevin3@m.co", "Kevin5@m.co"], - ["Fern", "Fern0@m.co", "Fern1@m.co", "Fern5@m.co"], - ], - ), - # Single account - ( - [["Alice", "alice@mail.com"]], - [["Alice", "alice@mail.com"]], - ), - # No merging needed - all separate - ( - [ - ["Alice", "alice@mail.com"], - ["Bob", "bob@mail.com"], - ["Charlie", "charlie@mail.com"], - ], - [ - ["Alice", "alice@mail.com"], - ["Bob", "bob@mail.com"], - ["Charlie", "charlie@mail.com"], - ], - ), - # Chain merging - A->B->C - ( - [ - ["John", "john1@mail.com", "john2@mail.com"], - ["John", "john2@mail.com", "john3@mail.com"], - ["John", "john3@mail.com", "john4@mail.com"], - ], - [["John", "john1@mail.com", "john2@mail.com", "john3@mail.com", "john4@mail.com"]], - ), - # Multiple emails per account with complex merging - ( - [ - ["David", "david1@mail.com", "david2@mail.com", "david3@mail.com"], - ["David", "david3@mail.com", "david4@mail.com"], - ["Sarah", "sarah@mail.com"], - ["David", "david5@mail.com", "david1@mail.com"], - ], - [ - [ - "David", - "david1@mail.com", - "david2@mail.com", - "david3@mail.com", - "david4@mail.com", - "david5@mail.com", - ], - ["Sarah", "sarah@mail.com"], - ], - ), - # Same name different people - ( - [ - ["John", "john1@mail.com"], - ["John", "john2@mail.com"], - ["John", "john3@mail.com"], - ], - [ - ["John", "john1@mail.com"], - ["John", "john2@mail.com"], - ["John", "john3@mail.com"], - ], - ), - ], - ) - @logged_test - def test_accounts_merge(self, accounts: list[list[str]], expected: list[list[str]]): - result = self.solution.accounts_merge(accounts) - # Sort both result and expected for comparison since order doesn't matter - result_sorted = [sorted(account) for account in sorted(result)] - expected_sorted = [sorted(account) for account in sorted(expected)] - assert result_sorted == expected_sorted diff --git a/leetcode_old/add_binary/README.md b/leetcode_old/add_binary/README.md deleted file mode 100644 index 92224a0..0000000 --- a/leetcode_old/add_binary/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Add Binary - -**Difficulty:** Easy -**Topics:** Math, String, Bit Manipulation, Simulation -**Tags:** grind-75 - -**LeetCode:** [Problem 67](https://leetcode.com/problems/add-binary/description/) - -## Problem Description - -Given two binary strings `a` and `b`, return _their sum as a binary string_. - -## Examples - -### Example 1: - -``` -Input: a = "11", b = "1" -Output: "100" -``` - -### Example 2: - -``` -Input: a = "1010", b = "1011" -Output: "10101" -``` - -## Constraints - -- `1 <= a.length, b.length <= 10^4` -- `a` and `b` consist only of `'0'` or `'1'` characters. -- Each string does not contain leading zeros except for the zero itself. diff --git a/leetcode_old/add_binary/__init__.py b/leetcode_old/add_binary/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/add_binary/playground.ipynb b/leetcode_old/add_binary/playground.ipynb deleted file mode 100644 index 69c0445..0000000 --- a/leetcode_old/add_binary/playground.ipynb +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "a = \"11\"\n", - "b = \"1\"\n", - "expected = \"100\"" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'100'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().add_binary(a, b)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/add_binary/solution.py b/leetcode_old/add_binary/solution.py deleted file mode 100644 index 36ae857..0000000 --- a/leetcode_old/add_binary/solution.py +++ /dev/null @@ -1,26 +0,0 @@ -class Solution: - # Time: O(len(a) + len(b)) - # Space: O(len(a) + len(b)) - def add_binary(self, a: str, b: str) -> str: - # int(a, 2) converts binary string to decimal: int("11", 2) → 3 - # bin() converts decimal to binary string with prefix: bin(3) → '0b11' - # [2:] removes the '0b' prefix to get just binary digits - return bin(int(a, 2) + int(b, 2))[2:] - - -# Python Base Conversion: -# -# String → Integer (using int() with base parameter): -# - int("1010", 2) → 10 (binary to decimal) -# - int("ff", 16) → 255 (hex to decimal) -# - int("10", 8) → 8 (octal to decimal) -# -# Integer → String (conversion functions add prefixes): -# - bin(10) → '0b1010' (binary with '0b' prefix) -# - hex(255) → '0xff' (hex with '0x' prefix) -# - oct(8) → '0o10' (octal with '0o' prefix) -# -# These prefixes match Python literal syntax: -# - 0b1010 = 10, 0xff = 255, 0o10 = 8 -# -# For string problems, slice off the prefix: bin(n)[2:] gives just the digits. diff --git a/leetcode_old/add_binary/tests.py b/leetcode_old/add_binary/tests.py deleted file mode 100644 index d8c772a..0000000 --- a/leetcode_old/add_binary/tests.py +++ /dev/null @@ -1,25 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestAddBinary: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "a, b, expected", - [ - ("11", "1", "100"), - ("1010", "1011", "10101"), - ("0", "0", "0"), - ("1", "1", "10"), - ("1111", "1111", "11110"), - ], - ) - @logged_test - def test_add_binary(self, a: str, b: str, expected: str): - result = self.solution.add_binary(a, b) - assert result == expected diff --git a/leetcode_old/balanced_binary_tree/README.md b/leetcode_old/balanced_binary_tree/README.md deleted file mode 100644 index 776d6aa..0000000 --- a/leetcode_old/balanced_binary_tree/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Balanced Binary Tree - -**Difficulty:** Easy -**Topics:** Tree, Depth-First Search, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 110](https://leetcode.com/problems/balanced-binary-tree/description/) - -## Problem Description - -Given a binary tree, determine if it is **height-balanced**. - -A height-balanced binary tree is a binary tree in which the depth of the two subtrees of every node never differs by more than one. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2020/10/06/balance_1.jpg) - -``` -Input: root = [3,9,20,null,null,15,7] -Output: true -``` - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2020/10/06/balance_2.jpg) - -``` -Input: root = [1,2,2,3,3,null,null,4,4] -Output: false -``` - -### Example 3: - -``` -Input: root = [] -Output: true -``` - -## Constraints - -- The number of nodes in the tree is in the range `[0, 5000]`. -- `-10^4 <= Node.val <= 10^4` diff --git a/leetcode_old/balanced_binary_tree/__init__.py b/leetcode_old/balanced_binary_tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/balanced_binary_tree/playground.ipynb b/leetcode_old/balanced_binary_tree/playground.ipynb deleted file mode 100644 index 9e4e1e6..0000000 --- a/leetcode_old/balanced_binary_tree/playground.ipynb +++ /dev/null @@ -1,171 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import TreeNode" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "root_list = [3, 9, 20, None, None, 15, 7]\n", - "root = TreeNode.from_list(root_list)\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "f76011d0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "9\n", - "\n", - "\n", - "\n", - "0->1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "2\n", - "\n", - "20\n", - "\n", - "\n", - "\n", - "0->2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "3\n", - "\n", - "15\n", - "\n", - "\n", - "\n", - "2->3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "4\n", - "\n", - "7\n", - "\n", - "\n", - "\n", - "2->4\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "TreeNode([3, 9, 20, None, None, 15, 7])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "root" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().is_balanced(root)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/balanced_binary_tree/solution.py b/leetcode_old/balanced_binary_tree/solution.py deleted file mode 100644 index 81a74aa..0000000 --- a/leetcode_old/balanced_binary_tree/solution.py +++ /dev/null @@ -1,20 +0,0 @@ -from leetcode_py import TreeNode - - -class Solution: - # Time: O(n) - # Space: O(h) - def is_balanced(self, root: TreeNode | None) -> bool: - def height(node: TreeNode | None) -> int: - if not node: - return 0 - - left = height(node.left) - right = height(node.right) - - if left == -1 or right == -1 or abs(left - right) > 1: - return -1 - - return max(left, right) + 1 - - return height(root) != -1 diff --git a/leetcode_old/balanced_binary_tree/tests.py b/leetcode_old/balanced_binary_tree/tests.py deleted file mode 100644 index cbc49a0..0000000 --- a/leetcode_old/balanced_binary_tree/tests.py +++ /dev/null @@ -1,33 +0,0 @@ -import pytest - -from leetcode_py import TreeNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestBalancedBinaryTree: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "root_list, expected", - [ - ([3, 9, 20, None, None, 15, 7], True), - ([1, 2, 2, 3, 3, None, None, 4, 4], False), - ([], True), - ([1], True), - ([1, 2], True), - ([1, None, 2], True), - ([1, 2, 3], True), - ([1, 2, None, 3], False), - ([1, None, 2, None, 3], False), - ([1, 2, 3, 4, 5, 6, 7], True), - ([1, 2, 3, 4, None, None, 7, 8], False), - ], - ) - @logged_test - def test_is_balanced(self, root_list: list[int | None], expected: bool): - root = TreeNode.from_list(root_list) - result = self.solution.is_balanced(root) - assert result == expected diff --git a/leetcode_old/basic_calculator/README.md b/leetcode_old/basic_calculator/README.md deleted file mode 100644 index 885a6c8..0000000 --- a/leetcode_old/basic_calculator/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Basic Calculator - -**Difficulty:** Hard -**Topics:** Math, String, Stack, Recursion -**Tags:** grind-75 - -**LeetCode:** [Problem 224](https://leetcode.com/problems/basic-calculator/description/) - -## Problem Description - -Given a string `s` representing a valid expression, implement a basic calculator to evaluate it, and return the result of the evaluation. - -**Note:** You are **not** allowed to use any built-in function which evaluates strings as mathematical expressions, such as `eval()`. - -## Examples - -### Example 1: - -``` -Input: s = "1 + 1" -Output: 2 -``` - -### Example 2: - -``` -Input: s = " 2-1 + 2 " -Output: 3 -``` - -### Example 3: - -``` -Input: s = "(1+(4+5+2)-3)+(6+8)" -Output: 23 -``` - -## Constraints - -- `1 <= s.length <= 3 * 10^5` -- `s` consists of digits, `'+'`, `'-'`, `'('`, `')'`, and `' '`. -- `s` represents a valid expression. -- `'+'` is **not** used as a unary operation (i.e., `"+1"` and `"+(2 + 3)"` is invalid). -- `'-'` could be used as a unary operation (i.e., `"-1"` and `"-(2 + 3)"` is valid). -- There will be no two consecutive operators in the input. -- Every number and running calculation will fit in a signed 32-bit integer. diff --git a/leetcode_old/basic_calculator/__init__.py b/leetcode_old/basic_calculator/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/basic_calculator/playground.ipynb b/leetcode_old/basic_calculator/playground.ipynb deleted file mode 100644 index 1724458..0000000 --- a/leetcode_old/basic_calculator/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "s = \"(1+(4+5+2)-3)+(6+8)\"\n", - "expected = 23" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "23" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().calculate(s)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/basic_calculator/solution.py b/leetcode_old/basic_calculator/solution.py deleted file mode 100644 index e441460..0000000 --- a/leetcode_old/basic_calculator/solution.py +++ /dev/null @@ -1,61 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(n) - def calculate(self, s: str) -> int: - stack = [] - num = 0 - sign = 1 - result = 0 - - for char in s: - if char.isdigit(): - num = num * 10 + int(char) - elif char in "+-": - result += sign * num - num = 0 - sign = 1 if char == "+" else -1 - elif char == "(": - stack.append(result) - stack.append(sign) - result = 0 - sign = 1 - elif char == ")": - if len(stack) < 2: - raise ValueError("Mismatched parentheses") - result += sign * num - num = 0 - result *= stack.pop() - result += stack.pop() - elif char != " ": - raise ValueError(f"Invalid character: '{char}'") - - if stack: - raise ValueError("Mismatched parentheses") - - return result + sign * num - - -# Example walkthrough: "(1+(4+5+2)-3)+(6+8)" = 23 -# -# char | num | sign | result | stack | action -# -----|-----|------|--------|------------|------------------ -# '(' | 0 | 1 | 0 | [0, 1] | push result=0, sign=1 -# '1' | 1 | 1 | 0 | [0, 1] | build num=1 -# '+' | 0 | 1 | 1 | [0, 1] | result += 1*1 = 1 -# '(' | 0 | 1 | 0 | [0,1,1,1] | push result=1, sign=1 -# '4' | 4 | 1 | 0 | [0,1,1,1] | build num=4 -# '+' | 0 | 1 | 4 | [0,1,1,1] | result += 1*4 = 4 -# '5' | 5 | 1 | 4 | [0,1,1,1] | build num=5 -# '+' | 0 | 1 | 9 | [0,1,1,1] | result += 1*5 = 9 -# '2' | 2 | 1 | 9 | [0,1,1,1] | build num=2 -# ')' | 0 | 1 | 11 | [0, 1] | result=11*1+1 = 12 -# '-' | 0 | -1 | 12 | [0, 1] | sign = -1 -# '3' | 3 | -1 | 12 | [0, 1] | build num=3 -# ')' | 0 | 1 | 9 | [] | result=9*1+0 = 9 -# '+' | 0 | 1 | 9 | [] | sign = 1 -# '(' | 0 | 1 | 0 | [9, 1] | push result=9, sign=1 -# '6' | 6 | 1 | 0 | [9, 1] | build num=6 -# '+' | 0 | 1 | 6 | [9, 1] | result += 1*6 = 6 -# '8' | 8 | 1 | 6 | [9, 1] | build num=8 -# ')' | 0 | 1 | 14 | [] | result=14*1+9 = 23 -# end | 0 | 1 | 14 | [] | return 14+1*0 = 23 diff --git a/leetcode_old/basic_calculator/tests.py b/leetcode_old/basic_calculator/tests.py deleted file mode 100644 index 08c96e9..0000000 --- a/leetcode_old/basic_calculator/tests.py +++ /dev/null @@ -1,68 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestBasicCalculator: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "s, expected", - [ - ("1 + 1", 2), - (" 2-1 + 2 ", 3), - ("(1+(4+5+2)-3)+(6+8)", 23), - ("1", 1), - ("-1", -1), - ("-(1+2)", -3), - ("2147483647", 2147483647), - ("1-1+1", 1), - # Additional edge cases - ("0", 0), - ("-0", 0), - ("()", 0), - ("((()))", 0), - ("1+(2+3)", 6), - ("(1+2)+3", 6), - ("1-(2+3)", -4), - ("(1-2)+3", 2), - ("-(-1)", 1), - ("-(-(-1))", -1), - ("1000000-999999", 1), - ("10+20-30+40", 40), - ("((1+2)+(3+4))", 10), - ("1+(2-(3+4))", -4), - ("-(1+(2+3))", -6), - (" 1 + 2 ", 3), - ("123+456", 579), - ("-2147483648", -2147483648), - ], - ) - @logged_test - def test_calculate(self, s: str, expected: int): - result = self.solution.calculate(s) - assert result == expected - - @pytest.mark.parametrize( - "s, error_msg", - [ - ("(1+2", "Mismatched parentheses"), - ("1+2)", "Mismatched parentheses"), - ("((1+2)", "Mismatched parentheses"), - ("1+2))", "Mismatched parentheses"), - ("1*2", r"Invalid character: '\*'"), - ("1/2", "Invalid character: '/'"), - ("1%2", "Invalid character: '%'"), - ("1^2", r"Invalid character: '\^'"), - ("1&2", "Invalid character: '&'"), - ("a+b", "Invalid character: 'a'"), - ("1+2.5", r"Invalid character: '\.'"), - ], - ) - @logged_test - def test_calculate_invalid_input(self, s: str, error_msg: str): - with pytest.raises(ValueError, match=error_msg): - self.solution.calculate(s) diff --git a/leetcode_old/best_time_to_buy_and_sell_stock/README.md b/leetcode_old/best_time_to_buy_and_sell_stock/README.md deleted file mode 100644 index a18f45b..0000000 --- a/leetcode_old/best_time_to_buy_and_sell_stock/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Best Time to Buy and Sell Stock - -**Difficulty:** Easy -**Topics:** Array, Dynamic Programming -**Tags:** grind-75 - -**LeetCode:** [Problem 121](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/) - -## Problem Description - -You are given an array `prices` where `prices[i]` is the price of a given stock on the ith day. - -You want to maximize your profit by choosing a **single day** to buy one stock and choosing a **different day in the future** to sell that stock. - -Return _the maximum profit you can achieve from this transaction_. If you cannot achieve any profit, return `0`. - -## Examples - -### Example 1: - -``` -Input: prices = [7,1,5,3,6,4] -Output: 5 -``` - -**Explanation:** Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5. -Note that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell. - -### Example 2: - -``` -Input: prices = [7,6,4,3,1] -Output: 0 -``` - -**Explanation:** In this case, no transactions are done and the max profit = 0. - -## Constraints - -- 1 <= prices.length <= 10^5 -- 0 <= prices[i] <= 10^4 diff --git a/leetcode_old/best_time_to_buy_and_sell_stock/__init__.py b/leetcode_old/best_time_to_buy_and_sell_stock/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/best_time_to_buy_and_sell_stock/playground.ipynb b/leetcode_old/best_time_to_buy_and_sell_stock/playground.ipynb deleted file mode 100644 index cc80126..0000000 --- a/leetcode_old/best_time_to_buy_and_sell_stock/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "prices = [7, 1, 5, 3, 6, 4]\n", - "expected = 5" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().max_profit(prices)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/best_time_to_buy_and_sell_stock/solution.py b/leetcode_old/best_time_to_buy_and_sell_stock/solution.py deleted file mode 100644 index 4f877ec..0000000 --- a/leetcode_old/best_time_to_buy_and_sell_stock/solution.py +++ /dev/null @@ -1,12 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(1) - def max_profit(self, prices: list[int]) -> int: - min_price = prices[0] - max_profit = 0 - - for price in prices[1:]: - max_profit = max(max_profit, price - min_price) - min_price = min(min_price, price) - - return max_profit diff --git a/leetcode_old/best_time_to_buy_and_sell_stock/tests.py b/leetcode_old/best_time_to_buy_and_sell_stock/tests.py deleted file mode 100644 index 9b66879..0000000 --- a/leetcode_old/best_time_to_buy_and_sell_stock/tests.py +++ /dev/null @@ -1,28 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestBestTimeToBuyAndSellStock: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "prices, expected", - [ - ([7, 1, 5, 3, 6, 4], 5), - ([7, 6, 4, 3, 1], 0), - ([1, 2, 3, 4, 5], 4), - ([5, 4, 3, 2, 1], 0), - ([1], 0), - ([2, 1], 0), - ([1, 2], 1), - ([3, 2, 6, 5, 0, 3], 4), - ], - ) - @logged_test - def test_max_profit(self, prices: list[int], expected: int): - result = self.solution.max_profit(prices) - assert result == expected diff --git a/leetcode_old/binary_search/README.md b/leetcode_old/binary_search/README.md deleted file mode 100644 index 16d0461..0000000 --- a/leetcode_old/binary_search/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Binary Search - -**Difficulty:** Easy -**Topics:** Array, Binary Search -**Tags:** grind-75 - -**LeetCode:** [Problem 704](https://leetcode.com/problems/binary-search/description/) - -## Problem Description - -Given an array of integers `nums` which is sorted in ascending order, and an integer `target`, write a function to search `target` in `nums`. If `target` exists, then return its index. Otherwise, return `-1`. - -You must write an algorithm with `O(log n)` runtime complexity. - -## Examples - -### Example 1: - -``` -Input: nums = [-1,0,3,5,9,12], target = 9 -Output: 4 -``` - -**Explanation:** 9 exists in nums and its index is 4 - -### Example 2: - -``` -Input: nums = [-1,0,3,5,9,12], target = 2 -Output: -1 -``` - -**Explanation:** 2 does not exist in nums so return -1 - -## Constraints - -- `1 <= nums.length <= 10^4` -- `-10^4 < nums[i], target < 10^4` -- All the integers in `nums` are **unique**. -- `nums` is sorted in ascending order. diff --git a/leetcode_old/binary_search/__init__.py b/leetcode_old/binary_search/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/binary_search/playground.ipynb b/leetcode_old/binary_search/playground.ipynb deleted file mode 100644 index d7a58c0..0000000 --- a/leetcode_old/binary_search/playground.ipynb +++ /dev/null @@ -1,69 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "nums = [-1, 0, 3, 5, 9, 12]\n", - "target = 9\n", - "expected = 4" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().search(nums, target)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/binary_search/solution.py b/leetcode_old/binary_search/solution.py deleted file mode 100644 index 7bc15e6..0000000 --- a/leetcode_old/binary_search/solution.py +++ /dev/null @@ -1,17 +0,0 @@ -class Solution: - # Time: O(log n) - # Space: O(1) - def search(self, nums: list[int], target: int) -> int: - left, right = 0, len(nums) - 1 - - while left <= right: - mid = (left + right) // 2 - - if nums[mid] == target: - return mid - elif nums[mid] < target: - left = mid + 1 - else: - right = mid - 1 - - return -1 diff --git a/leetcode_old/binary_search/tests.py b/leetcode_old/binary_search/tests.py deleted file mode 100644 index 0842386..0000000 --- a/leetcode_old/binary_search/tests.py +++ /dev/null @@ -1,39 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestBinarySearch: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "nums, target, expected", - [ - # Original examples - ([-1, 0, 3, 5, 9, 12], 9, 4), - ([-1, 0, 3, 5, 9, 12], 2, -1), - # Single element - ([5], 5, 0), - ([5], -5, -1), - # Target at boundaries - ([1, 3, 5, 7, 9], 1, 0), - ([1, 3, 5, 7, 9], 9, 4), - # Target not found - ([1, 3, 5, 7, 9], 4, -1), - # Two elements - ([1, 3], 1, 0), - ([1, 3], 3, 1), - ([1, 3], 2, -1), - # Negative numbers - ([-5, -2, 0, 3, 7], -2, 1), - ([-5, -2, 0, 3, 7], 0, 2), - ([-5, -2, 0, 3, 7], -1, -1), - ], - ) - @logged_test - def test_search(self, nums: list[int], target: int, expected: int): - result = self.solution.search(nums, target) - assert result == expected diff --git a/leetcode_old/binary_tree_level_order_traversal/README.md b/leetcode_old/binary_tree_level_order_traversal/README.md deleted file mode 100644 index 346f623..0000000 --- a/leetcode_old/binary_tree_level_order_traversal/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Binary Tree Level Order Traversal - -**Difficulty:** Medium -**Topics:** Tree, Breadth-First Search, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 102](https://leetcode.com/problems/binary-tree-level-order-traversal/description/) - -## Problem Description - -Given the `root` of a binary tree, return the level order traversal of its nodes' values. (i.e., from left to right, level by level). - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2021/02/19/tree1.jpg) - -``` -Input: root = [3,9,20,null,null,15,7] -Output: [[3],[9,20],[15,7]] -``` - -### Example 2: - -``` -Input: root = [1] -Output: [[1]] -``` - -### Example 3: - -``` -Input: root = [] -Output: [] -``` - -## Constraints - -- The number of nodes in the tree is in the range [0, 2000] -- -1000 <= Node.val <= 1000 diff --git a/leetcode_old/binary_tree_level_order_traversal/__init__.py b/leetcode_old/binary_tree_level_order_traversal/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/binary_tree_level_order_traversal/playground.ipynb b/leetcode_old/binary_tree_level_order_traversal/playground.ipynb deleted file mode 100644 index c0a13d9..0000000 --- a/leetcode_old/binary_tree_level_order_traversal/playground.ipynb +++ /dev/null @@ -1,71 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import TreeNode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "root_list = [3, 9, 20, None, None, 15, 7]\n", - "root = TreeNode.from_list(root_list)\n", - "expected = [[3], [9, 20], [15, 7]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().level_order(root)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/binary_tree_level_order_traversal/solution.py b/leetcode_old/binary_tree_level_order_traversal/solution.py deleted file mode 100644 index 702c596..0000000 --- a/leetcode_old/binary_tree_level_order_traversal/solution.py +++ /dev/null @@ -1,31 +0,0 @@ -from collections import deque - -from leetcode_py import TreeNode - - -class Solution: - # Time: O(n) - # Space: O(w) where w is max width of tree - def level_order(self, root: TreeNode | None) -> list[list[int]]: - if not root: - return [] - - result = [] - queue = deque([root]) - - while queue: - level_size = len(queue) - level = [] - - for _ in range(level_size): - node = queue.popleft() - level.append(node.val) - - if node.left: - queue.append(node.left) - if node.right: - queue.append(node.right) - - result.append(level) - - return result diff --git a/leetcode_old/binary_tree_level_order_traversal/tests.py b/leetcode_old/binary_tree_level_order_traversal/tests.py deleted file mode 100644 index daddfb4..0000000 --- a/leetcode_old/binary_tree_level_order_traversal/tests.py +++ /dev/null @@ -1,39 +0,0 @@ -import pytest - -from leetcode_py import TreeNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestBinaryTreeLevelOrderTraversal: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "root_list, expected", - [ - ([3, 9, 20, None, None, 15, 7], [[3], [9, 20], [15, 7]]), - ([1], [[1]]), - ([], []), - ([1, 2, 3, 4, 5, 6, 7], [[1], [2, 3], [4, 5, 6, 7]]), - ([1, 2, None, 3, None, 4, None, 5], [[1], [2], [3], [4], [5]]), - # Edge cases - ([1, None, 2, None, 3], [[1], [2], [3]]), # Right skewed - ([1, 2, None, 3, None], [[1], [2], [3]]), # Left skewed - ([0], [[0]]), # Single zero node - ([-1, -2, -3], [[-1], [-2, -3]]), # Negative values - ([1, 2, 3, None, None, None, 4], [[1], [2, 3], [4]]), # Sparse tree - ( - [5, 4, 8, 11, None, 13, 4, 7, 2, None, None, None, 1], - [[5], [4, 8], [11, 13, 4], [7, 2, 1]], - ), # Complex tree - ([1, 2, 2, 3, 3, 3, 3], [[1], [2, 2], [3, 3, 3, 3]]), # Duplicate values - ([1, None, None], [[1]]), # Single node with null children - ], - ) - @logged_test - def test_level_order(self, root_list: list[int | None], expected: list[list[int]]): - root = TreeNode.from_list(root_list) if root_list else None - result = self.solution.level_order(root) - assert result == expected diff --git a/leetcode_old/binary_tree_right_side_view/README.md b/leetcode_old/binary_tree_right_side_view/README.md deleted file mode 100644 index b44249a..0000000 --- a/leetcode_old/binary_tree_right_side_view/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# Binary Tree Right Side View - -**Difficulty:** Medium -**Topics:** Tree, Depth-First Search, Breadth-First Search, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 199](https://leetcode.com/problems/binary-tree-right-side-view/description/) - -## Problem Description - -Given the `root` of a binary tree, imagine yourself standing on the **right side** of it, return _the values of the nodes you can see ordered from top to bottom_. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2024/11/24/tmpd5jn43fs-1.png) - -``` -Input: root = [1,2,3,null,5,null,4] -Output: [1,3,4] -``` - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2024/11/24/tmpkpe40xeh-1.png) - -``` -Input: root = [1,2,3,4,null,null,null,5] -Output: [1,3,4,5] -``` - -### Example 3: - -``` -Input: root = [1,null,3] -Output: [1,3] -``` - -### Example 4: - -``` -Input: root = [] -Output: [] -``` - -## Constraints - -- The number of nodes in the tree is in the range `[0, 100]`. -- `-100 <= Node.val <= 100` diff --git a/leetcode_old/binary_tree_right_side_view/__init__.py b/leetcode_old/binary_tree_right_side_view/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/binary_tree_right_side_view/playground.ipynb b/leetcode_old/binary_tree_right_side_view/playground.ipynb deleted file mode 100644 index 1deced0..0000000 --- a/leetcode_old/binary_tree_right_side_view/playground.ipynb +++ /dev/null @@ -1,82 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import TreeNode" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "root_list: list[int | None] = [1, 2, 3, None, 5, None, 4]\n", - "expected = [1, 3, 4]" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[1, 3, 4]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "root = TreeNode.from_list(root_list)\n", - "result = Solution().right_side_view(root)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/binary_tree_right_side_view/solution.py b/leetcode_old/binary_tree_right_side_view/solution.py deleted file mode 100644 index 841c2c8..0000000 --- a/leetcode_old/binary_tree_right_side_view/solution.py +++ /dev/null @@ -1,67 +0,0 @@ -from collections import deque - -from leetcode_py import TreeNode - - -class Solution: - # Time: O(n) - # Space: O(h) - def right_side_view(self, root: TreeNode[int] | None) -> list[int]: - result: list[int] = [] - - def dfs(node: TreeNode[int] | None, level: int) -> None: - if not node: - return - if level == len(result): - result.append(node.val) - dfs(node.right, level + 1) - dfs(node.left, level + 1) - - dfs(root, 0) - return result - - -class SolutionDFS: - # Time: O(n) - # Space: O(h) - def right_side_view(self, root: TreeNode[int] | None) -> list[int]: - if not root: - return [] - - result: list[int] = [] - stack = [(root, 0)] - - while stack: - node, level = stack.pop() - if level == len(result): - result.append(node.val) - if node.left: - stack.append((node.left, level + 1)) - if node.right: - stack.append((node.right, level + 1)) - - return result - - -class SolutionBFS: - # Time: O(n) - # Space: O(w) - def right_side_view(self, root: TreeNode[int] | None) -> list[int]: - if not root: - return [] - - result: list[int] = [] - queue = deque([root]) - - while queue: - level_size = len(queue) - for i in range(level_size): - node = queue.popleft() - if i == level_size - 1: # rightmost node - result.append(node.val) - if node.left: - queue.append(node.left) - if node.right: - queue.append(node.right) - - return result diff --git a/leetcode_old/binary_tree_right_side_view/tests.py b/leetcode_old/binary_tree_right_side_view/tests.py deleted file mode 100644 index acfd3ad..0000000 --- a/leetcode_old/binary_tree_right_side_view/tests.py +++ /dev/null @@ -1,30 +0,0 @@ -import pytest - -from leetcode_py import TreeNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution, SolutionBFS, SolutionDFS - - -class TestBinaryTreeRightSideView: - @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS]) - @pytest.mark.parametrize( - "root_list, expected", - [ - ([1, 2, 3, None, 5, None, 4], [1, 3, 4]), - ([1, 2, 3, 4, None, None, None, 5], [1, 3, 4, 5]), - ([1, None, 3], [1, 3]), - ([], []), - ], - ) - @logged_test - def test_right_side_view( - self, - root_list: list[int | None], - expected: list[int], - solution_class: type[Solution | SolutionDFS | SolutionBFS], - ): - solution = solution_class() - root = TreeNode.from_list(root_list) - result = solution.right_side_view(root) - assert result == expected diff --git a/leetcode_old/climbing_stairs/README.md b/leetcode_old/climbing_stairs/README.md deleted file mode 100644 index 18ea888..0000000 --- a/leetcode_old/climbing_stairs/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Climbing Stairs - -**Difficulty:** Easy -**Topics:** Math, Dynamic Programming, Memoization -**Tags:** grind-75 - -**LeetCode:** [Problem 70](https://leetcode.com/problems/climbing-stairs/description/) - -## Problem Description - -You are climbing a staircase. It takes `n` steps to reach the top. - -Each time you can either climb `1` or `2` steps. In how many distinct ways can you climb to the top? - -## Examples - -### Example 1: - -``` -Input: n = 2 -Output: 2 -``` - -**Explanation:** There are two ways to climb to the top. - -1. 1 step + 1 step -2. 2 steps - -### Example 2: - -``` -Input: n = 3 -Output: 3 -``` - -**Explanation:** There are three ways to climb to the top. - -1. 1 step + 1 step + 1 step -2. 1 step + 2 steps -3. 2 steps + 1 step - -## Constraints - -- 1 <= n <= 45 diff --git a/leetcode_old/climbing_stairs/__init__.py b/leetcode_old/climbing_stairs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/climbing_stairs/playground.ipynb b/leetcode_old/climbing_stairs/playground.ipynb deleted file mode 100644 index 2edd00e..0000000 --- a/leetcode_old/climbing_stairs/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "n = 3\n", - "expected = 3" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "3" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().climb_stairs(n)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/climbing_stairs/solution.py b/leetcode_old/climbing_stairs/solution.py deleted file mode 100644 index 0a56db6..0000000 --- a/leetcode_old/climbing_stairs/solution.py +++ /dev/null @@ -1,16 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(1) - - # This follows Fibonacci pattern - # Standard Fib: F(0)=0, F(1)=1, F(2)=1, F(3)=2, F(4)=3, F(5)=5... - def climb_stairs(self, n: int) -> int: - if n <= 2: - return n - - prev2, prev1 = 1, 2 - for _ in range(3, n + 1): - current = prev1 + prev2 - prev2, prev1 = prev1, current - - return prev1 diff --git a/leetcode_old/climbing_stairs/tests.py b/leetcode_old/climbing_stairs/tests.py deleted file mode 100644 index f3c338f..0000000 --- a/leetcode_old/climbing_stairs/tests.py +++ /dev/null @@ -1,19 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestClimbingStairs: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "n, expected", - [(1, 1), (2, 2), (3, 3), (4, 5), (5, 8), (6, 13), (10, 89), (20, 10946), (45, 1836311903)], - ) - @logged_test - def test_climb_stairs(self, n: int, expected: int): - result = self.solution.climb_stairs(n) - assert result == expected diff --git a/leetcode_old/clone_graph/README.md b/leetcode_old/clone_graph/README.md deleted file mode 100644 index eeda5d5..0000000 --- a/leetcode_old/clone_graph/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# Clone Graph - -**Difficulty:** Medium -**Topics:** Hash Table, Depth-First Search, Breadth-First Search, Graph -**Tags:** grind-75 - -**LeetCode:** [Problem 133](https://leetcode.com/problems/clone-graph/description/) - -## Problem Description - -Given a reference of a node in a **connected** undirected graph. - -Return a **deep copy** (clone) of the graph. - -Each node in the graph contains a value (`int`) and a list (`List[Node]`) of its neighbors. - -``` -class Node { - public int val; - public List neighbors; -} -``` - -**Test case format:** - -For simplicity, each node's value is the same as the node's index (1-indexed). For example, the first node with `val == 1`, the second node with `val == 2`, and so on. The graph is represented in the test case using an adjacency list. - -**An adjacency list** is a collection of unordered **lists** used to represent a finite graph. Each list describes the set of neighbors of a node in the graph. - -The given node will always be the first node with `val = 1`. You must return the **copy of the given node** as a reference to the cloned graph. - -## Examples - -### Example 1: - - - -``` -Input: adjList = [[2,4],[1,3],[2,4],[1,3]] -Output: [[2,4],[1,3],[2,4],[1,3]] -Explanation: There are 4 nodes in the graph. -1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4). -2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3). -3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4). -4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3). -``` - -### Example 2: - - - -``` -Input: adjList = [[]] -Output: [[]] -Explanation: Note that the input contains one empty list. The graph consists of only one node with val = 1 and it does not have any neighbors. -``` - -### Example 3: - -``` -Input: adjList = [] -Output: [] -Explanation: This an empty graph, it does not have any nodes. -``` - -## Constraints - -- The number of nodes in the graph is in the range `[0, 100]`. -- `1 <= Node.val <= 100` -- `Node.val` is unique for each node. -- There are no repeated edges and no self-loops in the graph. -- The Graph is connected and all nodes can be visited starting from the given node. diff --git a/leetcode_old/clone_graph/__init__.py b/leetcode_old/clone_graph/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/clone_graph/playground.ipynb b/leetcode_old/clone_graph/playground.ipynb deleted file mode 100644 index 0be0e9e..0000000 --- a/leetcode_old/clone_graph/playground.ipynb +++ /dev/null @@ -1,209 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import GraphNode" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "2\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "1--2\n", - "\n", - "\n", - "\n", - "\n", - "4\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "1--4\n", - "\n", - "\n", - "\n", - "\n", - "3\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "2--3\n", - "\n", - "\n", - "\n", - "\n", - "3--4\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "GraphNode({1: [2, 4], 2: [1, 3], 3: [2, 4], 4: [1, 3]})" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Example test case\n", - "adj_list = [[2, 4], [1, 3], [2, 4], [1, 3]]\n", - "node = GraphNode.from_adjacency_list(adj_list)\n", - "node" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "2\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "1--2\n", - "\n", - "\n", - "\n", - "\n", - "4\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "1--4\n", - "\n", - "\n", - "\n", - "\n", - "3\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "2--3\n", - "\n", - "\n", - "\n", - "\n", - "3--4\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "GraphNode({1: [2, 4], 2: [1, 3], 3: [2, 4], 4: [1, 3]})" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().clone_graph(node)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result.is_clone(node) if result else node is None" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/clone_graph/solution.py b/leetcode_old/clone_graph/solution.py deleted file mode 100644 index bd53c0b..0000000 --- a/leetcode_old/clone_graph/solution.py +++ /dev/null @@ -1,73 +0,0 @@ -from collections import deque - -from leetcode_py import GraphNode - - -class Solution: - # Time: O(V + E) - # Space: O(V) - def clone_graph(self, node: GraphNode | None) -> GraphNode | None: - if node is None: - return None - - def dfs(node: GraphNode, visited: dict[int, GraphNode]): - if node.val in visited: - return visited[node.val] - - clone = GraphNode(node.val) - visited[node.val] = clone - - for neighbor in node.neighbors: - clone.neighbors.append(dfs(neighbor, visited)) - - return clone - - return dfs(node, visited={}) - - -class SolutionDFS: - # DFS Iterative - # Time: O(V + E) - # Space: O(V) - def clone_graph(self, node: GraphNode | None) -> GraphNode | None: - if node is None: - return None - - stack = [node] - visited = {node.val: GraphNode(node.val)} - - while stack: - current = stack.pop() - clone = visited[current.val] - - for neighbor in current.neighbors: - if neighbor.val not in visited: - visited[neighbor.val] = GraphNode(neighbor.val) - stack.append(neighbor) - clone.neighbors.append(visited[neighbor.val]) - - return visited[node.val] - - -class SolutionBFS: - # BFS - # Time: O(V + E) - # Space: O(V) - def clone_graph(self, node: GraphNode | None) -> GraphNode | None: - if node is None: - return None - - queue = deque([node]) - visited = {node.val: GraphNode(node.val)} - - while queue: - current = queue.popleft() - clone = visited[current.val] - - for neighbor in current.neighbors: - if neighbor.val not in visited: - visited[neighbor.val] = GraphNode(neighbor.val) - queue.append(neighbor) - clone.neighbors.append(visited[neighbor.val]) - - return visited[node.val] diff --git a/leetcode_old/clone_graph/tests.py b/leetcode_old/clone_graph/tests.py deleted file mode 100644 index fa0a211..0000000 --- a/leetcode_old/clone_graph/tests.py +++ /dev/null @@ -1,25 +0,0 @@ -import pytest - -from leetcode_py import GraphNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution, SolutionBFS, SolutionDFS - - -class TestCloneGraph: - @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS]) - @pytest.mark.parametrize( - "adj_list", - [[[2, 4], [1, 3], [2, 4], [1, 3]], [[]], []], - ) - @logged_test - def test_clone_graph( - self, - adj_list: list[list[int]], - solution_class: type[Solution | SolutionDFS | SolutionBFS], - ): - solution = solution_class() - node = GraphNode.from_adjacency_list(adj_list) - result = solution.clone_graph(node) - - assert result.is_clone(node) if result else node is None diff --git a/leetcode_old/coin_change/README.md b/leetcode_old/coin_change/README.md deleted file mode 100644 index 54b005e..0000000 --- a/leetcode_old/coin_change/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Coin Change - -**Difficulty:** Medium -**Topics:** Array, Dynamic Programming, Breadth-First Search -**Tags:** grind-75 - -**LeetCode:** [Problem 322](https://leetcode.com/problems/coin-change/description/) - -## Problem Description - -You are given an integer array `coins` representing coins of different denominations and an integer `amount` representing a total amount of money. - -Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return `-1`. - -You may assume that you have an infinite number of each kind of coin. - -## Examples - -### Example 1: - -``` -Input: coins = [1,2,5], amount = 11 -Output: 3 -``` - -**Explanation:** 11 = 5 + 5 + 1 - -### Example 2: - -``` -Input: coins = [2], amount = 3 -Output: -1 -``` - -### Example 3: - -``` -Input: coins = [1], amount = 0 -Output: 0 -``` - -## Constraints - -- `1 <= coins.length <= 12` -- `1 <= coins[i] <= 2^31 - 1` -- `0 <= amount <= 10^4` diff --git a/leetcode_old/coin_change/__init__.py b/leetcode_old/coin_change/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/coin_change/playground.ipynb b/leetcode_old/coin_change/playground.ipynb deleted file mode 100644 index 3c2d8e4..0000000 --- a/leetcode_old/coin_change/playground.ipynb +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "coins = [1, 2, 5]\n", - "amount = 11\n", - "expected = 3" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "3" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().coin_change(coins, amount)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/coin_change/solution.py b/leetcode_old/coin_change/solution.py deleted file mode 100644 index f808613..0000000 --- a/leetcode_old/coin_change/solution.py +++ /dev/null @@ -1,21 +0,0 @@ -class Solution: - # Time: O(amount * len(coins)) - # Space: O(amount) - def coin_change(self, coins: list[int], amount: int) -> int: - if amount == 0: - return 0 - - # Initialize dp array with amount + 1 (impossible value) - # Since max coins needed is amount (using all 1-cent coins) - # amount + 1 serves as "infinity" to indicate impossible cases - dp = [amount + 1] * (amount + 1) - - dp[0] = 0 - - for i in range(1, amount + 1): - for coin in coins: - if coin <= i: - dp[i] = min(dp[i], dp[i - coin] + 1) - - # Return result: -1 if impossible, otherwise minimum coins needed - return dp[amount] if dp[amount] <= amount else -1 diff --git a/leetcode_old/coin_change/tests.py b/leetcode_old/coin_change/tests.py deleted file mode 100644 index 50e7c63..0000000 --- a/leetcode_old/coin_change/tests.py +++ /dev/null @@ -1,31 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestCoinChange: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "coins, amount, expected", - [ - ([1, 2, 5], 11, 3), - ([2], 3, -1), - ([1], 0, 0), - ([1, 3, 4], 6, 2), - ([2, 5, 10, 1], 27, 4), - ([10, 1, 2, 5], 27, 4), - ([5], 3, -1), - ([5, 2], 3, -1), - ([1], 1, 1), - ([1, 2], 2, 1), - ([186, 419, 83, 408], 6249, 20), - ], - ) - @logged_test - def test_coin_change(self, coins: list[int], amount: int, expected: int): - result = self.solution.coin_change(coins, amount) - assert result == expected diff --git a/leetcode_old/combination_sum/README.md b/leetcode_old/combination_sum/README.md deleted file mode 100644 index 6d59d3e..0000000 --- a/leetcode_old/combination_sum/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Combination Sum - -**Difficulty:** Medium -**Topics:** Array, Backtracking -**Tags:** grind-75 - -**LeetCode:** [Problem 39](https://leetcode.com/problems/combination-sum/description/) - -## Problem Description - -Given an array of **distinct** integers `candidates` and a target integer `target`, return _a list of all **unique combinations** of_ `candidates` _where the chosen numbers sum to_ `target`. You may return the combinations in **any order**. - -The **same** number may be chosen from `candidates` an **unlimited number of times**. Two combinations are unique if the frequency of at least one of the chosen numbers is different. - -The test cases are generated such that the number of unique combinations that sum up to `target` is less than `150` combinations for the given input. - -## Examples - -### Example 1: - -``` -Input: candidates = [2,3,6,7], target = 7 -Output: [[2,2,3],[7]] -``` - -**Explanation:** 2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times. 7 is a candidate, and 7 = 7. These are the only two combinations. - -### Example 2: - -``` -Input: candidates = [2,3,5], target = 8 -Output: [[2,2,2,2],[2,3,3],[3,5]] -``` - -### Example 3: - -``` -Input: candidates = [2], target = 1 -Output: [] -``` - -## Constraints - -- 1 <= candidates.length <= 30 -- 2 <= candidates[i] <= 40 -- All elements of candidates are distinct. -- 1 <= target <= 40 diff --git a/leetcode_old/combination_sum/__init__.py b/leetcode_old/combination_sum/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/combination_sum/playground.ipynb b/leetcode_old/combination_sum/playground.ipynb deleted file mode 100644 index f11281f..0000000 --- a/leetcode_old/combination_sum/playground.ipynb +++ /dev/null @@ -1,74 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "candidates = [2, 3, 6, 7]\n", - "target = 7\n", - "expected = [[2, 2, 3], [7]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().combination_sum(candidates, target)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "# Sort for comparison\n", - "result_sorted = [sorted(combo) for combo in result]\n", - "expected_sorted = [sorted(combo) for combo in expected]\n", - "result_sorted.sort()\n", - "expected_sorted.sort()\n", - "assert result_sorted == expected_sorted" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/combination_sum/solution.py b/leetcode_old/combination_sum/solution.py deleted file mode 100644 index e0508a3..0000000 --- a/leetcode_old/combination_sum/solution.py +++ /dev/null @@ -1,19 +0,0 @@ -class Solution: - # Time: O(N^(T/M)) where N=len(candidates), T=target, M=min(candidates) - # Space: O(T/M) recursion + O(K * T/M) output, where K = number of solutions - def combination_sum(self, candidates: list[int], target: int) -> list[list[int]]: - result = [] - - def backtrack(start: int, path: list[int], remaining: int) -> None: - if remaining == 0: - result.append(path[:]) - return - - for i in range(start, len(candidates)): - if candidates[i] <= remaining: - path.append(candidates[i]) - backtrack(i, path, remaining - candidates[i]) - path.pop() - - backtrack(0, [], target) - return result diff --git a/leetcode_old/combination_sum/tests.py b/leetcode_old/combination_sum/tests.py deleted file mode 100644 index 200b2da..0000000 --- a/leetcode_old/combination_sum/tests.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestCombinationSum: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "candidates, target, expected", - [ - ([2, 3, 6, 7], 7, [[2, 2, 3], [7]]), - ([2, 3, 5], 8, [[2, 2, 2, 2], [2, 3, 3], [3, 5]]), - ([2], 1, []), - ([1], 1, [[1]]), - ([1], 2, [[1, 1]]), - ([2, 3, 4], 6, [[2, 2, 2], [2, 4], [3, 3]]), - ( - [7, 3, 2], - 18, - [ - [2, 2, 2, 2, 2, 2, 2, 2, 2], - [2, 2, 2, 2, 2, 2, 3, 3], - [2, 2, 2, 2, 3, 7], - [2, 2, 2, 3, 3, 3, 3], - [2, 2, 7, 7], - [2, 3, 3, 3, 7], - [3, 3, 3, 3, 3, 3], - ], - ), - ], - ) - @logged_test - def test_combination_sum(self, candidates: list[int], target: int, expected: list[list[int]]): - result = self.solution.combination_sum(candidates, target) - # Sort both result and expected for comparison - result_sorted = [sorted(combo) for combo in result] - expected_sorted = [sorted(combo) for combo in expected] - result_sorted.sort() - expected_sorted.sort() - assert result_sorted == expected_sorted diff --git a/leetcode_old/container_with_most_water/README.md b/leetcode_old/container_with_most_water/README.md deleted file mode 100644 index 78a4c7b..0000000 --- a/leetcode_old/container_with_most_water/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Container With Most Water - -**Difficulty:** Medium -**Topics:** Array, Two Pointers, Greedy -**Tags:** grind-75 - -**LeetCode:** [Problem 11](https://leetcode.com/problems/container-with-most-water/description/) - -## Problem Description - -You are given an integer array `height` of length `n`. There are `n` vertical lines drawn such that the two endpoints of the `i`th line are `(i, 0)` and `(i, height[i])`. - -Find two lines that together with the x-axis form a container, such that the container contains the most water. - -Return the maximum amount of water a container can store. - -Notice that you may not slant the container. - -## Examples - -### Example 1: - - - -``` -Input: height = [1,8,6,2,5,4,8,3,7] -Output: 49 -Explanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49. -``` - -### Example 2: - -``` -Input: height = [1,1] -Output: 1 -``` - -## Constraints - -- n == height.length -- 2 <= n <= 10^5 -- 0 <= height[i] <= 10^4 diff --git a/leetcode_old/container_with_most_water/__init__.py b/leetcode_old/container_with_most_water/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/container_with_most_water/playground.ipynb b/leetcode_old/container_with_most_water/playground.ipynb deleted file mode 100644 index 539853a..0000000 --- a/leetcode_old/container_with_most_water/playground.ipynb +++ /dev/null @@ -1,68 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "height = [1, 8, 6, 2, 5, 4, 8, 3, 7]\n", - "expected = 49" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().max_area(height)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/container_with_most_water/solution.py b/leetcode_old/container_with_most_water/solution.py deleted file mode 100644 index 34a1025..0000000 --- a/leetcode_old/container_with_most_water/solution.py +++ /dev/null @@ -1,17 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(1) - def max_area(self, height: list[int]) -> int: - left = 0 - right = len(height) - 1 - max_area_so_far = 0 - - while left < right: - area = min(height[left], height[right]) * (right - left) - max_area_so_far = max(area, max_area_so_far) - if height[right] > height[left]: - left += 1 - else: - right -= 1 - - return max_area_so_far diff --git a/leetcode_old/container_with_most_water/tests.py b/leetcode_old/container_with_most_water/tests.py deleted file mode 100644 index 2713d8d..0000000 --- a/leetcode_old/container_with_most_water/tests.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestContainerWithMostWater: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "height, expected", - [ - # Original cases - ([1, 8, 6, 2, 5, 4, 8, 3, 7], 49), - ([1, 1], 1), - ([1, 2, 1], 2), - # Edge cases - ([2, 1], 1), - ([1, 2], 1), - ([0, 2], 0), - ([2, 0], 0), - # Increasing heights - ([1, 2, 3, 4, 5], 6), - # Decreasing heights - ([5, 4, 3, 2, 1], 6), - # Same heights - ([3, 3, 3, 3], 9), - # Large differences - ([1, 1000, 1], 2), - ([1000, 1, 1000], 2000), - # Multiple peaks - ([2, 3, 4, 5, 18, 17, 6], 17), - ], - ) - @logged_test - def test_max_area(self, height: list[int], expected: int): - result = self.solution.max_area(height) - assert result == expected diff --git a/leetcode_old/contains_duplicate/README.md b/leetcode_old/contains_duplicate/README.md deleted file mode 100644 index c09c400..0000000 --- a/leetcode_old/contains_duplicate/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Contains Duplicate - -**Difficulty:** Easy -**Topics:** Array, Hash Table, Sorting -**Tags:** grind-75 - -**LeetCode:** [Problem 217](https://leetcode.com/problems/contains-duplicate/description/) - -## Problem Description - -Given an integer array `nums`, return `true` if any value appears **at least twice** in the array, and return `false` if every element is distinct. - -## Examples - -### Example 1: - -``` -Input: nums = [1,2,3,1] -Output: true -``` - -**Explanation:** The element 1 occurs at the indices 0 and 3. - -### Example 2: - -``` -Input: nums = [1,2,3,4] -Output: false -``` - -**Explanation:** All elements are distinct. - -### Example 3: - -``` -Input: nums = [1,1,1,3,3,4,3,2,4,2] -Output: true -``` - -## Constraints - -- 1 <= nums.length <= 10^5 -- -10^9 <= nums[i] <= 10^9 diff --git a/leetcode_old/contains_duplicate/__init__.py b/leetcode_old/contains_duplicate/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/contains_duplicate/playground.ipynb b/leetcode_old/contains_duplicate/playground.ipynb deleted file mode 100644 index 1cd54b5..0000000 --- a/leetcode_old/contains_duplicate/playground.ipynb +++ /dev/null @@ -1,68 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "nums = [1, 2, 3, 1]\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().contains_duplicate(nums)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/contains_duplicate/solution.py b/leetcode_old/contains_duplicate/solution.py deleted file mode 100644 index b9bd400..0000000 --- a/leetcode_old/contains_duplicate/solution.py +++ /dev/null @@ -1,10 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(n) - def contains_duplicate(self, nums: list[int]) -> bool: - seen = set() - for num in nums: - if num in seen: - return True - seen.add(num) - return False diff --git a/leetcode_old/contains_duplicate/tests.py b/leetcode_old/contains_duplicate/tests.py deleted file mode 100644 index 3a1d6cc..0000000 --- a/leetcode_old/contains_duplicate/tests.py +++ /dev/null @@ -1,32 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestContainsDuplicate: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "nums, expected", - [ - ([1, 2, 3, 1], True), - ([1, 2, 3, 4], False), - ([1, 1, 1, 3, 3, 4, 3, 2, 4, 2], True), - ([], False), - ([1], False), - ([1, 1], True), - ([-1, -2, -3, -1], True), - ([-1, -2, -3, -4], False), - ([0, 0], True), - ([1000000, 999999, 1000000], True), - (list(range(1000)), False), - ([1] * 1000, True), - ], - ) - @logged_test - def test_contains_duplicate(self, nums: list[int], expected: bool): - result = self.solution.contains_duplicate(nums) - assert result == expected diff --git a/leetcode_old/course_schedule/README.md b/leetcode_old/course_schedule/README.md deleted file mode 100644 index bdc1cdd..0000000 --- a/leetcode_old/course_schedule/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Course Schedule - -**Difficulty:** Medium -**Topics:** Depth-First Search, Breadth-First Search, Graph, Topological Sort -**Tags:** grind-75 - -**LeetCode:** [Problem 207](https://leetcode.com/problems/course-schedule/description/) - -## Problem Description - -There are a total of `numCourses` courses you have to take, labeled from `0` to `numCourses - 1`. You are given an array `prerequisites` where `prerequisites[i] = [ai, bi]` indicates that you **must** take course `bi` first if you want to take course `ai`. - -- For example, the pair `[0, 1]`, indicates that to take course `0` you have to first take course `1`. - -Return `true` if you can finish all courses. Otherwise, return `false`. - -## Examples - -### Example 1: - -``` -Input: numCourses = 2, prerequisites = [[1,0]] -Output: true -``` - -**Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0. So it is possible. - -### Example 2: - -``` -Input: numCourses = 2, prerequisites = [[1,0],[0,1]] -Output: false -``` - -**Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0, and to take course 0 you should also have finished course 1. So it is impossible. - -## Constraints - -- `1 <= numCourses <= 2000` -- `0 <= prerequisites.length <= 5000` -- `prerequisites[i].length == 2` -- `0 <= ai, bi < numCourses` -- All the pairs prerequisites[i] are **unique**. diff --git a/leetcode_old/course_schedule/__init__.py b/leetcode_old/course_schedule/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/course_schedule/playground.ipynb b/leetcode_old/course_schedule/playground.ipynb deleted file mode 100644 index 47eca12..0000000 --- a/leetcode_old/course_schedule/playground.ipynb +++ /dev/null @@ -1,69 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "num_courses = 2\n", - "prerequisites = [[1, 0]]\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().can_finish(num_courses, prerequisites)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/course_schedule/solution.py b/leetcode_old/course_schedule/solution.py deleted file mode 100644 index 54cf72e..0000000 --- a/leetcode_old/course_schedule/solution.py +++ /dev/null @@ -1,29 +0,0 @@ -class Solution: - # Time: O(V + E) where V = num_courses, E = prerequisites - # Space: O(V + E) for adjacency list and recursion stack - def can_finish(self, num_courses: int, prerequisites: list[list[int]]) -> bool: - UNVISITED, VISITING, VISITED = 0, 1, 2 - - graph: list[list[int]] = [[] for _ in range(num_courses)] - for course, prereq in prerequisites: - graph[course].append(prereq) - - state = [UNVISITED] * num_courses - - def has_cycle(course: int) -> bool: - if state[course] == VISITING: # Currently visiting - cycle detected - return True - if state[course] == VISITED: - return False - - state[course] = VISITING - for prereq in graph[course]: - if has_cycle(prereq): - return True - state[course] = VISITED - return False - - for course in range(num_courses): - if state[course] == UNVISITED and has_cycle(course): - return False - return True diff --git a/leetcode_old/course_schedule/tests.py b/leetcode_old/course_schedule/tests.py deleted file mode 100644 index 5eb8d60..0000000 --- a/leetcode_old/course_schedule/tests.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestCourseSchedule: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "num_courses, prerequisites, expected", - [ - # Basic cases - (2, [[1, 0]], True), - (2, [[1, 0], [0, 1]], False), - (1, [], True), - (3, [[1, 0], [2, 1]], True), - (4, [[1, 0], [2, 1], [3, 2], [1, 3]], False), - # Edge cases - (0, [], True), - (5, [], True), - (3, [[0, 1], [0, 2], [1, 2]], True), - # Self-loop - (2, [[0, 0]], False), - (3, [[1, 1]], False), - # Complex valid cases - (6, [[1, 0], [2, 0], [3, 1], [3, 2], [4, 3], [5, 4]], True), - (4, [[0, 1], [0, 2], [1, 3], [2, 3]], True), - # Complex cycles - (3, [[0, 1], [1, 2], [2, 0]], False), - (5, [[1, 0], [2, 1], [3, 2], [4, 3], [0, 4]], False), - (4, [[1, 0], [2, 0], [0, 3], [3, 1]], False), - ], - ) - @logged_test - def test_can_finish(self, num_courses: int, prerequisites: list[list[int]], expected: bool): - result = self.solution.can_finish(num_courses, prerequisites) - assert result == expected diff --git a/leetcode_old/diameter_of_binary_tree/README.md b/leetcode_old/diameter_of_binary_tree/README.md deleted file mode 100644 index 4b1580b..0000000 --- a/leetcode_old/diameter_of_binary_tree/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Diameter of Binary Tree - -**Difficulty:** Easy -**Topics:** Tree, Depth-First Search, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 543](https://leetcode.com/problems/diameter-of-binary-tree/description/) - -## Problem Description - -Given the `root` of a binary tree, return the length of the **diameter** of the tree. - -The **diameter** of a binary tree is the **length** of the longest path between any two nodes in a tree. This path may or may not pass through the `root`. - -The **length** of a path between two nodes is represented by the number of edges between them. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2021/03/06/diamtree.jpg) - -``` -Input: root = [1,2,3,4,5] -Output: 3 -``` - -**Explanation:** 3 is the length of the path [4,2,1,3] or [5,2,1,3]. - -### Example 2: - -``` -Input: root = [1,2] -Output: 1 -``` - -## Constraints - -- The number of nodes in the tree is in the range [1, 10^4]. -- -100 <= Node.val <= 100 diff --git a/leetcode_old/diameter_of_binary_tree/__init__.py b/leetcode_old/diameter_of_binary_tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/diameter_of_binary_tree/playground.ipynb b/leetcode_old/diameter_of_binary_tree/playground.ipynb deleted file mode 100644 index 4821880..0000000 --- a/leetcode_old/diameter_of_binary_tree/playground.ipynb +++ /dev/null @@ -1,171 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import TreeNode" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "root_list: list[int | None] = [1, 2, 3, 4, 5]\n", - "expected = 3" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "3" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "root = TreeNode.from_list(root_list)\n", - "result = Solution().diameter_of_binary_tree(root)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "248f3295", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "0->1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "4\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "0->4\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "2\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "1->2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "3\n", - "\n", - "5\n", - "\n", - "\n", - "\n", - "1->3\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "TreeNode([1, 2, 3, 4, 5])" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "root" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/diameter_of_binary_tree/solution.py b/leetcode_old/diameter_of_binary_tree/solution.py deleted file mode 100644 index bd0cbd2..0000000 --- a/leetcode_old/diameter_of_binary_tree/solution.py +++ /dev/null @@ -1,22 +0,0 @@ -from leetcode_py import TreeNode - - -class Solution: - # Time: O(n) - # Space: O(h) - def diameter_of_binary_tree(self, root: TreeNode | None) -> int: - self.max_diameter = 0 - - def dfs(node: TreeNode | None) -> int: - if not node: - return 0 - - left = dfs(node.left) - right = dfs(node.right) - - self.max_diameter = max(self.max_diameter, left + right) - - return max(left, right) + 1 - - dfs(root) - return self.max_diameter diff --git a/leetcode_old/diameter_of_binary_tree/tests.py b/leetcode_old/diameter_of_binary_tree/tests.py deleted file mode 100644 index 914bab5..0000000 --- a/leetcode_old/diameter_of_binary_tree/tests.py +++ /dev/null @@ -1,31 +0,0 @@ -import pytest - -from leetcode_py import TreeNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestDiameterOfBinaryTree: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "root_list, expected", - [ - ([1, 2, 3, 4, 5], 3), - ([1, 2], 1), - ([1], 0), - ([], 0), - ([1, 2, 3, 4, 5, None, None, 6, 7], 4), - ([1, None, 2, None, 3, None, 4], 3), - ([1, 2, None, 3, None, 4], 3), - ([1, 2, 3], 2), - ([1, 2, 3, 4, None, None, 5], 4), - ], - ) - @logged_test - def test_diameter_of_binary_tree(self, root_list: list[int | None], expected: int): - root = TreeNode.from_list(root_list) - result = self.solution.diameter_of_binary_tree(root) - assert result == expected diff --git a/leetcode_old/evaluate_reverse_polish_notation/README.md b/leetcode_old/evaluate_reverse_polish_notation/README.md deleted file mode 100644 index 4015f66..0000000 --- a/leetcode_old/evaluate_reverse_polish_notation/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Evaluate Reverse Polish Notation - -**Difficulty:** Medium -**Topics:** Array, Math, Stack -**Tags:** grind-75 - -**LeetCode:** [Problem 150](https://leetcode.com/problems/evaluate-reverse-polish-notation/description/) - -## Problem Description - -You are given an array of strings `tokens` that represents an arithmetic expression in a **Reverse Polish Notation**. - -Evaluate the expression. Return _an integer that represents the value of the expression_. - -**Note that:** - -- The valid operators are `'+'`, `'-'`, `'*'`, and `'/'`. -- Each operand may be an integer or another expression. -- The division between two integers always **truncates toward zero**. -- There will not be any division by zero. -- The input represents a valid arithmetic expression in a reverse polish notation. -- The answer and all the intermediate calculations can be represented in a **32-bit** integer. - -## Examples - -### Example 1: - -``` -Input: tokens = ["2","1","+","3","*"] -Output: 9 -``` - -**Explanation:** ((2 + 1) \* 3) = 9 - -### Example 2: - -``` -Input: tokens = ["4","13","5","/","+"] -Output: 6 -``` - -**Explanation:** (4 + (13 / 5)) = 6 - -### Example 3: - -``` -Input: tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"] -Output: 22 -``` - -**Explanation:** ((10 _ (6 / ((9 + 3) _ -11))) + 17) + 5 = 22 - -## Constraints - -- `1 <= tokens.length <= 10^4` -- `tokens[i]` is either an operator: `"+"`, `"-"`, `"*"`, or `"/"`, or an integer in the range `[-200, 200]`. diff --git a/leetcode_old/evaluate_reverse_polish_notation/__init__.py b/leetcode_old/evaluate_reverse_polish_notation/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/evaluate_reverse_polish_notation/playground.ipynb b/leetcode_old/evaluate_reverse_polish_notation/playground.ipynb deleted file mode 100644 index fc7ba18..0000000 --- a/leetcode_old/evaluate_reverse_polish_notation/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "tokens = [\"2\", \"1\", \"+\", \"3\", \"*\"]\n", - "expected = 9" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "9" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().eval_rpn(tokens)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/evaluate_reverse_polish_notation/solution.py b/leetcode_old/evaluate_reverse_polish_notation/solution.py deleted file mode 100644 index b3b2ea1..0000000 --- a/leetcode_old/evaluate_reverse_polish_notation/solution.py +++ /dev/null @@ -1,20 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(n) - def eval_rpn(self, tokens: list[str]) -> int: - stack: list[int] = [] - ops = { - "+": lambda a, b: a + b, - "-": lambda a, b: a - b, - "*": lambda a, b: a * b, - "/": lambda a, b: int(a / b), - } - - for token in tokens: - if token in ops: - b, a = stack.pop(), stack.pop() - stack.append(ops[token](a, b)) - else: - stack.append(int(token)) - - return stack[0] diff --git a/leetcode_old/evaluate_reverse_polish_notation/tests.py b/leetcode_old/evaluate_reverse_polish_notation/tests.py deleted file mode 100644 index f353e1f..0000000 --- a/leetcode_old/evaluate_reverse_polish_notation/tests.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestEvaluateReversePolishNotation: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "tokens, expected", - [ - # Original cases - (["2", "1", "+", "3", "*"], 9), - (["4", "13", "5", "/", "+"], 6), - (["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"], 22), - # Single number - (["42"], 42), - # Negative numbers - (["-1"], -1), - (["1", "-1", "+"], 0), - # Basic operations - (["3", "4", "+"], 7), - (["5", "2", "-"], 3), - (["6", "3", "*"], 18), - (["8", "2", "/"], 4), - # Division with negatives - (["-3", "4", "+", "2", "*", "1", "-"], 1), - # Complex expression - (["15", "7", "1", "1", "+", "/", "/", "3", "*", "2", "1", "1", "+", "+", "-"], 11), - ], - ) - @logged_test - def test_eval_rpn(self, tokens: list[str], expected: int): - result = self.solution.eval_rpn(tokens) - assert result == expected diff --git a/leetcode_old/find_median_from_data_stream/README.md b/leetcode_old/find_median_from_data_stream/README.md deleted file mode 100644 index 90ace29..0000000 --- a/leetcode_old/find_median_from_data_stream/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Find Median from Data Stream - -**Difficulty:** Hard -**Topics:** Two Pointers, Design, Sorting, Heap (Priority Queue), Data Stream -**Tags:** grind-75 - -**LeetCode:** [Problem 295](https://leetcode.com/problems/find-median-from-data-stream/description/) - -## Problem Description - -The **median** is the middle value in an ordered integer list. If the size of the list is even, there is no middle value, and the median is the mean of the two middle values. - -- For example, for `arr = [2,3,4]`, the median is `3`. -- For example, for `arr = [2,3]`, the median is `(2 + 3) / 2 = 2.5`. - -Implement the MedianFinder class: - -- `MedianFinder()` initializes the `MedianFinder` object. -- `void addNum(int num)` adds the integer `num` from the data stream to the data structure. -- `double findMedian()` returns the median of all elements so far. Answers within `10^-5` of the actual answer will be accepted. - -## Examples - -### Example 1: - -``` -Input -["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"] -[[], [1], [2], [], [3], []] -Output -[null, null, null, 1.5, null, 2.0] -``` - -**Explanation:** - -``` -MedianFinder medianFinder = new MedianFinder(); -medianFinder.addNum(1); // arr = [1] -medianFinder.addNum(2); // arr = [1, 2] -medianFinder.findMedian(); // return 1.5 (i.e., (1 + 2) / 2) -medianFinder.addNum(3); // arr = [1, 2, 3] -medianFinder.findMedian(); // return 2.0 -``` - -## Constraints - -- `-10^5 <= num <= 10^5` -- There will be at least one element in the data structure before calling `findMedian`. -- At most `5 * 10^4` calls will be made to `addNum` and `findMedian`. - -**Follow up:** - -- If all integer numbers from the stream are in the range `[0, 100]`, how would you optimize your solution? -- If `99%` of all integer numbers from the stream are in the range `[0, 100]`, how would you optimize your solution? diff --git a/leetcode_old/find_median_from_data_stream/__init__.py b/leetcode_old/find_median_from_data_stream/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/find_median_from_data_stream/playground.ipynb b/leetcode_old/find_median_from_data_stream/playground.ipynb deleted file mode 100644 index 3562233..0000000 --- a/leetcode_old/find_median_from_data_stream/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import MedianFinder" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "operations = [\"MedianFinder\", \"addNum\", \"addNum\", \"findMedian\", \"addNum\", \"findMedian\"]\n", - "inputs = [[], [1], [2], [], [3], []]\n", - "expected = [None, None, None, 1.5, None, 2.0]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "mf = None\n", - "results: list[float | None] = []\n", - "for i, op in enumerate(operations):\n", - " if op == \"MedianFinder\":\n", - " mf = MedianFinder()\n", - " results.append(None)\n", - " elif op == \"addNum\" and mf is not None:\n", - " mf.add_num(inputs[i][0])\n", - " results.append(None)\n", - " elif op == \"findMedian\" and mf is not None:\n", - " results.append(mf.find_median())\n", - "results" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert results == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/find_median_from_data_stream/solution.py b/leetcode_old/find_median_from_data_stream/solution.py deleted file mode 100644 index af1924e..0000000 --- a/leetcode_old/find_median_from_data_stream/solution.py +++ /dev/null @@ -1,115 +0,0 @@ -import heapq - - -class MedianFinder: - # Two balanced heaps approach for general streaming median - # Time: O(1) init - # Space: O(n) - def __init__(self) -> None: - self.small: list[int] = [] # max heap (negated) - self.large: list[int] = [] # min heap - - # Time: O(log n) - # Space: O(1) - def add_num(self, num: int) -> None: - heapq.heappush(self.small, -num) - - if self.small and self.large and (-self.small[0] > self.large[0]): - heapq.heappush(self.large, -heapq.heappop(self.small)) - - if len(self.small) > len(self.large) + 1: - heapq.heappush(self.large, -heapq.heappop(self.small)) - if len(self.large) > len(self.small) + 1: - heapq.heappush(self.small, -heapq.heappop(self.large)) - - # Time: O(1) - # Space: O(1) - def find_median(self) -> float: - if len(self.small) > len(self.large): - return -self.small[0] - if len(self.large) > len(self.small): - return self.large[0] - return (-self.small[0] + self.large[0]) / 2.0 - - -class MedianFinderHybrid: - # Hybrid counting array + heaps for bounded ranges with outliers - # Time: O(1) init - # Space: O(R + k) where R = range_size, k = outliers - def __init__(self, min_val: int = 0, max_val: int = 100) -> None: - self.min_val = min_val - self.max_val = max_val - self.counts = [0] * (max_val - min_val + 1) - self.outliers_small: list[int] = [] # max heap for < min_val - self.outliers_large: list[int] = [] # min heap for > max_val - self.total = 0 - - # Time: O(1) for range, O(log k) for outliers - # Space: O(1) - def add_num(self, num: int) -> None: - if self.min_val <= num <= self.max_val: - self.counts[num - self.min_val] += 1 - elif num < self.min_val: - heapq.heappush(self.outliers_small, -num) - else: - heapq.heappush(self.outliers_large, num) - self.total += 1 - - # Time: O(R + k log k) worst case, O(R) typical, O(1) if R constant - # Space: O(k) for sorting outliers - def find_median(self) -> float: - target = self.total // 2 - count = 0 - - # Count outliers < 0 - outliers_small_count = len(self.outliers_small) - if count + outliers_small_count > target: - sorted_small = sorted([-x for x in self.outliers_small]) - if self.total % 2 == 1: - return sorted_small[target - count] - else: - if target - count == 0: - return (sorted_small[0] + self._get_next_value(0)) / 2.0 - return (sorted_small[target - count - 1] + sorted_small[target - count]) / 2.0 - count += outliers_small_count - - # Count [min_val, max_val] range - for i in range(len(self.counts)): - if count + self.counts[i] > target: - val = i + self.min_val - if self.total % 2 == 1: - return val - else: - if target == count: - return (self._get_prev_value(count - 1) + val) / 2.0 - return val - count += self.counts[i] - - # Must be in outliers > 100 - sorted_large = sorted(self.outliers_large) - idx = target - count - if self.total % 2 == 1: - return sorted_large[idx] - else: - if idx == 0: - return (self._get_prev_value(count - 1) + sorted_large[0]) / 2.0 - return (sorted_large[idx - 1] + sorted_large[idx]) / 2.0 - - def _get_prev_value(self, pos: int) -> int: - count = 0 - # Check outliers < 0 - if pos < len(self.outliers_small): - return sorted([-x for x in self.outliers_small])[pos] - count += len(self.outliers_small) - - # Check [min_val, max_val] range - for i in range(len(self.counts)): - if count + self.counts[i] > pos: - return i + self.min_val - count += self.counts[i] - - # Must be in outliers > 100 - return sorted(self.outliers_large)[pos - count] - - def _get_next_value(self, pos: int) -> int: - return self._get_prev_value(pos + 1) diff --git a/leetcode_old/find_median_from_data_stream/tests.py b/leetcode_old/find_median_from_data_stream/tests.py deleted file mode 100644 index 0a07039..0000000 --- a/leetcode_old/find_median_from_data_stream/tests.py +++ /dev/null @@ -1,71 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import MedianFinder, MedianFinderHybrid - - -class TestFindMedianFromDataStream: - @pytest.mark.parametrize( - "finder_class, operations, inputs, expected", - [ - ( - MedianFinder, - ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"], - [[], [1], [2], [], [3], []], - [None, None, None, 1.5, None, 2.0], - ), - ( - MedianFinderHybrid, - ["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"], - [[], [1], [2], [], [3], []], - [None, None, None, 1.5, None, 2.0], - ), - ( - MedianFinder, - ["MedianFinder", "addNum", "findMedian"], - [[], [5], []], - [None, None, 5.0], - ), - ( - MedianFinderHybrid, - ["MedianFinder", "addNum", "findMedian"], - [[], [5], []], - [None, None, 5.0], - ), - ( - MedianFinder, - ["MedianFinder", "addNum", "addNum", "addNum", "addNum", "findMedian"], - [[], [1], [3], [2], [4], []], - [None, None, None, None, None, 2.5], - ), - ( - MedianFinderHybrid, - ["MedianFinder", "addNum", "addNum", "addNum", "addNum", "findMedian"], - [[], [1], [3], [2], [4], []], - [None, None, None, None, None, 2.5], - ), - ( - MedianFinderHybrid, - ["MedianFinder", "addNum", "addNum", "addNum", "findMedian"], - [[], [-1], [50], [101], []], - [None, None, None, None, 50.0], - ), - ], - ) - @logged_test - def test_median_finder( - self, finder_class, operations: list[str], inputs: list[list[int]], expected: list[float | None] - ): - mf = None - results: list[float | None] = [] - for i, op in enumerate(operations): - if op == "MedianFinder": - mf = finder_class() - results.append(None) - elif op == "addNum" and mf is not None: - mf.add_num(inputs[i][0]) - results.append(None) - elif op == "findMedian" and mf is not None: - results.append(mf.find_median()) - assert results == expected diff --git a/leetcode_old/first_bad_version/README.md b/leetcode_old/first_bad_version/README.md deleted file mode 100644 index a710eb0..0000000 --- a/leetcode_old/first_bad_version/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# First Bad Version - -**Difficulty:** Easy -**Topics:** Binary Search, Interactive -**Tags:** grind-75 - -**LeetCode:** [Problem 278](https://leetcode.com/problems/first-bad-version/description/) - -## Problem Description - -You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad. - -Suppose you have `n` versions `[1, 2, ..., n]` and you want to find out the first bad one, which causes all the following ones to be bad. - -You are given an API `bool isBadVersion(version)` which returns whether `version` is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API. - -## Examples - -### Example 1: - -``` -Input: n = 5, bad = 4 -Output: 4 -``` - -**Explanation:** - -``` -call isBadVersion(3) -> false -call isBadVersion(5) -> true -call isBadVersion(4) -> true -``` - -Then 4 is the first bad version. - -### Example 2: - -``` -Input: n = 1, bad = 1 -Output: 1 -``` - -## Constraints - -- 1 <= bad <= n <= 2^31 - 1 - -**Note:** The `isBadVersion` API is already defined for you. diff --git a/leetcode_old/first_bad_version/__init__.py b/leetcode_old/first_bad_version/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/first_bad_version/playground.ipynb b/leetcode_old/first_bad_version/playground.ipynb deleted file mode 100644 index c0d9e87..0000000 --- a/leetcode_old/first_bad_version/playground.ipynb +++ /dev/null @@ -1,81 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "n = 5\n", - "bad = 4\n", - "expected = 4" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "4" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "solution = Solution(first_bad=bad)\n", - "result = solution.first_bad_version(n)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/first_bad_version/solution.py b/leetcode_old/first_bad_version/solution.py deleted file mode 100644 index 7fa3f49..0000000 --- a/leetcode_old/first_bad_version/solution.py +++ /dev/null @@ -1,45 +0,0 @@ -class Solution: - # TODO: template constraint - def __init__(self, first_bad): - self.is_bad_version = lambda version: version >= first_bad - - # Time: O(log n) - # Space: O(1) - def first_bad_version(self, n: int) -> int: - left = 1 - right = n - - while left < right: - mid = (left + right) // 2 - if self.is_bad_version(mid): - right = mid - else: - left = mid + 1 - - return right - - -# BISECT PATTERNS - General Binary Search -# Given: arr = [10,20,30,30,30,40,50], target = 30 -# 0 1 2 3 4 5 6 -# -# bisect_left: Find FIRST occurrence (leftmost insertion point) -# while left < right: -# if arr[mid] >= target: # >= keeps moving left -# right = mid -# Returns: 2 (index of first 30, value=30) -# [10,20,30,30,30,40,50] -# 0 1 2 3 4 5 6 -# ↑ index 2 -# -# bisect_right: Find position AFTER last occurrence -# while left < right: -# if arr[mid] > target: # > allows equal values -# right = mid -# Returns: 5 (index after last 30, value=40) -# [10,20,30,30,30,40,50] -# 0 1 2 3 4 5 6 -# ↑ index 5 -# -# Key difference: >= vs > in the condition -# This problem uses bisect_left pattern to find first bad version diff --git a/leetcode_old/first_bad_version/tests.py b/leetcode_old/first_bad_version/tests.py deleted file mode 100644 index 570373b..0000000 --- a/leetcode_old/first_bad_version/tests.py +++ /dev/null @@ -1,17 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestFirstBadVersion: - - @pytest.mark.parametrize( - "n, bad, expected", [(5, 4, 4), (1, 1, 1), (3, 1, 1), (10, 7, 7), (5, -1, 1)] - ) - @logged_test - def test_first_bad_version(self, n: int, bad: int, expected: int): - solution = Solution(first_bad=bad) - result = solution.first_bad_version(n) - assert result == expected diff --git a/leetcode_old/flood_fill/README.md b/leetcode_old/flood_fill/README.md deleted file mode 100644 index 24f808f..0000000 --- a/leetcode_old/flood_fill/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Flood Fill - -**Difficulty:** Easy -**Topics:** Array, Depth-First Search, Breadth-First Search, Matrix -**Tags:** grind-75 - -**LeetCode:** [Problem 733](https://leetcode.com/problems/flood-fill/description/) - -## Problem Description - -You are given an image represented by an `m x n` grid of integers `image`, where `image[i][j]` represents the pixel value of the image. You are also given three integers `sr`, `sc`, and `color`. Your task is to perform a **flood fill** on the image starting from the pixel `image[sr][sc]`. - -To perform a **flood fill**: - -1. Begin with the starting pixel and change its color to `color`. -2. Perform the same process for each pixel that is **directly adjacent** (pixels that share a side with the original pixel, either horizontally or vertically) and shares the **same color** as the starting pixel. -3. Keep **repeating** this process by checking neighboring pixels of the _updated_ pixels and modifying their color if it matches the original color of the starting pixel. -4. The process **stops** when there are **no more** adjacent pixels of the original color to update. - -Return the **modified** image after performing the flood fill. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2021/06/01/flood1-grid.jpg) - -``` -Input: image = [[1,1,1],[1,1,0],[1,0,1]], sr = 1, sc = 1, color = 2 -Output: [[2,2,2],[2,2,0],[2,0,1]] -``` - -**Explanation:** From the center of the image with position `(sr, sc) = (1, 1)` (i.e., the red pixel), all pixels connected by a path of the same color as the starting pixel (i.e., the blue pixels) are colored with the new color. Note the bottom corner is not colored 2, because it is not horizontally or vertically connected to the starting pixel. - -### Example 2: - -``` -Input: image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, color = 0 -Output: [[0,0,0],[0,0,0]] -``` - -**Explanation:** The starting pixel is already colored with 0, which is the same as the target color. Therefore, no changes are made to the image. - -## Constraints - -- `m == image.length` -- `n == image[i].length` -- `1 <= m, n <= 50` -- `0 <= image[i][j], color < 2^16` -- `0 <= sr < m` -- `0 <= sc < n` diff --git a/leetcode_old/flood_fill/__init__.py b/leetcode_old/flood_fill/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/flood_fill/playground.ipynb b/leetcode_old/flood_fill/playground.ipynb deleted file mode 100644 index 26e6d32..0000000 --- a/leetcode_old/flood_fill/playground.ipynb +++ /dev/null @@ -1,71 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "image = [[1, 1, 1], [1, 1, 0], [1, 0, 1]]\n", - "sr = 1\n", - "sc = 1\n", - "color = 2\n", - "expected = [[2, 2, 2], [2, 2, 0], [2, 0, 1]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().flood_fill(image, sr, sc, color)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/flood_fill/solution.py b/leetcode_old/flood_fill/solution.py deleted file mode 100644 index 897ac71..0000000 --- a/leetcode_old/flood_fill/solution.py +++ /dev/null @@ -1,17 +0,0 @@ -class Solution: - # Time: O(m*n) - # Space: O(m*n) - def flood_fill(self, image: list[list[int]], sr: int, sc: int, color: int) -> list[list[int]]: - original = image[sr][sc] - if original == color: - return image - - def dfs(r: int, c: int) -> None: - if r < 0 or r >= len(image) or c < 0 or c >= len(image[0]) or image[r][c] != original: - return - image[r][c] = color - for dr, dc in [(1, 0), (-1, 0), (0, 1), (0, -1)]: - dfs(r + dr, c + dc) - - dfs(sr, sc) - return image diff --git a/leetcode_old/flood_fill/tests.py b/leetcode_old/flood_fill/tests.py deleted file mode 100644 index fdb3209..0000000 --- a/leetcode_old/flood_fill/tests.py +++ /dev/null @@ -1,26 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestFloodFill: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "image, sr, sc, color, expected", - [ - ([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 2, [[2, 2, 2], [2, 2, 0], [2, 0, 1]]), - ([[0, 0, 0], [0, 0, 0]], 0, 0, 0, [[0, 0, 0], [0, 0, 0]]), - ([[0, 0, 0], [0, 1, 1]], 1, 1, 1, [[0, 0, 0], [0, 1, 1]]), - ([[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 1, [[1, 1, 1], [1, 1, 0], [1, 0, 1]]), - ], - ) - @logged_test - def test_flood_fill( - self, image: list[list[int]], sr: int, sc: int, color: int, expected: list[list[int]] - ): - result = self.solution.flood_fill(image, sr, sc, color) - assert result == expected diff --git a/leetcode_old/implement_queue_using_stacks/README.md b/leetcode_old/implement_queue_using_stacks/README.md deleted file mode 100644 index a1e659f..0000000 --- a/leetcode_old/implement_queue_using_stacks/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Implement Queue using Stacks - -**Difficulty:** Easy -**Topics:** Stack, Design, Queue -**Tags:** grind-75 - -**LeetCode:** [Problem 232](https://leetcode.com/problems/implement-queue-using-stacks/description/) - -## Problem Description - -Implement a first in first out (FIFO) queue using only two stacks. The implemented queue should support all the functions of a normal queue (`push`, `peek`, `pop`, and `empty`). - -Implement the `MyQueue` class: - -- `void push(int x)` Pushes element x to the back of the queue. -- `int pop()` Removes the element from the front of the queue and returns it. -- `int peek()` Returns the element at the front of the queue. -- `boolean empty()` Returns `true` if the queue is empty, `false` otherwise. - -## Examples - -### Example 1: - -``` -Input -["MyQueue", "push", "push", "peek", "pop", "empty"] -[[], [1], [2], [], [], []] -Output -[null, null, null, 1, 1, false] -``` - -**Explanation:** - -``` -MyQueue myQueue = new MyQueue(); -myQueue.push(1); // queue is: [1] -myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue) -myQueue.peek(); // return 1 -myQueue.pop(); // return 1, queue is [2] -myQueue.empty(); // return false -``` - -## Constraints - -- 1 <= x <= 9 -- At most 100 calls will be made to push, pop, peek, and empty. -- All the calls to pop and peek are valid. - -**Notes:** - -- You must use **only** standard operations of a stack, which means only `push to top`, `peek/pop from top`, `size`, and `is empty` operations are valid. -- Depending on your language, the stack may not be supported natively. You may simulate a stack using a list or deque (double-ended queue) as long as you use only a stack's standard operations. - -**Follow-up:** Can you implement the queue such that each operation is amortized `O(1)` time complexity? In other words, performing `n` operations will take overall `O(n)` time even if one of those operations may take longer. diff --git a/leetcode_old/implement_queue_using_stacks/__init__.py b/leetcode_old/implement_queue_using_stacks/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/implement_queue_using_stacks/playground.ipynb b/leetcode_old/implement_queue_using_stacks/playground.ipynb deleted file mode 100644 index 5430378..0000000 --- a/leetcode_old/implement_queue_using_stacks/playground.ipynb +++ /dev/null @@ -1,81 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 2, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import MyQueue" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "queue = MyQueue()\n", - "queue.push(1)\n", - "queue.push(2)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "peek: 1, pop: 1, empty: False\n" - ] - } - ], - "source": [ - "result_peek = queue.peek()\n", - "result_pop = queue.pop()\n", - "result_empty = queue.empty()\n", - "print(f\"peek: {result_peek}, pop: {result_pop}, empty: {result_empty}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result_peek == 1\n", - "assert result_pop == 1\n", - "assert not result_empty" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/implement_queue_using_stacks/solution.py b/leetcode_old/implement_queue_using_stacks/solution.py deleted file mode 100644 index 71581ee..0000000 --- a/leetcode_old/implement_queue_using_stacks/solution.py +++ /dev/null @@ -1,51 +0,0 @@ -class MyQueue: - # Time: O(1) - # Space: O(n) - def __init__(self) -> None: - self.input_stack: list[int] = [] - self.output_stack: list[int] = [] - - # Time: O(1) - # Space: O(1) - def push(self, x: int) -> None: - self.input_stack.append(x) - - # Time: O(1) amortized - # Space: O(1) - def pop(self) -> int: - self._move_to_output() - return self.output_stack.pop() - - # Time: O(1) amortized - # Space: O(1) - def peek(self) -> int: - self._move_to_output() - return self.output_stack[-1] - - # Time: O(1) - # Space: O(1) - def empty(self) -> bool: - return not self.input_stack and not self.output_stack - - def _move_to_output(self) -> None: - if not self.output_stack: - while self.input_stack: - self.output_stack.append(self.input_stack.pop()) - - -# Amortized O(1) Explanation: -# Example with 4 push + 4 pop operations: -# -# push(1) # input: [1], output: [] - O(1) -# push(2) # input: [1,2], output: [] - O(1) -# push(3) # input: [1,2,3], output: [] - O(1) -# push(4) # input: [1,2,3,4], output: [] - O(1) -# -# pop() # Move all 4 to output: input: [], output: [4,3,2,1] then pop 1 - O(4) -# pop() # output: [4,3,2], just pop 2 - O(1) -# pop() # output: [4,3], just pop 3 - O(1) -# pop() # output: [4], just pop 4 - O(1) -# -# Total cost: 4 + 4 + 1 + 1 + 1 = 11 operations for 8 calls = 1.4 per operation -# Key: Each element moves exactly once from input to output, so expensive O(n) -# transfer is "spread out" over multiple cheap O(1) operations = amortized O(1) diff --git a/leetcode_old/implement_queue_using_stacks/tests.py b/leetcode_old/implement_queue_using_stacks/tests.py deleted file mode 100644 index 519121f..0000000 --- a/leetcode_old/implement_queue_using_stacks/tests.py +++ /dev/null @@ -1,48 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import MyQueue - - -class TestImplementQueueUsingStacks: - @pytest.mark.parametrize( - "operations, inputs, expected", - [ - ( - ["MyQueue", "push", "push", "peek", "pop", "empty"], - [[], [1], [2], [], [], []], - [None, None, None, 1, 1, False], - ), - ( - ["MyQueue", "empty", "push", "peek", "pop", "empty"], - [[], [], [1], [], [], []], - [None, True, None, 1, 1, True], - ), - ( - ["MyQueue", "push", "push", "push", "pop", "pop", "peek", "pop", "empty"], - [[], [1], [2], [3], [], [], [], [], []], - [None, None, None, None, 1, 2, 3, 3, True], - ), - ], - ) - @logged_test - def test_queue_operations( - self, operations: list[str], inputs: list[list[int]], expected: list[int | None | bool] - ): - queue = None - results: list[int | None | bool] = [] - for i, op in enumerate(operations): - if op == "MyQueue": - queue = MyQueue() - results.append(None) - elif op == "push" and queue is not None: - queue.push(inputs[i][0]) - results.append(None) - elif op == "pop" and queue is not None: - results.append(queue.pop()) - elif op == "peek" and queue is not None: - results.append(queue.peek()) - elif op == "empty" and queue is not None: - results.append(queue.empty()) - assert results == expected diff --git a/leetcode_old/implement_trie_prefix_tree/README.md b/leetcode_old/implement_trie_prefix_tree/README.md deleted file mode 100644 index 7e0cd3c..0000000 --- a/leetcode_old/implement_trie_prefix_tree/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Implement Trie (Prefix Tree) - -**Difficulty:** Medium -**Topics:** Hash Table, String, Design, Trie -**Tags:** grind-75 - -**LeetCode:** [Problem 208](https://leetcode.com/problems/implement-trie-prefix-tree/description/) - -## Problem Description - -A **trie** (pronounced as "try") or **prefix tree** is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker. - -Implement the Trie class: - -- `Trie()` Initializes the trie object. -- `void insert(String word)` Inserts the string `word` into the trie. -- `boolean search(String word)` Returns `true` if the string `word` is in the trie (i.e., was inserted before), and `false` otherwise. -- `boolean startsWith(String prefix)` Returns `true` if there is a previously inserted string `word` that has the prefix `prefix`, and `false` otherwise. - -## Examples - -### Example 1: - -``` -Input -["Trie", "insert", "search", "search", "startsWith", "insert", "search"] -[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]] -Output -[null, null, true, false, true, null, true] -``` - -**Explanation:** - -```python -trie = Trie() -trie.insert("apple") -trie.search("apple") # return True -trie.search("app") # return False -trie.starts_with("app") # return True -trie.insert("app") -trie.search("app") # return True -``` - -## Constraints - -- `1 <= word.length, prefix.length <= 2000` -- `word` and `prefix` consist only of lowercase English letters. -- At most `3 * 10^4` calls **in total** will be made to `insert`, `search`, and `starts_with`. diff --git a/leetcode_old/implement_trie_prefix_tree/__init__.py b/leetcode_old/implement_trie_prefix_tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/implement_trie_prefix_tree/playground.ipynb b/leetcode_old/implement_trie_prefix_tree/playground.ipynb deleted file mode 100644 index 75d2107..0000000 --- a/leetcode_old/implement_trie_prefix_tree/playground.ipynb +++ /dev/null @@ -1,217 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Trie" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "operations = [\"Trie\", \"insert\", \"search\", \"search\", \"starts_with\", \"insert\", \"search\"]\n", - "inputs = [[], [\"apple\"], [\"apple\"], [\"app\"], [\"app\"], [\"app\"], [\"app\"]]\n", - "expected = [None, None, True, False, True, None, True]" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[None, None, True, False, True, None, True]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "trie = None\n", - "results: list[bool | None] = []\n", - "for i, op in enumerate(operations):\n", - " if op == \"Trie\":\n", - " trie = Trie()\n", - " results.append(None)\n", - " elif op == \"insert\" and trie is not None:\n", - " trie.insert(inputs[i][0])\n", - " results.append(None)\n", - " elif op == \"search\" and trie is not None:\n", - " results.append(trie.search(inputs[i][0]))\n", - " elif op == \"starts_with\" and trie is not None:\n", - " results.append(trie.starts_with(inputs[i][0]))\n", - "results" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "c8308208", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root\n", - "\n", - "root\n", - "\n", - "\n", - "\n", - "root_0\n", - "\n", - "a\n", - "\n", - "\n", - "\n", - "root->root_0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root_0_0\n", - "\n", - "p\n", - "\n", - "\n", - "\n", - "root_0->root_0_0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root_0_0_0\n", - "\n", - "p\n", - "\n", - "\n", - "\n", - "root_0_0->root_0_0_0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root_0_0_0_0\n", - "\n", - "l\n", - "\n", - "\n", - "\n", - "root_0_0_0->root_0_0_0_0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root_0_0_0_leaf_1\n", - "\n", - "#: True\n", - "\n", - "\n", - "\n", - "root_0_0_0->root_0_0_0_leaf_1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root_0_0_0_0_0\n", - "\n", - "e\n", - "\n", - "\n", - "\n", - "root_0_0_0_0->root_0_0_0_0_0\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "root_0_0_0_0_0_leaf_0\n", - "\n", - "#: True\n", - "\n", - "\n", - "\n", - "root_0_0_0_0_0->root_0_0_0_0_0_leaf_0\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "trie" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert results == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/implement_trie_prefix_tree/solution.py b/leetcode_old/implement_trie_prefix_tree/solution.py deleted file mode 100644 index 35ea50b..0000000 --- a/leetcode_old/implement_trie_prefix_tree/solution.py +++ /dev/null @@ -1,40 +0,0 @@ -from leetcode_py.data_structures import DictTree, RecursiveDict - - -class Trie(DictTree[str]): - END_OF_WORD = "#" - - # Time: O(1) - # Space: O(1) - def __init__(self) -> None: - self.root: RecursiveDict[str] = {} - - # Time: O(m) where m is word length - # Space: O(m) - def insert(self, word: str) -> None: - node = self.root - for char in word: - if char not in node: - node[char] = {} - node = node[char] - node[self.END_OF_WORD] = True - - # Time: O(m) where m is word length - # Space: O(1) - def search(self, word: str) -> bool: - node = self.root - for char in word: - if char not in node: - return False - node = node[char] - return self.END_OF_WORD in node - - # Time: O(m) where m is prefix length - # Space: O(1) - def starts_with(self, prefix: str) -> bool: - node = self.root - for char in prefix: - if char not in node: - return False - node = node[char] - return True diff --git a/leetcode_old/implement_trie_prefix_tree/tests.py b/leetcode_old/implement_trie_prefix_tree/tests.py deleted file mode 100644 index 6b078bb..0000000 --- a/leetcode_old/implement_trie_prefix_tree/tests.py +++ /dev/null @@ -1,52 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Trie - - -class TestImplementTriePrefixTree: - @pytest.mark.parametrize( - "operations, inputs, expected", - [ - ( - ["Trie", "insert", "search", "search", "starts_with", "insert", "search"], - [[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]], - [None, None, True, False, True, None, True], - ), - ( - ["Trie", "insert", "insert", "search", "search", "starts_with", "starts_with"], - [[], ["hello"], ["world"], ["hello"], ["hi"], ["hel"], ["wor"]], - [None, None, None, True, False, True, True], - ), - ( - ["Trie", "insert", "insert", "search", "search", "starts_with", "starts_with"], - [[], ["a"], ["aa"], ["a"], ["aa"], ["a"], ["aa"]], - [None, None, None, True, True, True, True], - ), - ( - ["Trie", "insert", "search", "starts_with", "insert", "search", "starts_with"], - [[], ["test"], ["testing"], ["test"], ["testing"], ["testing"], ["test"]], - [None, None, False, True, None, True, True], - ), - (["Trie", "search", "starts_with"], [[], ["empty"], ["empty"]], [None, False, False]), - ], - ) - @logged_test - def test_trie_operations( - self, operations: list[str], inputs: list[list[str]], expected: list[bool | None] - ): - trie: Trie | None = None - results: list[bool | None] = [] - for i, op in enumerate(operations): - if op == "Trie": - trie = Trie() - results.append(None) - elif op == "insert" and trie is not None: - trie.insert(inputs[i][0]) - results.append(None) - elif op == "search" and trie is not None: - results.append(trie.search(inputs[i][0])) - elif op == "starts_with" and trie is not None: - results.append(trie.starts_with(inputs[i][0])) - assert results == expected diff --git a/leetcode_old/insert_interval/README.md b/leetcode_old/insert_interval/README.md deleted file mode 100644 index aff876d..0000000 --- a/leetcode_old/insert_interval/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Insert Interval - -**Difficulty:** Medium -**Topics:** Array -**Tags:** grind-75 - -**LeetCode:** [Problem 57](https://leetcode.com/problems/insert-interval/description/) - -## Problem Description - -You are given an array of non-overlapping intervals `intervals` where `intervals[i] = [starti, endi]` represent the start and the end of the ith interval and `intervals` is sorted in ascending order by `starti`. You are also given an interval `newInterval = [start, end]` that represents the start and end of another interval. - -Insert `newInterval` into `intervals` such that `intervals` is still sorted in ascending order by `starti` and `intervals` still does not have any overlapping intervals (merge overlapping intervals if necessary). - -Return `intervals` after the insertion. - -## Examples - -### Example 1: - -``` -Input: intervals = [[1,3],[6,9]], newInterval = [2,5] -Output: [[1,5],[6,9]] -``` - -### Example 2: - -``` -Input: intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8] -Output: [[1,2],[3,10],[12,16]] -Explanation: Because the new interval [4,8] overlaps with [3,5],[6,7],[8,10]. -``` - -## Constraints - -- 0 <= intervals.length <= 10^4 -- intervals[i].length == 2 -- 0 <= starti <= endi <= 10^5 -- intervals is sorted by starti in ascending order -- newInterval.length == 2 -- 0 <= start <= end <= 10^5 diff --git a/leetcode_old/insert_interval/__init__.py b/leetcode_old/insert_interval/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/insert_interval/playground.ipynb b/leetcode_old/insert_interval/playground.ipynb deleted file mode 100644 index 921d04b..0000000 --- a/leetcode_old/insert_interval/playground.ipynb +++ /dev/null @@ -1,69 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "intervals = [[1, 3], [6, 9]]\n", - "new_interval = [2, 5]\n", - "expected = [[1, 5], [6, 9]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().insert(intervals, new_interval)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/insert_interval/solution.py b/leetcode_old/insert_interval/solution.py deleted file mode 100644 index 25ce116..0000000 --- a/leetcode_old/insert_interval/solution.py +++ /dev/null @@ -1,22 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(n) - def insert(self, intervals: list[list[int]], new_interval: list[int]) -> list[list[int]]: - result = [] - i = 0 - - # Add intervals before new_interval - while i < len(intervals) and intervals[i][1] < new_interval[0]: - result.append(intervals[i]) - i += 1 - - # Merge overlapping intervals - while i < len(intervals) and intervals[i][0] <= new_interval[1]: - new_interval[0] = min(new_interval[0], intervals[i][0]) - new_interval[1] = max(new_interval[1], intervals[i][1]) - i += 1 - result.append(new_interval) - - # Add remaining intervals - result.extend(intervals[i:]) - return result diff --git a/leetcode_old/insert_interval/tests.py b/leetcode_old/insert_interval/tests.py deleted file mode 100644 index ee51e8f..0000000 --- a/leetcode_old/insert_interval/tests.py +++ /dev/null @@ -1,41 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestInsertInterval: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "intervals, new_interval, expected", - [ - # Original cases - ([[1, 3], [6, 9]], [2, 5], [[1, 5], [6, 9]]), - ([[1, 2], [3, 5], [6, 7], [8, 10], [12, 16]], [4, 8], [[1, 2], [3, 10], [12, 16]]), - # Empty intervals - ([], [5, 7], [[5, 7]]), - # Insert at beginning - ([[3, 5], [6, 9]], [1, 2], [[1, 2], [3, 5], [6, 9]]), - # Insert at end - ([[1, 3], [6, 9]], [10, 12], [[1, 3], [6, 9], [10, 12]]), - # No overlap - ([[1, 2], [4, 5]], [3, 3], [[1, 2], [3, 3], [4, 5]]), - # Complete overlap - ([[1, 5]], [2, 3], [[1, 5]]), - # Merge all intervals - ([[1, 2], [3, 4], [5, 6]], [0, 7], [[0, 7]]), - # Adjacent intervals - ([[1, 3], [6, 9]], [4, 5], [[1, 3], [4, 5], [6, 9]]), - # Touch boundaries - ([[1, 3], [6, 9]], [3, 6], [[1, 9]]), - ], - ) - @logged_test - def test_insert( - self, intervals: list[list[int]], new_interval: list[int], expected: list[list[int]] - ): - result = self.solution.insert(intervals, new_interval) - assert result == expected diff --git a/leetcode_old/invert_binary_tree/README.md b/leetcode_old/invert_binary_tree/README.md deleted file mode 100644 index a2b7ded..0000000 --- a/leetcode_old/invert_binary_tree/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Invert Binary Tree - -**Difficulty:** Easy -**Topics:** Tree, Depth-First Search, Breadth-First Search, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 226](https://leetcode.com/problems/invert-binary-tree/description/) - -## Problem Description - -Given the `root` of a binary tree, invert the tree, and return its root. - -## Examples - -### Example 1: - -``` -Input: root = [4,2,7,1,3,6,9] -Output: [4,7,2,9,6,3,1] -``` - -### Example 2: - -``` -Input: root = [2,1,3] -Output: [2,3,1] -``` - -### Example 3: - -``` -Input: root = [] -Output: [] -``` - -## Constraints - -- The number of nodes in the tree is in the range [0, 100] -- -100 <= Node.val <= 100 diff --git a/leetcode_old/invert_binary_tree/__init__.py b/leetcode_old/invert_binary_tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/invert_binary_tree/playground.ipynb b/leetcode_old/invert_binary_tree/playground.ipynb deleted file mode 100644 index c4a7c4c..0000000 --- a/leetcode_old/invert_binary_tree/playground.ipynb +++ /dev/null @@ -1,71 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import TreeNode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "root_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\n", - "root = TreeNode[int].from_list(root_list)\n", - "expected = TreeNode[int].from_list([4, 7, 2, 9, 6, 3, 1])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().invert_tree(root)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/invert_binary_tree/solution.py b/leetcode_old/invert_binary_tree/solution.py deleted file mode 100644 index 1e11b18..0000000 --- a/leetcode_old/invert_binary_tree/solution.py +++ /dev/null @@ -1,59 +0,0 @@ -from collections import deque - -from leetcode_py import TreeNode - -# Note: "Fringe" is the general CS term for the data structure holding nodes to be explored. -# Stack (LIFO) → DFS, Queue (FIFO) → BFS, Priority Queue → A*/Best-first search - - -class Solution: - # DFS recursive - # Time: O(n) - # Space: O(h) where h is height of tree - def invert_tree(self, root: TreeNode[int] | None) -> TreeNode[int] | None: - if not root: - return None - - root.left, root.right = self.invert_tree(root.right), self.invert_tree(root.left) - return root - - -class SolutionDFS: - # DFS iterative - # Time: O(n) - # Space: O(h) where h is height of tree - def invert_tree(self, root: TreeNode[int] | None) -> TreeNode[int] | None: - if not root: - return None - - stack: list[TreeNode[int] | None] = [root] - while stack: - node = stack.pop() - if node is None: - continue - node.left, node.right = node.right, node.left - - stack.append(node.left) - stack.append(node.right) - - return root - - -class SolutionBFS: - # Time: O(n) - # Space: O(w) where w is maximum width of tree - def invert_tree(self, root: TreeNode[int] | None) -> TreeNode[int] | None: - if not root: - return None - - queue: deque[TreeNode[int] | None] = deque([root]) - while queue: - node = queue.popleft() - if node is None: - continue - node.left, node.right = node.right, node.left - - queue.append(node.left) - queue.append(node.right) - - return root diff --git a/leetcode_old/invert_binary_tree/tests.py b/leetcode_old/invert_binary_tree/tests.py deleted file mode 100644 index dff6608..0000000 --- a/leetcode_old/invert_binary_tree/tests.py +++ /dev/null @@ -1,26 +0,0 @@ -import pytest - -from leetcode_py import TreeNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution, SolutionBFS, SolutionDFS - - -class TestInvertBinaryTree: - @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS]) - @pytest.mark.parametrize( - "root_list, expected_list", - [([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), ([2, 1, 3], [2, 3, 1]), ([], [])], - ) - @logged_test - def test_invert_tree( - self, - root_list: list[int | None], - expected_list: list[int | None], - solution_class: type[Solution | SolutionDFS | SolutionBFS], - ): - solution = solution_class() - root = TreeNode[int].from_list(root_list) - expected = TreeNode[int].from_list(expected_list) - result = solution.invert_tree(root) - assert result == expected diff --git a/leetcode_old/k_closest_points_to_origin/README.md b/leetcode_old/k_closest_points_to_origin/README.md deleted file mode 100644 index 54395ba..0000000 --- a/leetcode_old/k_closest_points_to_origin/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# K Closest Points to Origin - -**Difficulty:** Medium -**Topics:** Array, Math, Divide and Conquer, Geometry, Sorting, Heap (Priority Queue), Quickselect -**Tags:** grind-75 - -**LeetCode:** [Problem 973](https://leetcode.com/problems/k-closest-points-to-origin/description/) - -## Problem Description - -Given an array of `points` where `points[i] = [xi, yi]` represents a point on the **X-Y** plane and an integer `k`, return the `k` closest points to the origin `(0, 0)`. - -The distance between two points on the **X-Y** plane is the Euclidean distance (i.e., `√(x1 - x2)² + (y1 - y2)²`). - -You may return the answer in **any order**. The answer is **guaranteed** to be **unique** (except for the order that it is in). - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2021/03/03/closestplane1.jpg) - -``` -Input: points = [[1,3],[-2,2]], k = 1 -Output: [[-2,2]] -``` - -**Explanation:** The distance between (1, 3) and the origin is sqrt(10). The distance between (-2, 2) and the origin is sqrt(8). Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin. We only want the closest k = 1 points from the origin, so the answer is just [[-2,2]]. - -### Example 2: - -``` -Input: points = [[3,3],[5,-1],[-2,4]], k = 2 -Output: [[3,3],[-2,4]] -``` - -**Explanation:** The answer [[-2,4],[3,3]] would also be accepted. - -## Constraints - -- `1 <= k <= points.length <= 10^4` -- `-10^4 <= xi, yi <= 10^4` diff --git a/leetcode_old/k_closest_points_to_origin/__init__.py b/leetcode_old/k_closest_points_to_origin/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/k_closest_points_to_origin/playground.ipynb b/leetcode_old/k_closest_points_to_origin/playground.ipynb deleted file mode 100644 index 90da6e9..0000000 --- a/leetcode_old/k_closest_points_to_origin/playground.ipynb +++ /dev/null @@ -1,69 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "points = [[1, 3], [-2, 2]]\n", - "k = 1\n", - "expected = [[-2, 2]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().k_closest(points, k)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert sorted(result) == sorted(expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/k_closest_points_to_origin/solution.py b/leetcode_old/k_closest_points_to_origin/solution.py deleted file mode 100644 index d053d9b..0000000 --- a/leetcode_old/k_closest_points_to_origin/solution.py +++ /dev/null @@ -1,16 +0,0 @@ -import heapq - - -class Solution: - # Time: O(n log k) - # Space: O(k) - def k_closest(self, points: list[list[int]], k: int) -> list[list[int]]: - heap: list[tuple[int, list[int]]] = [] - - for x, y in points: - dist = x * x + y * y - heapq.heappush(heap, (-dist, [x, y])) - if len(heap) > k: - heapq.heappop(heap) - - return [point for _, point in heap] diff --git a/leetcode_old/k_closest_points_to_origin/tests.py b/leetcode_old/k_closest_points_to_origin/tests.py deleted file mode 100644 index 296af1b..0000000 --- a/leetcode_old/k_closest_points_to_origin/tests.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestKClosestPointsToOrigin: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "points, k, expected", - [ - # Basic examples - ([[1, 3], [-2, 2]], 1, [[-2, 2]]), - ([[3, 3], [5, -1], [-2, 4]], 2, [[3, 3], [-2, 4]]), - ([[0, 1], [1, 0]], 2, [[0, 1], [1, 0]]), - ([[1, 1], [1, 1], [1, 1]], 2, [[1, 1], [1, 1]]), - # Edge cases - ([[0, 0]], 1, [[0, 0]]), # Origin point - ([[1, 0], [0, 1], [-1, 0], [0, -1]], 1, [[1, 0]]), # Unit circle points - ([[2, 2], [1, 1], [3, 3]], 3, [[1, 1], [2, 2], [3, 3]]), # All points - # Negative coordinates - ([[-1, -1], [-2, -2], [1, 1]], 2, [[-1, -1], [1, 1]]), - # Large coordinates - ([[100, 100], [1, 1], [50, 50]], 1, [[1, 1]]), - # Same distances - ([[1, 0], [0, 1], [-1, 0], [0, -1]], 2, [[1, 0], [0, 1]]), - ], - ) - @logged_test - def test_k_closest(self, points: list[list[int]], k: int, expected: list[list[int]]): - result = self.solution.k_closest(points, k) - # Sort both result and expected for comparison since order doesn't matter - result_sorted = sorted(result) - expected_sorted = sorted(expected) - assert result_sorted == expected_sorted diff --git a/leetcode_old/kth_smallest_element_in_a_bst/README.md b/leetcode_old/kth_smallest_element_in_a_bst/README.md deleted file mode 100644 index fefde64..0000000 --- a/leetcode_old/kth_smallest_element_in_a_bst/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Kth Smallest Element in a BST - -**Difficulty:** Medium -**Topics:** Tree, Depth-First Search, Binary Search Tree, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 230](https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/) - -## Problem Description - -Given the `root` of a binary search tree, and an integer `k`, return the `k`th smallest value (1-indexed) of all the values of the nodes in the tree. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2021/01/28/kthtree1.jpg) - -``` -Input: root = [3,1,4,null,2], k = 1 -Output: 1 -``` - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2021/01/28/kthtree2.jpg) - -``` -Input: root = [5,3,6,2,4,null,null,1], k = 3 -Output: 3 -``` - -## Constraints - -- The number of nodes in the tree is `n`. -- `1 <= k <= n <= 10^4` -- `0 <= Node.val <= 10^4` - -**Follow up:** If the BST is modified often (i.e., we can do insert and delete operations) and you need to find the kth smallest frequently, how would you optimize? diff --git a/leetcode_old/kth_smallest_element_in_a_bst/__init__.py b/leetcode_old/kth_smallest_element_in_a_bst/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/kth_smallest_element_in_a_bst/playground.ipynb b/leetcode_old/kth_smallest_element_in_a_bst/playground.ipynb deleted file mode 100644 index da34058..0000000 --- a/leetcode_old/kth_smallest_element_in_a_bst/playground.ipynb +++ /dev/null @@ -1,160 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import TreeNode" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "root_list = [3, 1, 4, None, 2]\n", - "k = 1\n", - "expected = 1" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "root = TreeNode.from_list(root_list)\n", - "result = Solution().kth_smallest(root, k)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "6dc42838", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "0->1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "3\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "0->3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "2\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "1->2\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "TreeNode([3, 1, 4, None, 2])" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "root" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/kth_smallest_element_in_a_bst/solution.py b/leetcode_old/kth_smallest_element_in_a_bst/solution.py deleted file mode 100644 index 57bd5db..0000000 --- a/leetcode_old/kth_smallest_element_in_a_bst/solution.py +++ /dev/null @@ -1,41 +0,0 @@ -from leetcode_py import TreeNode - - -class Solution: - # Inorder Recursive - # Time: O(k) - # Space: O(h) - def kth_smallest(self, root: TreeNode | None, k: int) -> int: - def inorder(node: TreeNode | None): - if not node: - return - yield from inorder(node.left) - yield node.val - yield from inorder(node.right) - - for i, val in enumerate(inorder(root)): - if i == k - 1: - return val - - raise ValueError(f"Tree has fewer than {k} nodes") - - -# Binary Tree Traversal Patterns -# -# def inorder(node): -# if node: -# inorder(node.left) -# print(node.val) -# inorder(node.right) -# -# def preorder(node): -# if node: -# print(node.val) -# preorder(node.left) -# preorder(node.right) -# -# def postorder(node): -# if node: -# postorder(node.left) -# postorder(node.right) -# print(node.val) diff --git a/leetcode_old/kth_smallest_element_in_a_bst/tests.py b/leetcode_old/kth_smallest_element_in_a_bst/tests.py deleted file mode 100644 index 1e52c52..0000000 --- a/leetcode_old/kth_smallest_element_in_a_bst/tests.py +++ /dev/null @@ -1,21 +0,0 @@ -import pytest - -from leetcode_py import TreeNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestKthSmallestElementInABst: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "root_list, k, expected", - [([3, 1, 4, None, 2], 1, 1), ([5, 3, 6, 2, 4, None, None, 1], 3, 3), ([1], 1, 1)], - ) - @logged_test - def test_kth_smallest(self, root_list: list[int | None], k: int, expected: int): - root = TreeNode.from_list(root_list) - result = self.solution.kth_smallest(root, k) - assert result == expected diff --git a/leetcode_old/largest_rectangle_in_histogram/README.md b/leetcode_old/largest_rectangle_in_histogram/README.md deleted file mode 100644 index ac89d7c..0000000 --- a/leetcode_old/largest_rectangle_in_histogram/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Largest Rectangle in Histogram - -**Difficulty:** Hard -**Topics:** Array, Stack, Monotonic Stack -**Tags:** grind-75 - -**LeetCode:** [Problem 84](https://leetcode.com/problems/largest-rectangle-in-histogram/description/) - -## Problem Description - -Given an array of integers `heights` representing the histogram's bar height where the width of each bar is `1`, return the area of the largest rectangle in the histogram. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2021/01/04/histogram.jpg) - -``` -Input: heights = [2,1,5,6,2,3] -Output: 10 -``` - -**Explanation:** The above is a histogram where width of each bar is 1. The largest rectangle is shown in the red area, which has an area = 10 units. - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2021/01/04/histogram-1.jpg) - -``` -Input: heights = [2,4] -Output: 4 -``` - -## Constraints - -- `1 <= heights.length <= 10^5` -- `0 <= heights[i] <= 10^4` diff --git a/leetcode_old/largest_rectangle_in_histogram/__init__.py b/leetcode_old/largest_rectangle_in_histogram/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/largest_rectangle_in_histogram/playground.ipynb b/leetcode_old/largest_rectangle_in_histogram/playground.ipynb deleted file mode 100644 index 74de151..0000000 --- a/leetcode_old/largest_rectangle_in_histogram/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "heights = [2, 1, 5, 6, 2, 3]\n", - "expected = 10" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "10" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().largest_rectangle_area(heights)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/largest_rectangle_in_histogram/solution.py b/leetcode_old/largest_rectangle_in_histogram/solution.py deleted file mode 100644 index c500980..0000000 --- a/leetcode_old/largest_rectangle_in_histogram/solution.py +++ /dev/null @@ -1,29 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(n) - def largest_rectangle_area(self, heights: list[int]) -> int: - # Monotonic stack approach - # Stack stores indices of bars in increasing height order - # When we find a shorter bar, we calculate area using previous bars - - stack: list[int] = [] # Stack of indices - max_area = 0 - - for i, height in enumerate(heights): - # While current height is less than stack top height - # Pop from stack and calculate area with popped height as smallest - while stack and heights[stack[-1]] > height: - max_area = max(max_area, self.calculate_area(heights, stack, i)) - - stack.append(i) - - while stack: - max_area = max(max_area, self.calculate_area(heights, stack, len(heights))) - - return max_area - - @staticmethod - def calculate_area(heights: list[int], stack: list[int], right_bound: int) -> int: - h = heights[stack.pop()] - w = right_bound if not stack else right_bound - stack[-1] - 1 - return h * w diff --git a/leetcode_old/largest_rectangle_in_histogram/tests.py b/leetcode_old/largest_rectangle_in_histogram/tests.py deleted file mode 100644 index 715712f..0000000 --- a/leetcode_old/largest_rectangle_in_histogram/tests.py +++ /dev/null @@ -1,46 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestLargestRectangleInHistogram: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "heights, expected", - [ - # Basic examples - ([2, 1, 5, 6, 2, 3], 10), - ([2, 4], 4), - # Edge cases - ([1], 1), - ([0], 0), - ([1, 1], 2), - ([0, 0, 0], 0), - # Patterns - ([1, 2, 3, 4, 5], 9), - ([5, 4, 3, 2, 1], 9), - ([3, 3, 3, 3], 12), - ([2, 1, 2], 3), - ([1, 3, 1], 3), - # Complex cases - ([6, 7, 5, 2, 4, 5, 9, 3], 16), - ([4, 2, 0, 3, 2, 5], 6), - ([1, 2, 2, 1], 4), - ([0, 9], 9), - ([9, 0], 9), - # Large rectangles - ([2, 1, 5, 6, 2, 3, 1, 5, 6, 2], 10), - ([1, 8, 6, 2, 5, 4, 8, 3, 7], 16), - ([50, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 50), - ([1, 1, 1, 1, 1, 50, 1, 1, 1, 1, 1], 50), - ([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50], 50), - ], - ) - @logged_test - def test_largest_rectangle_area(self, heights: list[int], expected: int): - result = self.solution.largest_rectangle_area(heights) - assert result == expected diff --git a/leetcode_old/linked_list_cycle/README.md b/leetcode_old/linked_list_cycle/README.md deleted file mode 100644 index 4ec75c4..0000000 --- a/leetcode_old/linked_list_cycle/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Linked List Cycle - -**Difficulty:** Easy -**Topics:** Hash Table, Linked List, Two Pointers -**Tags:** grind-75 - -**LeetCode:** [Problem 141](https://leetcode.com/problems/linked-list-cycle/description/) - -## Problem Description - -Given `head`, the head of a linked list, determine if the linked list has a cycle in it. - -There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the `next` pointer. Internally, `pos` is used to denote the index of the node that tail's `next` pointer is connected to. **Note that `pos` is not passed as a parameter**. - -Return `true` _if there is a cycle in the linked list_. Otherwise, return `false`. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist.png) - -``` -Input: head = [3,2,0,-4], pos = 1 -Output: true -``` - -**Explanation:** There is a cycle in the linked list, where the tail connects to the 1st node (0-indexed). - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test2.png) - -``` -Input: head = [1,2], pos = 0 -Output: true -``` - -**Explanation:** There is a cycle in the linked list, where the tail connects to the 0th node. - -### Example 3: - -![Example 3](https://assets.leetcode.com/uploads/2018/12/07/circularlinkedlist_test3.png) - -``` -Input: head = [1], pos = -1 -Output: false -``` - -**Explanation:** There is no cycle in the linked list. - -## Constraints - -- The number of the nodes in the list is in the range `[0, 10^4]`. -- `-10^5 <= Node.val <= 10^5` -- `pos` is `-1` or a **valid index** in the linked-list. - -**Follow up:** Can you solve it using `O(1)` (i.e. constant) memory? diff --git a/leetcode_old/linked_list_cycle/__init__.py b/leetcode_old/linked_list_cycle/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/linked_list_cycle/playground.ipynb b/leetcode_old/linked_list_cycle/playground.ipynb deleted file mode 100644 index 7f74fb3..0000000 --- a/leetcode_old/linked_list_cycle/playground.ipynb +++ /dev/null @@ -1,87 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import sys\n", - "\n", - "sys.path.append(os.path.join(os.getcwd(), \"..\"))\n", - "from linked_list_cycle.tests import TestLinkedListCycle\n", - "\n", - "# Example test case\n", - "values = [3, 2, 0, -4]\n", - "pos = 1\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "head = TestLinkedListCycle().create_cycle_list(values, pos)\n", - "result = Solution().has_cycle(head)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/linked_list_cycle/solution.py b/leetcode_old/linked_list_cycle/solution.py deleted file mode 100644 index 6c3f838..0000000 --- a/leetcode_old/linked_list_cycle/solution.py +++ /dev/null @@ -1,18 +0,0 @@ -from leetcode_py import ListNode - - -class Solution: - # Time: O(n) - # Space: O(1) - def has_cycle(self, head: ListNode[int] | None) -> bool: - fast = head - slow = head - - while fast and fast.next: - assert slow is not None - fast = fast.next.next - slow = slow.next - if fast is slow: - return True - - return False diff --git a/leetcode_old/linked_list_cycle/tests.py b/leetcode_old/linked_list_cycle/tests.py deleted file mode 100644 index 8af1b82..0000000 --- a/leetcode_old/linked_list_cycle/tests.py +++ /dev/null @@ -1,59 +0,0 @@ -import pytest - -from leetcode_py import ListNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestLinkedListCycle: - def setup_method(self): - self.solution = Solution() - - def create_cycle_list(self, values: list[int], pos: int): - if not values: - return None - - nodes = [] - head = ListNode(values[0]) - nodes.append(head) - current = head - - for i in range(1, len(values)): - current.next = ListNode(values[i]) - current = current.next - nodes.append(current) - - if pos != -1 and pos < len(nodes): - current.next = nodes[pos] - - return head - - @pytest.mark.parametrize( - "values, pos, expected", - [ - ([3, 2, 0, -4], 1, True), - ([1, 2], 0, True), - ([1], -1, False), - ([], -1, False), - ([1, 2, 3], -1, False), - ([1, 2, 3, 4, 5], 0, True), - ([1, 2, 3, 4, 5], 2, True), - ([1, 2, 3, 4, 5], 4, True), - ([1], 0, True), - ([1, 2], 1, True), - ([1, 2, 3, 4, 5, 6, 7, 8], 3, True), - ([1, 2, 3, 4, 5, 6, 7, 8], -1, False), - ([1, 2], -1, False), - ([5, 10], 0, True), - ([5, 10], 1, True), - ([0], -1, False), - ([-1, -2, -3], 1, True), - ([100, 200, 300], 0, True), - ], - ) - @logged_test - def test_has_cycle(self, values: list[int], pos: int, expected: bool): - head = self.create_cycle_list(values, pos) - result = self.solution.has_cycle(head) - assert result == expected diff --git a/leetcode_old/longest_palindrome/README.md b/leetcode_old/longest_palindrome/README.md deleted file mode 100644 index 327f719..0000000 --- a/leetcode_old/longest_palindrome/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Longest Palindrome - -**Difficulty:** Easy -**Topics:** Hash Table, String, Greedy -**Tags:** grind-75 - -**LeetCode:** [Problem 409](https://leetcode.com/problems/longest-palindrome/description/) - -## Problem Description - -Given a string `s` which consists of lowercase or uppercase letters, return the length of the longest palindrome that can be built with those letters. - -Letters are case sensitive, for example, "Aa" is not considered a palindrome. - -## Examples - -### Example 1: - -``` -Input: s = "abccccdd" -Output: 7 -``` - -**Explanation:** One longest palindrome that can be built is "dccaccd", whose length is 7. - -### Example 2: - -``` -Input: s = "a" -Output: 1 -``` - -**Explanation:** The longest palindrome that can be built is "a", whose length is 1. - -## Constraints - -- `1 <= s.length <= 2000` -- `s` consists of lowercase and/or uppercase English letters only. diff --git a/leetcode_old/longest_palindrome/__init__.py b/leetcode_old/longest_palindrome/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/longest_palindrome/playground.ipynb b/leetcode_old/longest_palindrome/playground.ipynb deleted file mode 100644 index 5f53c98..0000000 --- a/leetcode_old/longest_palindrome/playground.ipynb +++ /dev/null @@ -1,68 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "s = \"abccccdd\"\n", - "expected = 7" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().longest_palindrome(s)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/longest_palindrome/solution.py b/leetcode_old/longest_palindrome/solution.py deleted file mode 100644 index baa568f..0000000 --- a/leetcode_old/longest_palindrome/solution.py +++ /dev/null @@ -1,17 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(1) - def longest_palindrome(self, s: str) -> int: - char_count: dict[str, int] = {} - for char in s: - char_count[char] = char_count.get(char, 0) + 1 - - length = 0 - has_odd = False - - for count in char_count.values(): - length += count // 2 * 2 - if count % 2 == 1: - has_odd = True - - return length + (1 if has_odd else 0) diff --git a/leetcode_old/longest_palindrome/tests.py b/leetcode_old/longest_palindrome/tests.py deleted file mode 100644 index c4a79d1..0000000 --- a/leetcode_old/longest_palindrome/tests.py +++ /dev/null @@ -1,31 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestLongestPalindrome: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "s, expected", - [ - ("abccccdd", 7), - ("a", 1), - ("Aa", 1), - ("aabbcc", 6), - ("abc", 1), - ("abcdef", 1), - ("aab", 3), - ("aaaa", 4), - ("AaBbCc", 1), - ("civilwartestingwhetherthatnaptionoranynartionsoconceivedandsodedicatedcanlongendure", 73), - ("bananas", 5), - ], - ) - @logged_test - def test_longest_palindrome(self, s: str, expected: int): - result = self.solution.longest_palindrome(s) - assert result == expected diff --git a/leetcode_old/longest_palindromic_substring/README.md b/leetcode_old/longest_palindromic_substring/README.md deleted file mode 100644 index 29669ab..0000000 --- a/leetcode_old/longest_palindromic_substring/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Longest Palindromic Substring - -**Difficulty:** Medium -**Topics:** Two Pointers, String, Dynamic Programming -**Tags:** grind-75 - -**LeetCode:** [Problem 5](https://leetcode.com/problems/longest-palindromic-substring/description/) - -## Problem Description - -Given a string `s`, return the longest palindromic substring in `s`. - -## Examples - -### Example 1: - -``` -Input: s = "babad" -Output: "bab" -``` - -**Explanation:** "aba" is also a valid answer. - -### Example 2: - -``` -Input: s = "cbbd" -Output: "bb" -``` - -## Constraints - -- `1 <= s.length <= 1000` -- `s` consist of only digits and English letters. diff --git a/leetcode_old/longest_palindromic_substring/__init__.py b/leetcode_old/longest_palindromic_substring/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/longest_palindromic_substring/playground.ipynb b/leetcode_old/longest_palindromic_substring/playground.ipynb deleted file mode 100644 index 8100878..0000000 --- a/leetcode_old/longest_palindromic_substring/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# # Example test case\n", - "s = \"babad\"\n", - "expected = {\"bab\", \"aba\"}" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'bab'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().longest_palindrome(s)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result in expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/longest_palindromic_substring/solution.py b/leetcode_old/longest_palindromic_substring/solution.py deleted file mode 100644 index b673d49..0000000 --- a/leetcode_old/longest_palindromic_substring/solution.py +++ /dev/null @@ -1,52 +0,0 @@ -class Solution: - # Time: O(n^2) - # Space: O(1) - def longest_palindrome(self, s: str) -> str: - start = 0 - max_len = 0 - - for i in range(len(s)): - # Odd length palindromes (center at i) - len1 = self.expand(s, i, i) - # Even length palindromes (center between i and i+1) - len2 = self.expand(s, i, i + 1) - - curr_len = max(len1, len2) - if curr_len > max_len: - max_len = curr_len - start = i - (curr_len - 1) // 2 - - return s[start : start + max_len] - - @staticmethod - def expand(s: str, left: int, right: int) -> int: - while left >= 0 and right < len(s) and s[left] == s[right]: - left -= 1 - right += 1 - return right - left - 1 - - -class SolutionManacher: - # Time: O(n) - # Space: O(n) - def longest_palindrome(self, s: str) -> str: - t = "#".join("^{}$".format(s)) - n = len(t) - p = [0] * n - center = right = 0 - for i in range(1, n - 1): - mirror_value = 2 * center - i - p[i] = min(right - i, p[mirror_value]) if right > i else 0 - - while t[i + 1 + p[i]] == t[i - 1 - p[i]]: - p[i] += 1 - - if i + p[i] > right: - center, right = i, i + p[i] - - max_len = max(p) - center_index = p.index(max_len) - - # Map back to original string: (center_index - max_len) // 2 - start = (center_index - max_len) // 2 - return s[start : start + max_len] diff --git a/leetcode_old/longest_palindromic_substring/tests.py b/leetcode_old/longest_palindromic_substring/tests.py deleted file mode 100644 index 7992302..0000000 --- a/leetcode_old/longest_palindromic_substring/tests.py +++ /dev/null @@ -1,74 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution, SolutionManacher - - -class TestLongestPalindromicSubstring: - def setup_method(self): - self.solution = Solution() - self.solution_manacher = SolutionManacher() - - @pytest.mark.parametrize( - "solution_class", - [Solution, SolutionManacher], - ) - @pytest.mark.parametrize( - "s, expected", - [ - ("babad", {"bab", "aba"}), - ("cbbd", {"bb"}), - ("a", {"a"}), - ("ac", {"a", "c"}), - ("racecar", {"racecar"}), - ("abcdef", {"a", "b", "c", "d", "e", "f"}), - ("aabbaa", {"aabbaa"}), - ("abacabad", {"abacaba"}), - ("aaaaaaaa", {"aaaaaaaa"}), - ("noon", {"noon"}), - ("abccba", {"abccba"}), - ("", {""}), - ("aa", {"aa"}), - ("aba", {"aba"}), - ("abcba", {"abcba"}), - ("forgeeksskeegfor", {"geeksskeeg"}), - ("bananas", {"anana"}), - ( - "abcdefghijklmnopqrstuvwxyz", - { - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - }, - ), - ], - ) - @logged_test - def test_longest_palindrome(self, solution_class, s: str, expected: set[str]): - solution = solution_class() - result = solution.longest_palindrome(s) - assert result in expected diff --git a/leetcode_old/longest_substring_without_repeating_characters/README.md b/leetcode_old/longest_substring_without_repeating_characters/README.md deleted file mode 100644 index 53c884d..0000000 --- a/leetcode_old/longest_substring_without_repeating_characters/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Longest Substring Without Repeating Characters - -**Difficulty:** Medium -**Topics:** Hash Table, String, Sliding Window -**Tags:** grind-75 - -**LeetCode:** [Problem 3](https://leetcode.com/problems/longest-substring-without-repeating-characters/description/) - -## Problem Description - -Given a string `s`, find the length of the **longest** **substring** without duplicate characters. - -## Examples - -### Example 1: - -``` -Input: s = "abcabcbb" -Output: 3 -``` - -**Explanation:** The answer is "abc", with the length of 3. - -### Example 2: - -``` -Input: s = "bbbbb" -Output: 1 -``` - -**Explanation:** The answer is "b", with the length of 1. - -### Example 3: - -``` -Input: s = "pwwkew" -Output: 3 -``` - -**Explanation:** The answer is "wke", with the length of 3. -Notice that the answer must be a substring, "pwke" is a subsequence and not a substring. - -## Constraints - -- 0 <= s.length <= 5 \* 10^4 -- s consists of English letters, digits, symbols and spaces. diff --git a/leetcode_old/longest_substring_without_repeating_characters/__init__.py b/leetcode_old/longest_substring_without_repeating_characters/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/longest_substring_without_repeating_characters/playground.ipynb b/leetcode_old/longest_substring_without_repeating_characters/playground.ipynb deleted file mode 100644 index c437eba..0000000 --- a/leetcode_old/longest_substring_without_repeating_characters/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "s = \"abcabcbb\"\n", - "expected = 3" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "3" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().length_of_longest_substring(s)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/longest_substring_without_repeating_characters/solution.py b/leetcode_old/longest_substring_without_repeating_characters/solution.py deleted file mode 100644 index 034f4a8..0000000 --- a/leetcode_old/longest_substring_without_repeating_characters/solution.py +++ /dev/null @@ -1,15 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(min(m, n)) where m is charset size - def length_of_longest_substring(self, s: str) -> int: - seen: set[str] = set() - left = max_len = 0 - - for right in range(len(s)): - while s[right] in seen: - seen.remove(s[left]) - left += 1 - seen.add(s[right]) - max_len = max(max_len, right - left + 1) - - return max_len diff --git a/leetcode_old/longest_substring_without_repeating_characters/tests.py b/leetcode_old/longest_substring_without_repeating_characters/tests.py deleted file mode 100644 index 272cdd4..0000000 --- a/leetcode_old/longest_substring_without_repeating_characters/tests.py +++ /dev/null @@ -1,35 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestLongestSubstringWithoutRepeatingCharacters: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "s, expected", - [ - ("abcabcbb", 3), # "abc" - ("bbbbb", 1), # "b" - ("pwwkew", 3), # "wke" - ("", 0), # empty - ("a", 1), # single char - ("au", 2), # "au" - ("dvdf", 3), # "vdf" - ("abcdef", 6), # no repeats - ("aab", 2), # "ab" - ("cdd", 2), # "cd" - ("abba", 2), # "ab" or "ba" - ("tmmzuxt", 5), # "mzuxt" - (" ", 1), # space char - ("!@#$%", 5), # symbols - ("abcabcabcabc", 3), # repeating pattern - ], - ) - @logged_test - def test_length_of_longest_substring(self, s: str, expected: int): - result = self.solution.length_of_longest_substring(s) - assert result == expected diff --git a/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/README.md b/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/README.md deleted file mode 100644 index a5b2f27..0000000 --- a/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Lowest Common Ancestor of a Binary Search Tree - -**Difficulty:** Medium -**Topics:** Tree, Depth-First Search, Binary Search Tree, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 235](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/) - -## Problem Description - -Given a binary search tree (BST), find the lowest common ancestor (LCA) node of two given nodes in the BST. - -According to the definition of LCA on Wikipedia: "The lowest common ancestor is defined between two nodes `p` and `q` as the lowest node in `T` that has both `p` and `q` as descendants (where we allow **a node to be a descendant of itself**)." - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2018/12/14/binarysearchtree_improved.png) - -``` -Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 -Output: 6 -``` - -**Explanation:** The LCA of nodes 2 and 8 is 6. - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2018/12/14/binarysearchtree_improved.png) - -``` -Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 -Output: 2 -``` - -**Explanation:** The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition. - -### Example 3: - -``` -Input: root = [2,1], p = 2, q = 1 -Output: 2 -``` - -## Constraints - -- The number of nodes in the tree is in the range `[2, 10^5]`. -- `-10^9 <= Node.val <= 10^9` -- All `Node.val` are **unique**. -- `p != q` -- `p` and `q` will exist in the BST. diff --git a/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/__init__.py b/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb b/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb deleted file mode 100644 index 89e233e..0000000 --- a/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/playground.ipynb +++ /dev/null @@ -1,225 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import TreeNode" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "root_list = [6, 2, 8, 0, 4, 7, 9, None, None, 3, 5]\n", - "p_val = 2\n", - "q_val = 8\n", - "expected_val = 6" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "6" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "root = TreeNode[int].from_list(root_list)\n", - "assert root is not None\n", - "p = root.find_node(p_val)\n", - "q = root.find_node(q_val)\n", - "assert p is not None and q is not None\n", - "result = Solution().lowest_common_ancestor(root, p, q)\n", - "result.val if result else None" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "d84494ee", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "6\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "0->1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "6\n", - "\n", - "8\n", - "\n", - "\n", - "\n", - "0->6\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "2\n", - "\n", - "0\n", - "\n", - "\n", - "\n", - "1->2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "3\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "1->3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "4\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "3->4\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "5\n", - "\n", - "5\n", - "\n", - "\n", - "\n", - "3->5\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "7\n", - "\n", - "7\n", - "\n", - "\n", - "\n", - "6->7\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "8\n", - "\n", - "9\n", - "\n", - "\n", - "\n", - "6->8\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "TreeNode([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5])" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "root" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result and result.val == expected_val" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/solution.py b/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/solution.py deleted file mode 100644 index f6f5dcd..0000000 --- a/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/solution.py +++ /dev/null @@ -1,20 +0,0 @@ -from leetcode_py import TreeNode - - -class Solution: - # Time: O(log n) average, O(n) worst case - # Space: O(1) iterative, O(log n) recursive - def lowest_common_ancestor( - self, root: TreeNode[int] | None, p: TreeNode[int], q: TreeNode[int] - ) -> TreeNode[int] | None: - while root: - # Both nodes are in left subtree - if p.val < root.val and q.val < root.val: - root = root.left - # Both nodes are in right subtree - elif p.val > root.val and q.val > root.val: - root = root.right - # Split point - one node on each side or one is the root - else: - return root - return None diff --git a/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/tests.py b/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/tests.py deleted file mode 100644 index b28db6c..0000000 --- a/leetcode_old/lowest_common_ancestor_of_a_binary_search_tree/tests.py +++ /dev/null @@ -1,35 +0,0 @@ -import pytest - -from leetcode_py import TreeNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestLowestCommonAncestorOfABinarySearchTree: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "root_list, p_val, q_val, expected_val", - [ - ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 8, 6), - ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 4, 2), - ([2, 1], 2, 1, 2), - ([2, 1], 1, 2, 2), - ([6, 2, 8, 0, 4, 7, 9], 0, 4, 2), - ([6, 2, 8, 0, 4, 7, 9], 7, 9, 8), - ], - ) - @logged_test - def test_lowest_common_ancestor( - self, root_list: list[int | None], p_val: int, q_val: int, expected_val: int - ): - root = TreeNode[int].from_list(root_list) - assert root is not None - p = root.find_node(p_val) - q = root.find_node(q_val) - assert p is not None and q is not None - result = self.solution.lowest_common_ancestor(root, p, q) - assert result is not None - assert result.val == expected_val diff --git a/leetcode_old/lowest_common_ancestor_of_a_binary_tree/README.md b/leetcode_old/lowest_common_ancestor_of_a_binary_tree/README.md deleted file mode 100644 index 1c562a6..0000000 --- a/leetcode_old/lowest_common_ancestor_of_a_binary_tree/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# Lowest Common Ancestor of a Binary Tree - -**Difficulty:** Medium -**Topics:** Tree, Depth-First Search, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 236](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/) - -## Problem Description - -Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. - -According to the definition of LCA on Wikipedia: "The lowest common ancestor is defined between two nodes `p` and `q` as the lowest node in `T` that has both `p` and `q` as descendants (where we allow **a node to be a descendant of itself**)." - -## Examples - -### Example 1: - - - -``` -Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 -Output: 3 -Explanation: The LCA of nodes 5 and 1 is 3. -``` - -### Example 2: - - - -``` -Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 -Output: 5 -Explanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition. -``` - -### Example 3: - -``` -Input: root = [1,2], p = 1, q = 2 -Output: 1 -``` - -## Constraints - -- The number of nodes in the tree is in the range [2, 10^5]. -- -10^9 <= Node.val <= 10^9 -- All Node.val are unique. -- p != q -- p and q will exist in the tree. diff --git a/leetcode_old/lowest_common_ancestor_of_a_binary_tree/__init__.py b/leetcode_old/lowest_common_ancestor_of_a_binary_tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/lowest_common_ancestor_of_a_binary_tree/playground.ipynb b/leetcode_old/lowest_common_ancestor_of_a_binary_tree/playground.ipynb deleted file mode 100644 index 6594efb..0000000 --- a/leetcode_old/lowest_common_ancestor_of_a_binary_tree/playground.ipynb +++ /dev/null @@ -1,222 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import TreeNode" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "root_list = [3, 5, 1, 6, 2, 0, 8, None, None, 7, 4]\n", - "root = TreeNode.from_list(root_list)\n", - "assert root is not None\n", - "p = root.find_node(5)\n", - "q = root.find_node(1)\n", - "expected_val = 3" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "6ad16444", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "5\n", - "\n", - "\n", - "\n", - "0->1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "6\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "0->6\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "2\n", - "\n", - "6\n", - "\n", - "\n", - "\n", - "1->2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "3\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "1->3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "4\n", - "\n", - "7\n", - "\n", - "\n", - "\n", - "3->4\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "5\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "3->5\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "7\n", - "\n", - "0\n", - "\n", - "\n", - "\n", - "6->7\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "8\n", - "\n", - "8\n", - "\n", - "\n", - "\n", - "6->8\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "TreeNode([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "root" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "3" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().lowest_common_ancestor(root, p, q)\n", - "result.val" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result.val == expected_val" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/lowest_common_ancestor_of_a_binary_tree/solution.py b/leetcode_old/lowest_common_ancestor_of_a_binary_tree/solution.py deleted file mode 100644 index 33f138a..0000000 --- a/leetcode_old/lowest_common_ancestor_of_a_binary_tree/solution.py +++ /dev/null @@ -1,21 +0,0 @@ -from leetcode_py import TreeNode - - -class Solution: - # Time: O(n) - # Space: O(h) - def lowest_common_ancestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode: - result = self._lca(root, p, q) - assert result is not None - return result - - def _lca(self, root: TreeNode | None, p: TreeNode, q: TreeNode) -> TreeNode | None: - if not root or root == p or root == q: - return root - - left = self._lca(root.left, p, q) - right = self._lca(root.right, p, q) - - if left and right: - return root - return left or right diff --git a/leetcode_old/lowest_common_ancestor_of_a_binary_tree/tests.py b/leetcode_old/lowest_common_ancestor_of_a_binary_tree/tests.py deleted file mode 100644 index d629f35..0000000 --- a/leetcode_old/lowest_common_ancestor_of_a_binary_tree/tests.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest - -from leetcode_py import TreeNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestLowestCommonAncestorOfABinaryTree: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "root_list, p_val, q_val, expected_val", - [ - ([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 5, 1, 3), - ([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 5, 4, 5), - ([1, 2], 1, 2, 1), - ([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 6, 7, 5), - ([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 7, 4, 2), - ([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4], 0, 8, 1), - ([1], 1, 1, 1), - ([2, 1, 3], 1, 3, 2), - ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 2, 8, 6), - ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 3, 5, 4), - ([6, 2, 8, 0, 4, 7, 9, None, None, 3, 5], 0, 3, 2), - ], - ) - @logged_test - def test_lowest_common_ancestor( - self, root_list: list[int | None], p_val: int, q_val: int, expected_val: int - ): - root = TreeNode.from_list(root_list) - assert root is not None - p = root.find_node(p_val) - q = root.find_node(q_val) - assert p is not None and q is not None - result = self.solution.lowest_common_ancestor(root, p, q) - assert result is not None - assert result.val == expected_val diff --git a/leetcode_old/lru_cache/README.md b/leetcode_old/lru_cache/README.md deleted file mode 100644 index 7037dbc..0000000 --- a/leetcode_old/lru_cache/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# LRU Cache - -**Difficulty:** Medium -**Topics:** Hash Table, Linked List, Design, Doubly-Linked List -**Tags:** grind-75 - -**LeetCode:** [Problem 146](https://leetcode.com/problems/lru-cache/description/) - -## Problem Description - -Design a data structure that follows the constraints of a Least Recently Used (LRU) cache. - -Implement the `LRUCache` class: - -- `LRUCache(int capacity)` Initialize the LRU cache with positive size capacity -- `int get(int key)` Return the value of the key if the key exists, otherwise return -1 -- `void put(int key, int value)` Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key - -The functions `get` and `put` must each run in `O(1)` average time complexity. - -## Examples - -### Example 1: - -``` -Input -["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"] -[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]] -Output -[null, null, null, 1, null, -1, null, -1, 3, 4] - -Explanation -LRUCache lRUCache = new LRUCache(2); -lRUCache.put(1, 1); // cache is {1=1} -lRUCache.put(2, 2); // cache is {1=1, 2=2} -lRUCache.get(1); // return 1 -lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3} -lRUCache.get(2); // returns -1 (not found) -lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3} -lRUCache.get(1); // return -1 (not found) -lRUCache.get(3); // return 3 -lRUCache.get(4); // return 4 -``` - -## Constraints - -- 1 <= capacity <= 3000 -- 0 <= key <= 10^4 -- 0 <= value <= 10^5 -- At most 2 \* 10^5 calls will be made to get and put diff --git a/leetcode_old/lru_cache/__init__.py b/leetcode_old/lru_cache/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/lru_cache/playground.ipynb b/leetcode_old/lru_cache/playground.ipynb deleted file mode 100644 index 3d277f1..0000000 --- a/leetcode_old/lru_cache/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import LRUCache" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "operations = [\"LRUCache\", \"put\", \"put\", \"get\", \"put\", \"get\", \"put\", \"get\", \"get\", \"get\"]\n", - "inputs = [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]\n", - "expected = [None, None, None, 1, None, -1, None, -1, 3, 4]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "cache = None\n", - "results: list[int | None] = []\n", - "for i, op in enumerate(operations):\n", - " if op == \"LRUCache\":\n", - " cache = LRUCache(inputs[i][0])\n", - " results.append(None)\n", - " elif op == \"get\" and cache is not None:\n", - " results.append(cache.get(inputs[i][0]))\n", - " elif op == \"put\" and cache is not None:\n", - " cache.put(inputs[i][0], inputs[i][1])\n", - " results.append(None)\n", - "results" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert results == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/lru_cache/solution.py b/leetcode_old/lru_cache/solution.py deleted file mode 100644 index c0bcd7a..0000000 --- a/leetcode_old/lru_cache/solution.py +++ /dev/null @@ -1,108 +0,0 @@ -from collections import OrderedDict - -from leetcode_py.data_structures.doubly_list_node import DoublyListNode - - -class LRUCache: - # Space: O(capacity) - def __init__(self, capacity: int) -> None: - self.capacity = capacity - self.cache: OrderedDict[int, int] = OrderedDict() - - # Time: O(1) - # Space: O(1) - def get(self, key: int) -> int: - if key not in self.cache: - return -1 - - # Move to end (most recent) - self.cache.move_to_end(key) - return self.cache[key] - - # Time: O(1) - # Space: O(1) - def put(self, key: int, value: int) -> None: - if key in self.cache: - # Update existing and move to end - self.cache[key] = value - self.cache.move_to_end(key) - else: - # Add new - if len(self.cache) >= self.capacity: - # Remove LRU (first item) - self.cache.popitem(last=False) - - self.cache[key] = value - - -class CacheNode(DoublyListNode[int]): - def __init__(self, key: int = 0, val: int = 0) -> None: - super().__init__(val) - self.key = key - - -class LRUCacheWithDoublyList: - def __init__(self, capacity: int) -> None: - self.capacity = capacity - self.cache: dict[int, CacheNode] = {} - - # Dummy head and tail nodes - self.head = CacheNode() - self.tail = CacheNode() - self.head.next = self.tail - self.tail.prev = self.head - - def _add_node(self, node: CacheNode) -> None: - """Add node right after head""" - node.prev = self.head - node.next = self.head.next - if self.head.next: - self.head.next.prev = node - self.head.next = node - - def _remove_node(self, node: CacheNode) -> None: - """Remove node from list""" - if node.prev: - node.prev.next = node.next - if node.next: - node.next.prev = node.prev - - def _move_to_head(self, node: CacheNode) -> None: - """Move node to head (most recent)""" - self._remove_node(node) - self._add_node(node) - - def _pop_tail(self) -> CacheNode: - """Remove last node before tail""" - last_node = self.tail.prev - assert isinstance(last_node, CacheNode), "Expected CacheNode" - self._remove_node(last_node) - return last_node - - def get(self, key: int) -> int: - node = self.cache.get(key) - if not node: - return -1 - - # Move to head (most recent) - self._move_to_head(node) - return node.val - - def put(self, key: int, value: int) -> None: - node = self.cache.get(key) - - if node: - # Update existing - node.val = value - self._move_to_head(node) - else: - # Add new - new_node = CacheNode(key, value) - - if len(self.cache) >= self.capacity: - # Remove LRU - tail = self._pop_tail() - del self.cache[tail.key] - - self.cache[key] = new_node - self._add_node(new_node) diff --git a/leetcode_old/lru_cache/tests.py b/leetcode_old/lru_cache/tests.py deleted file mode 100644 index 8d3e6e8..0000000 --- a/leetcode_old/lru_cache/tests.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import LRUCache, LRUCacheWithDoublyList - - -class TestLRUCache: - @pytest.mark.parametrize("lru_class", [LRUCache, LRUCacheWithDoublyList]) - @pytest.mark.parametrize( - "operations, inputs, expected", - [ - ( - ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"], - [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]], - [None, None, None, 1, None, -1, None, -1, 3, 4], - ), - ( - ["LRUCache", "get", "put", "get", "put", "put", "get", "get"], - [[2], [2], [2, 6], [1], [1, 5], [1, 2], [1], [2]], - [None, -1, None, -1, None, None, 2, 6], - ), - ( - ["LRUCache", "put", "get", "put", "get", "get"], - [[1], [2, 1], [2], [3, 2], [2], [3]], - [None, None, 1, None, -1, 2], - ), - ], - ) - @logged_test - def test_lru_cache( - self, - lru_class: type[LRUCache | LRUCacheWithDoublyList], - operations: list[str], - inputs: list[list[int]], - expected: list[int | None], - ): - cache = None - results: list[int | None] = [] - for i, op in enumerate(operations): - if op == "LRUCache": - cache = lru_class(inputs[i][0]) - results.append(None) - elif op == "get" and cache is not None: - results.append(cache.get(inputs[i][0])) - elif op == "put" and cache is not None: - cache.put(inputs[i][0], inputs[i][1]) - results.append(None) - assert results == expected diff --git a/leetcode_old/majority_element/README.md b/leetcode_old/majority_element/README.md deleted file mode 100644 index 3822272..0000000 --- a/leetcode_old/majority_element/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Majority Element - -**Difficulty:** Easy -**Topics:** Array, Hash Table, Divide and Conquer, Sorting, Counting -**Tags:** grind-75 - -**LeetCode:** [Problem 169](https://leetcode.com/problems/majority-element/description/) - -## Problem Description - -Given an array `nums` of size `n`, return the majority element. - -The majority element is the element that appears more than `⌊n / 2⌋` times. You may assume that the majority element always exists in the array. - -## Examples - -### Example 1: - -``` -Input: nums = [3,2,3] -Output: 3 -``` - -### Example 2: - -``` -Input: nums = [2,2,1,1,1,2,2] -Output: 2 -``` - -## Constraints - -- n == nums.length -- 1 <= n <= 5 \* 10^4 -- -10^9 <= nums[i] <= 10^9 - -**Follow-up:** Could you solve the problem in linear time and in O(1) space? diff --git a/leetcode_old/majority_element/__init__.py b/leetcode_old/majority_element/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/majority_element/playground.ipynb b/leetcode_old/majority_element/playground.ipynb deleted file mode 100644 index de16ac0..0000000 --- a/leetcode_old/majority_element/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "nums = [3, 2, 3]\n", - "expected = 3" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "3" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().majority_element(nums)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/majority_element/solution.py b/leetcode_old/majority_element/solution.py deleted file mode 100644 index 1499c32..0000000 --- a/leetcode_old/majority_element/solution.py +++ /dev/null @@ -1,14 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(1) - def majority_element(self, nums: list[int]) -> int: - # Boyer-Moore Voting Algorithm - candidate = 0 - count = 0 - - for num in nums: - if count == 0: - candidate = num - count += 1 if num == candidate else -1 - - return candidate diff --git a/leetcode_old/majority_element/tests.py b/leetcode_old/majority_element/tests.py deleted file mode 100644 index 5414f0f..0000000 --- a/leetcode_old/majority_element/tests.py +++ /dev/null @@ -1,19 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestMajorityElement: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "nums, expected", - [([3, 2, 3], 3), ([2, 2, 1, 1, 1, 2, 2], 2), ([1], 1), ([1, 1, 2], 1), ([2, 2, 2, 1, 1], 2)], - ) - @logged_test - def test_majority_element(self, nums: list[int], expected: int): - result = self.solution.majority_element(nums) - assert result == expected diff --git a/leetcode_old/maximum_depth_of_binary_tree/README.md b/leetcode_old/maximum_depth_of_binary_tree/README.md deleted file mode 100644 index 4663e24..0000000 --- a/leetcode_old/maximum_depth_of_binary_tree/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Maximum Depth of Binary Tree - -**Difficulty:** Easy -**Topics:** Tree, Depth-First Search, Breadth-First Search, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 104](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/) - -## Problem Description - -Given the `root` of a binary tree, return _its maximum depth_. - -A binary tree's **maximum depth** is the number of nodes along the longest path from the root node down to the farthest leaf node. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2020/11/26/tmp-tree.jpg) - -``` -Input: root = [3,9,20,null,null,15,7] -Output: 3 -``` - -### Example 2: - -``` -Input: root = [1,null,2] -Output: 2 -``` - -## Constraints - -- The number of nodes in the tree is in the range `[0, 10^4]`. -- `-100 <= Node.val <= 100` diff --git a/leetcode_old/maximum_depth_of_binary_tree/__init__.py b/leetcode_old/maximum_depth_of_binary_tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/maximum_depth_of_binary_tree/playground.ipynb b/leetcode_old/maximum_depth_of_binary_tree/playground.ipynb deleted file mode 100644 index 19d1eed..0000000 --- a/leetcode_old/maximum_depth_of_binary_tree/playground.ipynb +++ /dev/null @@ -1,171 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import TreeNode" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "root_list = [3, 9, 20, None, None, 15, 7]\n", - "expected = 3" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "3" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "root = TreeNode.from_list(root_list)\n", - "result = Solution().max_depth(root)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "3d476584", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "9\n", - "\n", - "\n", - "\n", - "0->1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "2\n", - "\n", - "20\n", - "\n", - "\n", - "\n", - "0->2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "3\n", - "\n", - "15\n", - "\n", - "\n", - "\n", - "2->3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "4\n", - "\n", - "7\n", - "\n", - "\n", - "\n", - "2->4\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "TreeNode([3, 9, 20, None, None, 15, 7])" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "root" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/maximum_depth_of_binary_tree/solution.py b/leetcode_old/maximum_depth_of_binary_tree/solution.py deleted file mode 100644 index 727e907..0000000 --- a/leetcode_old/maximum_depth_of_binary_tree/solution.py +++ /dev/null @@ -1,14 +0,0 @@ -from leetcode_py import TreeNode - - -class Solution: - # Time: O(n) - # Space: O(h) - def max_depth(self, root: TreeNode | None) -> int: - if not root: - return 0 - - left_depth = self.max_depth(root.left) - right_depth = self.max_depth(root.right) - - return 1 + max(left_depth, right_depth) diff --git a/leetcode_old/maximum_depth_of_binary_tree/tests.py b/leetcode_old/maximum_depth_of_binary_tree/tests.py deleted file mode 100644 index 3bc3acb..0000000 --- a/leetcode_old/maximum_depth_of_binary_tree/tests.py +++ /dev/null @@ -1,39 +0,0 @@ -import pytest - -from leetcode_py import TreeNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestMaximumDepthOfBinaryTree: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "root_list, expected", - [ - # Original examples - ([3, 9, 20, None, None, 15, 7], 3), - ([1, None, 2], 2), - ([], 0), - # Single node - ([1], 1), - # Left skewed tree (depth 3) - ([1, 2, None, 3], 3), - # Right skewed tree (depth 2) - ([1, None, 2], 2), - # Balanced tree - ([1, 2, 3, 4, 5, 6, 7], 3), - # Unbalanced tree (left heavy) - ([1, 2, 3, 4, 5, None, None, 6, 7], 4), - # Two nodes - ([1, 2], 2), - ([1, None, 2], 2), - ], - ) - @logged_test - def test_max_depth(self, root_list: list[int | None], expected: int): - root = TreeNode.from_list(root_list) - result = self.solution.max_depth(root) - assert result == expected diff --git a/leetcode_old/maximum_profit_in_job_scheduling/README.md b/leetcode_old/maximum_profit_in_job_scheduling/README.md deleted file mode 100644 index 100bfa7..0000000 --- a/leetcode_old/maximum_profit_in_job_scheduling/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Maximum Profit in Job Scheduling - -**Difficulty:** Hard -**Topics:** Array, Binary Search, Dynamic Programming, Sorting -**Tags:** grind-75 - -**LeetCode:** [Problem 1235](https://leetcode.com/problems/maximum-profit-in-job-scheduling/description/) - -## Problem Description - -We have `n` jobs, where every job is scheduled to be done from `startTime[i]` to `endTime[i]`, obtaining a profit of `profit[i]`. - -You're given the `startTime`, `endTime` and `profit` arrays, return the maximum profit you can take such that there are no two jobs in the subset with overlapping time range. - -If you choose a job that ends at time `X` you will be able to start another job that starts at time `X`. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2019/10/10/sample1_1584.png) - -``` -Input: startTime = [1,2,3,3], endTime = [3,4,5,6], profit = [50,10,40,70] -Output: 120 -``` - -**Explanation:** The subset chosen is the first and fourth job. Time range [1-3]+[3-6] , we get profit of 120 = 50 + 70. - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2019/10/10/sample22_1584.png) - -``` -Input: startTime = [1,2,3,4,6], endTime = [3,5,10,6,9], profit = [20,20,100,70,60] -Output: 150 -``` - -**Explanation:** The subset chosen is the first, fourth and fifth job. Profit obtained 150 = 20 + 70 + 60. - -### Example 3: - -![Example 3](https://assets.leetcode.com/uploads/2019/10/10/sample3_1584.png) - -``` -Input: startTime = [1,1,1], endTime = [2,3,4], profit = [5,6,4] -Output: 6 -``` - -## Constraints - -- `1 <= startTime.length == endTime.length == profit.length <= 5 * 10^4` -- `1 <= startTime[i] < endTime[i] <= 10^9` -- `1 <= profit[i] <= 10^4` diff --git a/leetcode_old/maximum_profit_in_job_scheduling/__init__.py b/leetcode_old/maximum_profit_in_job_scheduling/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/maximum_profit_in_job_scheduling/playground.ipynb b/leetcode_old/maximum_profit_in_job_scheduling/playground.ipynb deleted file mode 100644 index 9499ca9..0000000 --- a/leetcode_old/maximum_profit_in_job_scheduling/playground.ipynb +++ /dev/null @@ -1,174 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "import bisect\n", - "\n", - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "start_time = [1, 2, 3, 3]\n", - "end_time = [3, 4, 5, 6]\n", - "profit = [50, 10, 40, 70]\n", - "expected = 120" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "120" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().job_scheduling(start_time, end_time, profit)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - }, - { - "cell_type": "markdown", - "id": "dd4ebe51", - "metadata": {}, - "source": [ - "# Bisect" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "3596ff83", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Original array: [1, 3, 3, 5, 7]\n", - "Indices: [0, 1, 2, 3, 4]\n", - "\n" - ] - } - ], - "source": [ - "# Demo array\n", - "arr = [1, 3, 3, 5, 7]\n", - "print(f\"Original array: {arr}\")\n", - "print(f\"Indices: {list(range(len(arr)))}\")\n", - "print()" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "508a52e9", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== bisect_left vs bisect_right ===\n", - "x=0: left=0, right=0\n", - "x=3: left=1, right=3\n", - "x=4: left=3, right=3\n", - "x=8: left=5, right=5\n", - "\n" - ] - } - ], - "source": [ - "# bisect_left vs bisect_right\n", - "print(\"=== bisect_left vs bisect_right ===\")\n", - "for x in [0, 3, 4, 8]:\n", - " left = bisect.bisect_left(arr, x)\n", - " right = bisect.bisect_right(arr, x)\n", - " print(f\"x={x}: left={left}, right={right}\")\n", - "print()" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "b5dfa28a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "=== insort demonstration ===\n", - "Before: [1, 3, 5]\n", - "After insort(4): [1, 3, 4, 5]\n", - "After insort(3): [1, 3, 3, 4, 5]\n", - "\n" - ] - } - ], - "source": [ - "# insort demonstration\n", - "print(\"=== insort demonstration ===\")\n", - "test_arr = [1, 3, 5]\n", - "print(f\"Before: {test_arr}\")\n", - "bisect.insort(test_arr, 4)\n", - "print(f\"After insort(4): {test_arr}\")\n", - "bisect.insort(test_arr, 3)\n", - "print(f\"After insort(3): {test_arr}\")\n", - "print()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/maximum_profit_in_job_scheduling/solution.py b/leetcode_old/maximum_profit_in_job_scheduling/solution.py deleted file mode 100644 index 6fc5cfc..0000000 --- a/leetcode_old/maximum_profit_in_job_scheduling/solution.py +++ /dev/null @@ -1,51 +0,0 @@ -import bisect - - -class Solution: - # Time: O(n log n) - # Space: O(n) - def job_scheduling(self, start_time: list[int], end_time: list[int], profit: list[int]) -> int: - - jobs = sorted(zip(end_time, start_time, profit)) - dp = [0] * len(jobs) - - for i, (end, start, p) in enumerate(jobs): - # Binary search for latest non-overlapping job - j = bisect.bisect_right([job[0] for job in jobs[:i]], start) - 1 - - # Take current job + best profit from non-overlapping jobs - take = p + (dp[j] if j >= 0 else 0) - # Skip current job - skip = dp[i - 1] if i > 0 else 0 - - dp[i] = max(take, skip) - - return dp[-1] if jobs else 0 - - -# bisect and insort Explanation: -# -# Etymology: "bisect" = bi (two) + sect (cut) = cut into two parts -# Bisection method = binary search algorithm that repeatedly cuts search space in half -# -# bisect module provides binary search for SORTED lists (O(log n)): -# - bisect_left(arr, x): leftmost insertion position -# - bisect_right(arr, x): rightmost insertion position (default) -# - bisect(arr, x): alias for bisect_right -# -# insort module maintains sorted order while inserting: -# - insort_left(arr, x): insert at leftmost position -# - insort_right(arr, x): insert at rightmost position (default) -# - insort(arr, x): alias for insort_right -# -# Examples: -# arr = [1, 3, 3, 5] -# bisect_left(arr, 3) → 1 (before existing 3s) -# bisect_right(arr, 3) → 3 (after existing 3s) -# bisect_right(arr, 4) → 3 (between 3 and 5) -# -# insort(arr, 4) → arr becomes [1, 3, 3, 4, 5] -# -# In our solution: -# bisect_right([2,4,6], 5) = 2 (insertion position) -# j = 2 - 1 = 1 (index of latest job ending ≤ start_time) diff --git a/leetcode_old/maximum_profit_in_job_scheduling/tests.py b/leetcode_old/maximum_profit_in_job_scheduling/tests.py deleted file mode 100644 index 21da4c0..0000000 --- a/leetcode_old/maximum_profit_in_job_scheduling/tests.py +++ /dev/null @@ -1,34 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestMaximumProfitInJobScheduling: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "start_time, end_time, profit, expected", - [ - # Original examples - ([1, 2, 3, 3], [3, 4, 5, 6], [50, 10, 40, 70], 120), - ([1, 2, 3, 4, 6], [3, 5, 10, 6, 9], [20, 20, 100, 70, 60], 150), - ([1, 1, 1], [2, 3, 4], [5, 6, 4], 6), - # Edge cases - ([1], [2], [100], 100), # Single job - ([1, 2], [2, 3], [50, 100], 150), # Adjacent jobs - ([1, 3], [2, 4], [50, 100], 150), # Overlapping jobs - can take both - ([1, 3, 5], [2, 4, 6], [10, 20, 30], 60), # No overlaps - take all - ([1, 1, 1], [2, 2, 2], [10, 20, 30], 30), # Same time slots - take best - ([1, 2, 3, 4], [2, 3, 4, 5], [1, 1, 1, 1], 4), # All same profit - take all - ([1, 5, 10], [3, 7, 12], [100, 1, 1], 102), # High profit + non-overlapping - ], - ) - @logged_test - def test_job_scheduling( - self, start_time: list[int], end_time: list[int], profit: list[int], expected: int - ): - result = self.solution.job_scheduling(start_time, end_time, profit) - assert result == expected diff --git a/leetcode_old/maximum_subarray/README.md b/leetcode_old/maximum_subarray/README.md deleted file mode 100644 index e4d7afa..0000000 --- a/leetcode_old/maximum_subarray/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Maximum Subarray - -**Difficulty:** Medium -**Topics:** Array, Divide and Conquer, Dynamic Programming -**Tags:** grind-75 - -**LeetCode:** [Problem 53](https://leetcode.com/problems/maximum-subarray/description/) - -## Problem Description - -Given an integer array `nums`, find the subarray with the largest sum, and return its sum. - -## Examples - -### Example 1: - -``` -Input: nums = [-2,1,-3,4,-1,2,1,-5,4] -Output: 6 -``` - -**Explanation:** The subarray [4,-1,2,1] has the largest sum 6. - -### Example 2: - -``` -Input: nums = [1] -Output: 1 -``` - -**Explanation:** The subarray [1] has the largest sum 1. - -### Example 3: - -``` -Input: nums = [5,4,-1,7,8] -Output: 23 -``` - -**Explanation:** The subarray [5,4,-1,7,8] has the largest sum 23. - -## Constraints - -- `1 <= nums.length <= 10^5` -- `-10^4 <= nums[i] <= 10^4` - -**Follow up:** If you have figured out the `O(n)` solution, try coding another solution using the **divide and conquer** approach, which is more subtle. diff --git a/leetcode_old/maximum_subarray/__init__.py b/leetcode_old/maximum_subarray/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/maximum_subarray/playground.ipynb b/leetcode_old/maximum_subarray/playground.ipynb deleted file mode 100644 index a34773e..0000000 --- a/leetcode_old/maximum_subarray/playground.ipynb +++ /dev/null @@ -1,68 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]\n", - "expected = 6" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().max_sub_array(nums)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/maximum_subarray/solution.py b/leetcode_old/maximum_subarray/solution.py deleted file mode 100644 index 2176165..0000000 --- a/leetcode_old/maximum_subarray/solution.py +++ /dev/null @@ -1,11 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(1) - def max_sub_array(self, nums: list[int]) -> int: - max_sum = current_sum = nums[0] - - for i in range(1, len(nums)): - current_sum = max(nums[i], current_sum + nums[i]) - max_sum = max(max_sum, current_sum) - - return max_sum diff --git a/leetcode_old/maximum_subarray/tests.py b/leetcode_old/maximum_subarray/tests.py deleted file mode 100644 index f186921..0000000 --- a/leetcode_old/maximum_subarray/tests.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestMaximumSubarray: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "nums, expected", - [ - ([-2, 1, -3, 4, -1, 2, 1, -5, 4], 6), - ([1], 1), - ([5, 4, -1, 7, 8], 23), - ([-1], -1), - ([-2, -1], -1), - ([1, 2, 3, 4, 5], 15), - ([-5, -2, -8, -1], -1), - ], - ) - @logged_test - def test_max_sub_array(self, nums: list[int], expected: int): - result = self.solution.max_sub_array(nums) - assert result == expected diff --git a/leetcode_old/merge_intervals/README.md b/leetcode_old/merge_intervals/README.md deleted file mode 100644 index 0f648cf..0000000 --- a/leetcode_old/merge_intervals/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Merge Intervals - -**Difficulty:** Medium -**Topics:** Array, Sorting -**Tags:** grind-75 - -**LeetCode:** [Problem 56](https://leetcode.com/problems/merge-intervals/description/) - -## Problem Description - -Given an array of `intervals` where `intervals[i] = [starti, endi]`, merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input. - -## Examples - -### Example 1: - -``` -Input: intervals = [[1,3],[2,6],[8,10],[15,18]] -Output: [[1,6],[8,10],[15,18]] -``` - -**Explanation:** Since intervals [1,3] and [2,6] overlap, merge them into [1,6]. - -### Example 2: - -``` -Input: intervals = [[1,4],[4,5]] -Output: [[1,5]] -``` - -**Explanation:** Intervals [1,4] and [4,5] are considered overlapping. - -### Example 3: - -``` -Input: intervals = [[4,7],[1,4]] -Output: [[1,7]] -``` - -**Explanation:** Intervals [1,4] and [4,7] are considered overlapping. - -## Constraints - -- `1 <= intervals.length <= 10^4` -- `intervals[i].length == 2` -- `0 <= starti <= endi <= 10^4` diff --git a/leetcode_old/merge_intervals/__init__.py b/leetcode_old/merge_intervals/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/merge_intervals/playground.ipynb b/leetcode_old/merge_intervals/playground.ipynb deleted file mode 100644 index 99158ad..0000000 --- a/leetcode_old/merge_intervals/playground.ipynb +++ /dev/null @@ -1,68 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "intervals = [[1, 3], [2, 6], [8, 10], [15, 18]]\n", - "expected = [[1, 6], [8, 10], [15, 18]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().merge(intervals)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/merge_intervals/solution.py b/leetcode_old/merge_intervals/solution.py deleted file mode 100644 index 55ca082..0000000 --- a/leetcode_old/merge_intervals/solution.py +++ /dev/null @@ -1,14 +0,0 @@ -class Solution: - # Time: O(n log n) - # Space: O(1) - def merge(self, intervals: list[list[int]]) -> list[list[int]]: - intervals.sort() - merged = [intervals[0]] - - for start, end in intervals[1:]: - if start <= merged[-1][1]: - merged[-1][1] = max(merged[-1][1], end) - else: - merged.append([start, end]) - - return merged diff --git a/leetcode_old/merge_intervals/tests.py b/leetcode_old/merge_intervals/tests.py deleted file mode 100644 index 812653e..0000000 --- a/leetcode_old/merge_intervals/tests.py +++ /dev/null @@ -1,32 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestMergeIntervals: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "intervals, expected", - [ - # Original test cases - ([[1, 3], [2, 6], [8, 10], [15, 18]], [[1, 6], [8, 10], [15, 18]]), - ([[1, 4], [4, 5]], [[1, 5]]), - ([[4, 7], [1, 4]], [[1, 7]]), - # Edge cases - ([[1, 1]], [[1, 1]]), # Single point interval - ([[1, 2], [3, 4]], [[1, 2], [3, 4]]), # No overlap - ([[1, 4], [2, 3]], [[1, 4]]), # Complete overlap - ([[1, 10], [2, 6], [8, 10], [15, 18]], [[1, 10], [15, 18]]), # Multiple merges - ([[0, 0], [1, 1], [2, 2]], [[0, 0], [1, 1], [2, 2]]), # All single points - ([[1, 3], [2, 6], [8, 10], [9, 12], [15, 18]], [[1, 6], [8, 12], [15, 18]]), # Chain merge - ([[4, 5], [4, 6]], [[4, 6]]), # Same start time - ], - ) - @logged_test - def test_merge(self, intervals: list[list[int]], expected: list[list[int]]): - result = self.solution.merge(intervals) - assert result == expected diff --git a/leetcode_old/merge_k_sorted_lists/README.md b/leetcode_old/merge_k_sorted_lists/README.md deleted file mode 100644 index 7912c38..0000000 --- a/leetcode_old/merge_k_sorted_lists/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Merge k Sorted Lists - -**Difficulty:** Hard -**Topics:** Linked List, Divide and Conquer, Heap (Priority Queue), Merge Sort -**Tags:** grind-75 - -**LeetCode:** [Problem 23](https://leetcode.com/problems/merge-k-sorted-lists/description/) - -## Problem Description - -You are given an array of `k` linked-lists `lists`, each linked-list is sorted in ascending order. - -_Merge all the linked-lists into one sorted linked-list and return it._ - -## Examples - -### Example 1: - -``` -Input: lists = [[1,4,5],[1,3,4],[2,6]] -Output: [1,1,2,3,4,4,5,6] -``` - -**Explanation:** The linked-lists are: - -``` -[ - 1->4->5, - 1->3->4, - 2->6 -] -``` - -merging them into one sorted linked list: - -``` -1->1->2->3->4->4->5->6 -``` - -### Example 2: - -``` -Input: lists = [] -Output: [] -``` - -### Example 3: - -``` -Input: lists = [[]] -Output: [] -``` - -## Constraints - -- `k == lists.length` -- `0 <= k <= 10^4` -- `0 <= lists[i].length <= 500` -- `-10^4 <= lists[i][j] <= 10^4` -- `lists[i]` is sorted in ascending order. -- The sum of `lists[i].length` will not exceed `10^4`. diff --git a/leetcode_old/merge_k_sorted_lists/__init__.py b/leetcode_old/merge_k_sorted_lists/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/merge_k_sorted_lists/playground.ipynb b/leetcode_old/merge_k_sorted_lists/playground.ipynb deleted file mode 100644 index f374726..0000000 --- a/leetcode_old/merge_k_sorted_lists/playground.ipynb +++ /dev/null @@ -1,83 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 7, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import ListNode" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "lists_data = [[1, 4, 5], [1, 3, 4], [2, 6]]\n", - "lists = [ListNode.from_list(lst) for lst in lists_data]\n", - "expected_data = [1, 1, 2, 3, 4, 4, 5, 6]" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[1, 1, 2, 3, 4, 4, 5, 6]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().merge_k_lists(lists)\n", - "ListNode.to_list(result) if result else []" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "expected = ListNode.from_list(expected_data)\n", - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/merge_k_sorted_lists/solution.py b/leetcode_old/merge_k_sorted_lists/solution.py deleted file mode 100644 index d6ed124..0000000 --- a/leetcode_old/merge_k_sorted_lists/solution.py +++ /dev/null @@ -1,35 +0,0 @@ -from leetcode_py import ListNode - - -class Solution: - # Time: O(n log k) where n is total nodes, k is number of lists - # Space: O(log k) for recursion stack - def merge_k_lists(self, lists: list[ListNode | None]) -> ListNode | None: - if not lists: - return None - return self._divide_conquer(lists, 0, len(lists) - 1) - - def _divide_conquer(self, lists: list[ListNode | None], left: int, right: int) -> ListNode | None: - if left == right: - return lists[left] - - mid = (left + right) // 2 - l1 = self._divide_conquer(lists, left, mid) - l2 = self._divide_conquer(lists, mid + 1, right) - return self._merge_two(l1, l2) - - def _merge_two(self, l1: ListNode | None, l2: ListNode | None) -> ListNode | None: - dummy = ListNode(0) - curr = dummy - - while l1 and l2: - if l1.val <= l2.val: - curr.next = l1 - l1 = l1.next - else: - curr.next = l2 - l2 = l2.next - curr = curr.next - - curr.next = l1 or l2 - return dummy.next diff --git a/leetcode_old/merge_k_sorted_lists/tests.py b/leetcode_old/merge_k_sorted_lists/tests.py deleted file mode 100644 index 7249fc0..0000000 --- a/leetcode_old/merge_k_sorted_lists/tests.py +++ /dev/null @@ -1,45 +0,0 @@ -import pytest - -from leetcode_py import ListNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestMergeKSortedLists: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "lists_data, expected_data", - [ - ([[1, 4, 5], [1, 3, 4], [2, 6]], [1, 1, 2, 3, 4, 4, 5, 6]), - ([], []), - ([[]], []), - ([[1]], [1]), - ([[1, 2], [3, 4]], [1, 2, 3, 4]), - ([[5], [1, 3], [2, 4, 6]], [1, 2, 3, 4, 5, 6]), - ([[-1, 0, 1], [-2, 2]], [-2, -1, 0, 1, 2]), - ([[1, 1, 1], [2, 2, 2]], [1, 1, 1, 2, 2, 2]), - ([[]], []), - ([[], [1], []], [1]), - ([[0]], [0]), - ([[-10, -5, -1], [-8, -3], [-7, -2, 0]], [-10, -8, -7, -5, -3, -2, -1, 0]), - ([[1, 2, 3, 4, 5]], [1, 2, 3, 4, 5]), - ([[10], [9], [8], [7]], [7, 8, 9, 10]), - ([[1, 100], [2, 99], [3, 98]], [1, 2, 3, 98, 99, 100]), - ([[], [], []], []), - ([[0, 0, 0], [0, 0]], [0, 0, 0, 0, 0]), - ([[1, 3, 5, 7], [2, 4, 6, 8]], [1, 2, 3, 4, 5, 6, 7, 8]), - ( - [[100, 200, 300], [50, 150, 250], [75, 125, 175]], - [50, 75, 100, 125, 150, 175, 200, 250, 300], - ), - ], - ) - @logged_test - def test_merge_k_lists(self, lists_data: list[list[int]], expected_data: list[int]): - lists = [ListNode.from_list(lst) for lst in lists_data] - result = self.solution.merge_k_lists(lists) - expected = ListNode.from_list(expected_data) - assert result == expected diff --git a/leetcode_old/merge_two_sorted_lists/README.md b/leetcode_old/merge_two_sorted_lists/README.md deleted file mode 100644 index a9721a5..0000000 --- a/leetcode_old/merge_two_sorted_lists/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Merge Two Sorted Lists - -**Difficulty:** Easy -**Topics:** Linked List, Recursion -**Tags:** grind-75 - -**LeetCode:** [Problem 21](https://leetcode.com/problems/merge-two-sorted-lists/description/) - -## Problem Description - -You are given the heads of two sorted linked lists `list1` and `list2`. - -Merge the two lists into one **sorted** list. The list should be made by splicing together the nodes of the first two lists. - -Return _the head of the merged linked list_. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2020/10/03/merge_ex1.jpg) - -``` -Input: list1 = [1,2,4], list2 = [1,3,4] -Output: [1,1,2,3,4,4] -``` - -### Example 2: - -``` -Input: list1 = [], list2 = [] -Output: [] -``` - -### Example 3: - -``` -Input: list1 = [], list2 = [0] -Output: [0] -``` - -## Constraints - -- The number of nodes in both lists is in the range `[0, 50]`. -- `-100 <= Node.val <= 100` -- Both `list1` and `list2` are sorted in **non-decreasing** order. diff --git a/leetcode_old/merge_two_sorted_lists/__init__.py b/leetcode_old/merge_two_sorted_lists/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/merge_two_sorted_lists/playground.ipynb b/leetcode_old/merge_two_sorted_lists/playground.ipynb deleted file mode 100644 index 492ef37..0000000 --- a/leetcode_old/merge_two_sorted_lists/playground.ipynb +++ /dev/null @@ -1,74 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import ListNode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "list1_vals = [1, 2, 4]\n", - "list2_vals = [1, 3, 4]\n", - "expected_vals = [1, 1, 2, 3, 4, 4]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "list1 = ListNode.from_list(list1_vals)\n", - "list2 = ListNode.from_list(list2_vals)\n", - "result = Solution().merge_two_lists(list1, list2)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "expected = ListNode.from_list(expected_vals)\n", - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/merge_two_sorted_lists/solution.py b/leetcode_old/merge_two_sorted_lists/solution.py deleted file mode 100644 index 07ebb03..0000000 --- a/leetcode_old/merge_two_sorted_lists/solution.py +++ /dev/null @@ -1,22 +0,0 @@ -from leetcode_py import ListNode - - -class Solution: - # Time: O(m + n) - # Space: O(1) - def merge_two_lists(self, list1: ListNode | None, list2: ListNode | None) -> ListNode | None: - - dummy = ListNode(0) - current = dummy - - while list1 and list2: - if list1.val <= list2.val: - current.next = list1 - list1 = list1.next - else: - current.next = list2 - list2 = list2.next - current = current.next - - current.next = list1 or list2 - return dummy.next diff --git a/leetcode_old/merge_two_sorted_lists/tests.py b/leetcode_old/merge_two_sorted_lists/tests.py deleted file mode 100644 index 5cb715d..0000000 --- a/leetcode_old/merge_two_sorted_lists/tests.py +++ /dev/null @@ -1,37 +0,0 @@ -import pytest - -from leetcode_py import ListNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestMergeTwoSortedLists: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "list1_vals, list2_vals, expected_vals", - [ - ([1, 2, 4], [1, 3, 4], [1, 1, 2, 3, 4, 4]), - ([], [], []), - ([], [0], [0]), - ([0], [], [0]), - ([1], [2], [1, 2]), - ([2], [1], [1, 2]), - ([1, 1, 1], [2, 2, 2], [1, 1, 1, 2, 2, 2]), - ([1, 3, 5], [2, 4, 6], [1, 2, 3, 4, 5, 6]), - ([-10, -5, 0], [-8, -3, 1], [-10, -8, -5, -3, 0, 1]), - ([5], [1, 2, 3, 4], [1, 2, 3, 4, 5]), - ([1, 2, 3, 4], [5], [1, 2, 3, 4, 5]), - ], - ) - @logged_test - def test_merge_two_lists( - self, list1_vals: list[int], list2_vals: list[int], expected_vals: list[int] - ): - list1 = ListNode.from_list(list1_vals) - list2 = ListNode.from_list(list2_vals) - expected = ListNode.from_list(expected_vals) - result = self.solution.merge_two_lists(list1, list2) - assert result == expected diff --git a/leetcode_old/middle_of_the_linked_list/README.md b/leetcode_old/middle_of_the_linked_list/README.md deleted file mode 100644 index 1f36bf2..0000000 --- a/leetcode_old/middle_of_the_linked_list/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Middle of the Linked List - -**Difficulty:** Easy -**Topics:** Linked List, Two Pointers -**Tags:** grind-75 - -**LeetCode:** [Problem 876](https://leetcode.com/problems/middle-of-the-linked-list/description/) - -## Problem Description - -Given the `head` of a singly linked list, return _the middle node of the linked list_. - -If there are two middle nodes, return **the second middle** node. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2021/07/23/lc-midlist1.jpg) - -``` -Input: head = [1,2,3,4,5] -Output: [3,4,5] -``` - -**Explanation:** The middle node of the list is node 3. - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2021/07/23/lc-midlist2.jpg) - -``` -Input: head = [1,2,3,4,5,6] -Output: [4,5,6] -``` - -**Explanation:** Since the list has two middle nodes with values 3 and 4, we return the second one. - -## Constraints - -- The number of nodes in the list is in the range `[1, 100]`. -- `1 <= Node.val <= 100` diff --git a/leetcode_old/middle_of_the_linked_list/__init__.py b/leetcode_old/middle_of_the_linked_list/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/middle_of_the_linked_list/playground.ipynb b/leetcode_old/middle_of_the_linked_list/playground.ipynb deleted file mode 100644 index 921ff0f..0000000 --- a/leetcode_old/middle_of_the_linked_list/playground.ipynb +++ /dev/null @@ -1,127 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 5, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import ListNode" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "head_list = [1, 2, 3, 4, 5]\n", - "expected_list = [3, 4, 5]" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_0\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "node_1\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "node_0->node_1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_2\n", - "\n", - "5\n", - "\n", - "\n", - "\n", - "node_1->node_2\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "ListNode([3, 4, 5])" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "head = ListNode.from_list(head_list)\n", - "result = Solution().middle_node(head)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "expected = ListNode.from_list(expected_list)\n", - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/middle_of_the_linked_list/solution.py b/leetcode_old/middle_of_the_linked_list/solution.py deleted file mode 100644 index bec661f..0000000 --- a/leetcode_old/middle_of_the_linked_list/solution.py +++ /dev/null @@ -1,13 +0,0 @@ -from leetcode_py import ListNode - - -class Solution: - # Time: O(n) - # Space: O(1) - def middle_node(self, head: ListNode | None) -> ListNode | None: - slow = fast = head - while fast and fast.next: - assert slow is not None - slow = slow.next - fast = fast.next.next - return slow diff --git a/leetcode_old/middle_of_the_linked_list/tests.py b/leetcode_old/middle_of_the_linked_list/tests.py deleted file mode 100644 index 7454186..0000000 --- a/leetcode_old/middle_of_the_linked_list/tests.py +++ /dev/null @@ -1,31 +0,0 @@ -import pytest - -from leetcode_py import ListNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestMiddleOfTheLinkedList: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "head_list, expected_list", - [ - ([1, 2, 3, 4, 5], [3, 4, 5]), # odd length - ([1, 2, 3, 4, 5, 6], [4, 5, 6]), # even length - ([1], [1]), # single node - ([1, 2], [2]), # two nodes - ([1, 2, 3], [2, 3]), # three nodes - ([1, 2, 3, 4], [3, 4]), # four nodes - ([10, 20, 30, 40, 50, 60, 70], [40, 50, 60, 70]), # larger odd - ([5, 15, 25, 35], [25, 35]), # larger even - ], - ) - @logged_test - def test_middle_node(self, head_list: list[int], expected_list: list[int]): - head = ListNode.from_list(head_list) - expected = ListNode.from_list(expected_list) - result = self.solution.middle_node(head) - assert result == expected diff --git a/leetcode_old/min_stack/README.md b/leetcode_old/min_stack/README.md deleted file mode 100644 index 46edefb..0000000 --- a/leetcode_old/min_stack/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Min Stack - -**Difficulty:** Medium -**Topics:** Stack, Design -**Tags:** grind-75 - -**LeetCode:** [Problem 155](https://leetcode.com/problems/min-stack/description/) - -## Problem Description - -Design a stack that supports push, pop, top, and retrieving the minimum element in constant time. - -Implement the `MinStack` class: - -- `MinStack()` initializes the stack object. -- `void push(int val)` pushes the element `val` onto the stack. -- `void pop()` removes the element on the top of the stack. -- `int top()` gets the top element of the stack. -- `int getMin()` retrieves the minimum element in the stack. - -You must implement a solution with `O(1)` time complexity for each function. - -## Examples - -### Example 1: - -``` -Input -["MinStack","push","push","push","getMin","pop","top","getMin"] -[[],[-2],[0],[-3],[],[],[],[]] - -Output -[null,null,null,null,-3,null,0,-2] -``` - -**Explanation:** - -``` -MinStack minStack = new MinStack(); -minStack.push(-2); -minStack.push(0); -minStack.push(-3); -minStack.getMin(); // return -3 -minStack.pop(); -minStack.top(); // return 0 -minStack.getMin(); // return -2 -``` - -## Constraints - -- `-2^31 <= val <= 2^31 - 1` -- Methods `pop`, `top` and `getMin` operations will always be called on **non-empty** stacks. -- At most `3 * 10^4` calls will be made to `push`, `pop`, `top`, and `getMin`. diff --git a/leetcode_old/min_stack/__init__.py b/leetcode_old/min_stack/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/min_stack/playground.ipynb b/leetcode_old/min_stack/playground.ipynb deleted file mode 100644 index 72b888c..0000000 --- a/leetcode_old/min_stack/playground.ipynb +++ /dev/null @@ -1,95 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import MinStack" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "operations = [\"MinStack\", \"push\", \"push\", \"push\", \"getMin\", \"pop\", \"top\", \"getMin\"]\n", - "inputs = [[], [-2], [0], [-3], [], [], [], []]" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[None, None, None, None, -3, None, 0, -2]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "stack = None\n", - "results: list[int | None] = []\n", - "for i, op in enumerate(operations):\n", - " if op == \"MinStack\":\n", - " stack = MinStack()\n", - " results.append(None)\n", - " elif op == \"push\" and stack is not None:\n", - " stack.push(inputs[i][0])\n", - " results.append(None)\n", - " elif op == \"pop\" and stack is not None:\n", - " stack.pop()\n", - " results.append(None)\n", - " elif op == \"top\" and stack is not None:\n", - " results.append(stack.top())\n", - " elif op == \"getMin\" and stack is not None:\n", - " results.append(stack.get_min())\n", - "results" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "expected = [None, None, None, None, -3, None, 0, -2]\n", - "assert results == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/min_stack/solution.py b/leetcode_old/min_stack/solution.py deleted file mode 100644 index fc59403..0000000 --- a/leetcode_old/min_stack/solution.py +++ /dev/null @@ -1,43 +0,0 @@ -class MinStack: - # Time: O(1) for all operations - # Space: O(n) where n is number of elements - def __init__(self) -> None: - self.stack: list[int] = [] - self.min_stack: list[int] = [] - - # Time: O(1) - # Space: O(1) - def push(self, val: int) -> None: - self.stack.append(val) - if not self.min_stack or val <= self.min_stack[-1]: - self.min_stack.append(val) - - # Time: O(1) - # Space: O(1) - def pop(self) -> None: - if self.stack[-1] == self.min_stack[-1]: - self.min_stack.pop() - self.stack.pop() - - # Time: O(1) - # Space: O(1) - def top(self) -> int: - return self.stack[-1] - - # Time: O(1) - # Space: O(1) - def get_min(self) -> int: - return self.min_stack[-1] - - -# Example walkthrough: push(-2), push(0), push(-3), getMin(), pop(), top(), getMin() -# -# Initial: stack=[], min_stack=[] -# -# push(-2): stack=[-2], min_stack=[-2] (first element, add to both) -# push(0): stack=[-2,0], min_stack=[-2] (0 > -2, don't add to min_stack) -# push(-3): stack=[-2,0,-3], min_stack=[-2,-3] (-3 <= -2, add to min_stack) -# getMin(): return -3 (top of min_stack) -# pop(): stack=[-2,0], min_stack=[-2] (-3 was min, remove from both stacks) -# top(): return 0 (top of main stack) -# getMin(): return -2 (top of min_stack after pop) diff --git a/leetcode_old/min_stack/tests.py b/leetcode_old/min_stack/tests.py deleted file mode 100644 index af73df4..0000000 --- a/leetcode_old/min_stack/tests.py +++ /dev/null @@ -1,52 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import MinStack - - -class TestMinStack: - @pytest.mark.parametrize( - "operations, inputs, expected", - [ - ( - ["MinStack", "push", "push", "push", "getMin", "pop", "top", "getMin"], - [[], [-2], [0], [-3], [], [], [], []], - [None, None, None, None, -3, None, 0, -2], - ), - ( - ["MinStack", "push", "top", "getMin", "pop"], - [[], [5], [], [], []], - [None, None, 5, 5, None], - ), - ( - ["MinStack", "push", "push", "push", "getMin", "pop", "getMin", "pop", "getMin"], - [[], [1], [1], [2], [], [], [], [], []], - [None, None, None, None, 1, None, 1, None, 1], - ), - ( - ["MinStack", "push", "push", "getMin", "push", "getMin", "pop", "getMin"], - [[], [3], [1], [], [0], [], [], []], - [None, None, None, 1, None, 0, None, 1], - ), - ], - ) - @logged_test - def test_min_stack(self, operations: list[str], inputs: list[list[int]], expected: list[int | None]): - stack: MinStack | None = None - results: list[int | None] = [] - for i, op in enumerate(operations): - if op == "MinStack": - stack = MinStack() - results.append(None) - elif op == "push" and stack is not None: - stack.push(inputs[i][0]) - results.append(None) - elif op == "pop" and stack is not None: - stack.pop() - results.append(None) - elif op == "top" and stack is not None: - results.append(stack.top()) - elif op == "getMin" and stack is not None: - results.append(stack.get_min()) - assert results == expected diff --git a/leetcode_old/minimum_height_trees/README.md b/leetcode_old/minimum_height_trees/README.md deleted file mode 100644 index 15ad725..0000000 --- a/leetcode_old/minimum_height_trees/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Minimum Height Trees - -**Difficulty:** Medium -**Topics:** Depth-First Search, Breadth-First Search, Graph, Topological Sort -**Tags:** grind-75 - -**LeetCode:** [Problem 310](https://leetcode.com/problems/minimum-height-trees/description/) - -## Problem Description - -A tree is an undirected graph in which any two vertices are connected by _exactly_ one path. In other words, any connected graph without simple cycles is a tree. - -Given a tree of `n` nodes labelled from `0` to `n - 1`, and an array of `n - 1` `edges` where `edges[i] = [ai, bi]` indicates that there is an undirected edge between the two nodes `ai` and `bi` in the tree, you can choose any node of the tree as the root. When you select a node `x` as the root, the result tree has height `h`. Among all possible rooted trees, those with minimum height (i.e. `min(h)`) are called **minimum height trees** (MHTs). - -Return _a list of all **MHTs'** root labels_. You can return the answer in **any order**. - -The **height** of a rooted tree is the number of edges on the longest downward path between the root and a leaf. - -## Examples - -### Example 1: - - - -``` -Input: n = 4, edges = [[1,0],[1,2],[1,3]] -Output: [1] -Explanation: As shown, the height of the tree is 1 when the root is the node with label 1 which is the only MHT. -``` - -### Example 2: - - - -``` -Input: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]] -Output: [3,4] -``` - -## Constraints - -- `1 <= n <= 2 * 10^4` -- `edges.length == n - 1` -- `0 <= ai, bi < n` -- `ai != bi` -- All the pairs `(ai, bi)` are distinct. -- The given input is **guaranteed** to be a tree and there will be **no repeated** edges. diff --git a/leetcode_old/minimum_height_trees/__init__.py b/leetcode_old/minimum_height_trees/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/minimum_height_trees/playground.ipynb b/leetcode_old/minimum_height_trees/playground.ipynb deleted file mode 100644 index 51e0605..0000000 --- a/leetcode_old/minimum_height_trees/playground.ipynb +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "n = 4\n", - "edges = [[1, 0], [1, 2], [1, 3]]\n", - "expected = [1]" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[1]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().find_min_height_trees(n, edges)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert sorted(result) == sorted(expected)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/minimum_height_trees/solution.py b/leetcode_old/minimum_height_trees/solution.py deleted file mode 100644 index 5b9a6d2..0000000 --- a/leetcode_old/minimum_height_trees/solution.py +++ /dev/null @@ -1,29 +0,0 @@ -from collections import defaultdict, deque - - -class Solution: - # Time: O(V) - # Space: O(V) - def find_min_height_trees(self, n: int, edges: list[list[int]]) -> list[int]: - if n == 1: - return [0] - - graph = defaultdict(set) - for u, v in edges: - graph[u].add(v) - graph[v].add(u) - - leaves = deque([i for i in range(n) if len(graph[i]) == 1]) - - remaining = n - while remaining > 2: - size = len(leaves) - remaining -= size - for _ in range(size): - leaf = leaves.popleft() - neighbor = graph[leaf].pop() - graph[neighbor].remove(leaf) - if len(graph[neighbor]) == 1: - leaves.append(neighbor) - - return list(leaves) diff --git a/leetcode_old/minimum_height_trees/tests.py b/leetcode_old/minimum_height_trees/tests.py deleted file mode 100644 index 11fd934..0000000 --- a/leetcode_old/minimum_height_trees/tests.py +++ /dev/null @@ -1,23 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestMinimumHeightTrees: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "n, edges, expected", - [ - (4, [[1, 0], [1, 2], [1, 3]], [1]), - (6, [[3, 0], [3, 1], [3, 2], [3, 4], [5, 4]], [3, 4]), - (1, [], [0]), - ], - ) - @logged_test - def test_find_min_height_trees(self, n: int, edges: list[list[int]], expected: list[int]): - result = self.solution.find_min_height_trees(n, edges) - assert sorted(result) == sorted(expected) diff --git a/leetcode_old/minimum_window_substring/README.md b/leetcode_old/minimum_window_substring/README.md deleted file mode 100644 index f54b7da..0000000 --- a/leetcode_old/minimum_window_substring/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Minimum Window Substring - -**Difficulty:** Hard -**Topics:** Hash Table, String, Sliding Window -**Tags:** grind-75 - -**LeetCode:** [Problem 76](https://leetcode.com/problems/minimum-window-substring/description/) - -## Problem Description - -Given two strings `s` and `t` of lengths `m` and `n` respectively, return the **minimum window substring** of `s` such that every character in `t` (including duplicates) is included in the window. If there is no such substring, return the empty string `""`. - -The testcases will be generated such that the answer is unique. - -## Examples - -### Example 1: - -``` -Input: s = "ADOBECODEBANC", t = "ABC" -Output: "BANC" -``` - -**Explanation:** The minimum window substring "BANC" includes 'A', 'B', and 'C' from string t. - -### Example 2: - -``` -Input: s = "a", t = "a" -Output: "a" -``` - -**Explanation:** The entire string s is the minimum window. - -### Example 3: - -``` -Input: s = "a", t = "aa" -Output: "" -``` - -**Explanation:** Both 'a's from t must be included in the window. Since the largest window of s only has one 'a', return empty string. - -## Constraints - -- `m == s.length` -- `n == t.length` -- `1 <= m, n <= 10^5` -- `s` and `t` consist of uppercase and lowercase English letters. - -**Follow up:** Could you find an algorithm that runs in `O(m + n)` time? diff --git a/leetcode_old/minimum_window_substring/__init__.py b/leetcode_old/minimum_window_substring/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/minimum_window_substring/playground.ipynb b/leetcode_old/minimum_window_substring/playground.ipynb deleted file mode 100644 index 1f99f98..0000000 --- a/leetcode_old/minimum_window_substring/playground.ipynb +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "s = \"ADOBECODEBANC\"\n", - "t = \"ABC\"\n", - "expected = \"BANC\"" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'BANC'" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().min_window(s, t)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/minimum_window_substring/solution.py b/leetcode_old/minimum_window_substring/solution.py deleted file mode 100644 index 60bcb89..0000000 --- a/leetcode_old/minimum_window_substring/solution.py +++ /dev/null @@ -1,48 +0,0 @@ -from collections import Counter - - -class Solution: - # Sliding Window - # Time: O(m + n) where m = len(s), n = len(t) - # Space: O(k) where k is unique chars in t - def min_window(self, s: str, t: str) -> str: - if not t or len(t) > len(s): - return "" - - need = Counter(t) - - left = 0 - formed = 0 - required = len(need) - window_counts: dict[str, int] = {} - - # Result: (window length, left, right) - ans: tuple[float, int | None, int | None] = (float("inf"), None, None) - - for right in range(len(s)): - char = s[right] - window_counts[char] = window_counts.get(char, 0) + 1 - - # Check if current char frequency matches desired frequency in t - if char in need and window_counts[char] == need[char]: - formed += 1 - - # Contract window until it's no longer valid - while left <= right and formed == required: - char = s[left] - - # Update result if this window is smaller - if right - left + 1 < ans[0]: - ans = (right - left + 1, left, right) - - # Remove from left - window_counts[char] -= 1 - if char in need and window_counts[char] < need[char]: - formed -= 1 - - left += 1 - - if ans[0] == float("inf"): - return "" - assert ans[1] is not None and ans[2] is not None - return s[ans[1] : ans[2] + 1] diff --git a/leetcode_old/minimum_window_substring/tests.py b/leetcode_old/minimum_window_substring/tests.py deleted file mode 100644 index 8a3e16a..0000000 --- a/leetcode_old/minimum_window_substring/tests.py +++ /dev/null @@ -1,39 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestMinimumWindowSubstring: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "s, t, expected", - [ - # Basic cases - ("ADOBECODEBANC", "ABC", "BANC"), - ("a", "a", "a"), - ("a", "aa", ""), - # Edge cases - ("", "a", ""), # Empty s - ("a", "", ""), # Empty t - ("", "", ""), # Both empty - ("ab", "ba", "ab"), # Same length - ("abc", "cba", "abc"), # Entire string needed - # Duplicates - ("ADOBECODEBANC", "AABC", "ADOBECODEBA"), # Correct: needs 2 A's, 1 B, 1 C - ("aa", "aa", "aa"), - # No solution - ("abc", "def", ""), - ("a", "b", ""), - # Multiple valid windows - ("ADOBECODEBANC", "AB", "BA"), # Correct: "BA" is shorter than "ADOB" - ("abcdef", "cf", "cdef"), - ], - ) - @logged_test - def test_min_window(self, s: str, t: str, expected: str): - result = self.solution.min_window(s, t) - assert result == expected diff --git a/leetcode_old/number_of_islands/README.md b/leetcode_old/number_of_islands/README.md deleted file mode 100644 index ada9064..0000000 --- a/leetcode_old/number_of_islands/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Number of Islands - -**Difficulty:** Medium -**Topics:** Array, Depth-First Search, Breadth-First Search, Union Find, Matrix -**Tags:** grind-75 - -**LeetCode:** [Problem 200](https://leetcode.com/problems/number-of-islands/description/) - -## Problem Description - -Given an `m x n` 2D binary grid `grid` which represents a map of `'1'`s (land) and `'0'`s (water), return _the number of islands_. - -An **island** is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water. - -## Examples - -### Example 1: - -``` -Input: grid = [ - ["1","1","1","1","0"], - ["1","1","0","1","0"], - ["1","1","0","0","0"], - ["0","0","0","0","0"] -] -Output: 1 -``` - -### Example 2: - -``` -Input: grid = [ - ["1","1","0","0","0"], - ["1","1","0","0","0"], - ["0","0","1","0","0"], - ["0","0","0","1","1"] -] -Output: 3 -``` - -## Constraints - -- `m == grid.length` -- `n == grid[i].length` -- `1 <= m, n <= 300` -- `grid[i][j]` is `'0'` or `'1'`. diff --git a/leetcode_old/number_of_islands/__init__.py b/leetcode_old/number_of_islands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/number_of_islands/playground.ipynb b/leetcode_old/number_of_islands/playground.ipynb deleted file mode 100644 index e943b42..0000000 --- a/leetcode_old/number_of_islands/playground.ipynb +++ /dev/null @@ -1,73 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "grid = [\n", - " [\"1\", \"1\", \"1\", \"1\", \"0\"],\n", - " [\"1\", \"1\", \"0\", \"1\", \"0\"],\n", - " [\"1\", \"1\", \"0\", \"0\", \"0\"],\n", - " [\"0\", \"0\", \"0\", \"0\", \"0\"],\n", - "]\n", - "expected = 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().num_islands(grid)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/number_of_islands/solution.py b/leetcode_old/number_of_islands/solution.py deleted file mode 100644 index 62368c8..0000000 --- a/leetcode_old/number_of_islands/solution.py +++ /dev/null @@ -1,27 +0,0 @@ -class Solution: - # Time: O(m * n) where m = rows, n = cols - # Space: O(m * n) for recursion stack in worst case - def num_islands(self, grid: list[list[str]]) -> int: - if not grid or not grid[0]: - return 0 - VISITED = "0" - UNVISITED_ISLAND = "1" - - rows, cols = len(grid), len(grid[0]) - islands = 0 - - def dfs(r: int, c: int) -> None: - if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] != UNVISITED_ISLAND: - return - - grid[r][c] = VISITED - for dr, dc in [(1, 0), (-1, 0), (0, 1), (0, -1)]: - dfs(r + dr, c + dc) - - for r in range(rows): - for c in range(cols): - if grid[r][c] == UNVISITED_ISLAND: - islands += 1 - dfs(r, c) - - return islands diff --git a/leetcode_old/number_of_islands/tests.py b/leetcode_old/number_of_islands/tests.py deleted file mode 100644 index c3eb4e3..0000000 --- a/leetcode_old/number_of_islands/tests.py +++ /dev/null @@ -1,67 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestNumberOfIslands: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "grid, expected", - [ - # Basic examples - ( - [ - ["1", "1", "1", "1", "0"], - ["1", "1", "0", "1", "0"], - ["1", "1", "0", "0", "0"], - ["0", "0", "0", "0", "0"], - ], - 1, - ), - ( - [ - ["1", "1", "0", "0", "0"], - ["1", "1", "0", "0", "0"], - ["0", "0", "1", "0", "0"], - ["0", "0", "0", "1", "1"], - ], - 3, - ), - # Edge cases - ([["1"]], 1), - ([["0"]], 0), - ([["1", "0", "1"], ["0", "1", "0"], ["1", "0", "1"]], 5), - # All water - ([["0", "0", "0"], ["0", "0", "0"]], 0), - # All land - ([["1", "1", "1"], ["1", "1", "1"]], 1), - # Single row - ([["1", "0", "1", "0", "1"]], 3), - # Single column - ([["1"], ["0"], ["1"], ["0"], ["1"]], 3), - # L-shaped island - ([["1", "1", "0"], ["1", "0", "0"], ["1", "1", "1"]], 1), - # Diagonal pattern (no connections) - ([["1", "0", "0"], ["0", "1", "0"], ["0", "0", "1"]], 3), - # Large grid with multiple islands - ( - [ - ["1", "0", "0", "1", "1", "0"], - ["0", "0", "0", "0", "1", "0"], - ["0", "0", "1", "0", "0", "0"], - ["1", "1", "0", "0", "0", "1"], - ], - 5, - ), - # Snake-like island - ([["1", "1", "0"], ["0", "1", "0"], ["0", "1", "1"]], 1), - ], - ) - @logged_test - def test_num_islands(self, grid: list[list[str]], expected: int): - result = self.solution.num_islands(grid) - assert result == expected diff --git a/leetcode_old/partition_equal_subset_sum/README.md b/leetcode_old/partition_equal_subset_sum/README.md deleted file mode 100644 index 82f48a5..0000000 --- a/leetcode_old/partition_equal_subset_sum/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Partition Equal Subset Sum - -**Difficulty:** Medium -**Topics:** Array, Dynamic Programming -**Tags:** grind-75 - -**LeetCode:** [Problem 416](https://leetcode.com/problems/partition-equal-subset-sum/description/) - -## Problem Description - -Given an integer array `nums`, return `true` if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or `false` otherwise. - -## Examples - -### Example 1: - -``` -Input: nums = [1,5,11,5] -Output: true -``` - -**Explanation:** The array can be partitioned as [1, 5, 5] and [11]. - -### Example 2: - -``` -Input: nums = [1,2,3,5] -Output: false -``` - -**Explanation:** The array cannot be partitioned into equal sum subsets. - -## Constraints - -- 1 <= nums.length <= 200 -- 1 <= nums[i] <= 100 diff --git a/leetcode_old/partition_equal_subset_sum/__init__.py b/leetcode_old/partition_equal_subset_sum/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/partition_equal_subset_sum/playground.ipynb b/leetcode_old/partition_equal_subset_sum/playground.ipynb deleted file mode 100644 index 388fafc..0000000 --- a/leetcode_old/partition_equal_subset_sum/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "nums = [1, 5, 11, 5]\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().can_partition(nums)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/partition_equal_subset_sum/solution.py b/leetcode_old/partition_equal_subset_sum/solution.py deleted file mode 100644 index 9a6bde9..0000000 --- a/leetcode_old/partition_equal_subset_sum/solution.py +++ /dev/null @@ -1,92 +0,0 @@ -class Solution: - # Time: O(n * sum) - # Space: O(sum) - def can_partition(self, nums: list[int]) -> bool: - """ - Example: nums = [1, 5, 11, 5], target = 11 - - Initial: dp = [T, F, F, F, F, F, F, F, F, F, F, F] - 0 1 2 3 4 5 6 7 8 9 10 11 - - After num=1: [T, T, F, F, F, F, F, F, F, F, F, F] - └─┘ (can make sum 1) - - After num=5: [T, T, F, F, F, T, T, F, F, F, F, F] - └─┘ └─┘ └─┘ (can make sums 5,6) - - After num=11:[T, T, F, F, F, T, T, F, F, F, F, T] - └─┘ (target!) - - Backward iteration prevents using same number twice - """ - total = sum(nums) - if total % 2: - return False - - target = total // 2 - dp = [False] * (target + 1) - dp[0] = True - - for num in nums: - for j in range(target, num - 1, -1): - dp[j] = dp[j] or dp[j - num] - - # Early termination: found target sum! - if dp[target]: - return True - - return False - - -class SolutionBitset: - # Time: O(n * sum) - # Space: O(1) - def can_partition(self, nums: list[int]) -> bool: - """ - Example: nums = [1, 5, 11, 5], target = 11 - - Bitset representation (bit position = achievable sum): - - Initial: dp = 1 (binary: 1) - Bits: ...0001 - Sums: {0} - - After num=1: dp |= dp << 1 - a = dp = 1 (bin: 0001) - b = dp << 1 = 2 (bin: 0010) - c = a | b = 3 (bin: 0011) - Sums: {0, 1} - - After num=5: dp |= dp << 5 - a = dp = 3 (bin: 0000011) - b = dp << 5 = 96 (bin: 1100000) - c = a | b = 99 (bin: 1100011) - Sums: {0, 1, 5, 6} - - After num=11: dp |= dp << 11 - a = dp = 99 (bin: 00000001100011) - b = dp << 11 = 202752 (bin: 110001100000000) - c = a | b = 202851 (bin: 110001101100011) - Sums: {0, 1, 5, 6, 11, 12, 16, 17} - - Check: (dp & (1 << 11)) != 0 - a = dp = 202851 (bin: 110001101100011) - b = 1 << 11 = 2048 (bin: 100000000000) - c = a & b = 2048 (bin: 100000000000) - c != 0 → bit 11 is set → True! - """ - total = sum(nums) - if total % 2 != 0: - return False - - target = total // 2 - dp = 1 - - for num in nums: - dp |= dp << num - - # Early termination: found target sum! - if (dp & (1 << target)) != 0: - return True - - return False diff --git a/leetcode_old/partition_equal_subset_sum/tests.py b/leetcode_old/partition_equal_subset_sum/tests.py deleted file mode 100644 index 303f5f2..0000000 --- a/leetcode_old/partition_equal_subset_sum/tests.py +++ /dev/null @@ -1,56 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution, SolutionBitset - - -class TestPartitionEqualSubsetSum: - @pytest.mark.parametrize("solution_class", [Solution, SolutionBitset]) - @pytest.mark.parametrize( - "nums, expected", - [ - ([1, 5, 11, 5], True), - ([1, 2, 3, 5], False), - ([1, 1], True), - ([1], False), - ([2, 2, 1, 1], True), - # Edge cases - ([100], False), - ([1, 1, 1, 1], True), - ([2, 3, 5], True), - # Large numbers - ([50, 50], True), - ([99, 1], False), - ([100, 100], True), - # Multiple solutions - ([1, 2, 3, 4], True), - ([3, 3, 3, 4, 5], True), - # No solution cases - ([1, 3, 5], False), - ([7, 11, 13], False), - # Larger arrays - ([1, 1, 1, 1, 1, 1, 1, 1], True), - ([2, 4, 6, 8, 10], False), - ([5, 10, 15, 20], True), - # Very large numbers (testing Python's arbitrary precision) - ([1000, 1000], True), - ([5000, 5000], True), - ([9999, 1], False), - ([10000, 10000], True), - # Extremely large numbers (beyond 64-bit) - ([2**16, 2**16], True), # 65536 each, target = 65536 - ([2**20, 2**20], True), # 1048576 each, target = 1048576 - ([2**20 + 1, 2**20 - 1], False), # sum = 2^21, target = 2^20, cannot partition - ], - ) - @logged_test - def test_can_partition( - self, - nums: list[int], - expected: bool, - solution_class: type[Solution | SolutionBitset], - ): - solution = solution_class() - result = solution.can_partition(nums) - assert result == expected diff --git a/leetcode_old/permutations/README.md b/leetcode_old/permutations/README.md deleted file mode 100644 index 69459b5..0000000 --- a/leetcode_old/permutations/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Permutations - -**Difficulty:** Medium -**Topics:** Array, Backtracking -**Tags:** grind-75 - -**LeetCode:** [Problem 46](https://leetcode.com/problems/permutations/description/) - -## Problem Description - -Given an array `nums` of distinct integers, return all the possible permutations. You can return the answer in any order. - -## Examples - -### Example 1: - -``` -Input: nums = [1,2,3] -Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] -``` - -### Example 2: - -``` -Input: nums = [0,1] -Output: [[0,1],[1,0]] -``` - -### Example 3: - -``` -Input: nums = [1] -Output: [[1]] -``` - -## Constraints - -- 1 <= nums.length <= 6 -- -10 <= nums[i] <= 10 -- All the integers of nums are unique. diff --git a/leetcode_old/permutations/__init__.py b/leetcode_old/permutations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/permutations/playground.ipynb b/leetcode_old/permutations/playground.ipynb deleted file mode 100644 index 8e7608d..0000000 --- a/leetcode_old/permutations/playground.ipynb +++ /dev/null @@ -1,75 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "nums = [1, 2, 3]\n", - "expected = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().permute(nums)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "# Check that we have the right number of permutations\n", - "assert len(result) == len(expected)\n", - "# Sort for comparison since order doesn't matter\n", - "result_sorted = [sorted(perm) for perm in result]\n", - "expected_sorted = [sorted(perm) for perm in expected]\n", - "result_sorted.sort()\n", - "expected_sorted.sort()\n", - "assert result_sorted == expected_sorted" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/permutations/solution.py b/leetcode_old/permutations/solution.py deleted file mode 100644 index 1bdf59f..0000000 --- a/leetcode_old/permutations/solution.py +++ /dev/null @@ -1,18 +0,0 @@ -class Solution: - # Time: O(n! * n) - # Space: O(n! * n) output + O(n) recursion - def permute(self, nums: list[int]) -> list[list[int]]: - result = [] - - def backtrack(start: int) -> None: - if start == len(nums): - result.append(nums[:]) - return - - for i in range(start, len(nums)): - nums[start], nums[i] = nums[i], nums[start] - backtrack(start + 1) - nums[start], nums[i] = nums[i], nums[start] - - backtrack(0) - return result diff --git a/leetcode_old/permutations/tests.py b/leetcode_old/permutations/tests.py deleted file mode 100644 index d455d72..0000000 --- a/leetcode_old/permutations/tests.py +++ /dev/null @@ -1,43 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestPermutations: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "nums, expected", - [ - ([], [[]]), - ([1, 2, 3], [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]), - ([0, 1], [[0, 1], [1, 0]]), - ([1], [[1]]), - ([-1, 0, 1], [[-1, 0, 1], [-1, 1, 0], [0, -1, 1], [0, 1, -1], [1, -1, 0], [1, 0, -1]]), - ([4, 5], [[4, 5], [5, 4]]), - ([1, 2, 3, 4], 24), # Test count only for larger input - ([10], [[10]]), - ([-5, -3], [[-5, -3], [-3, -5]]), - ], - ) - @logged_test - def test_permute(self, nums: list[int], expected): - result = self.solution.permute(nums) - - # If expected is int, just check count (for larger inputs) - if isinstance(expected, int): - assert len(result) == expected - # Verify all permutations are unique - assert len(set(tuple(perm) for perm in result)) == expected - return - - # Sort both result and expected for comparison since order doesn't matter - result_sorted = [sorted(perm) for perm in result] - expected_sorted = [sorted(perm) for perm in expected] - result_sorted.sort() - expected_sorted.sort() - assert len(result) == len(expected) - assert result_sorted == expected_sorted diff --git a/leetcode_old/product_of_array_except_self/README.md b/leetcode_old/product_of_array_except_self/README.md deleted file mode 100644 index 3f75f03..0000000 --- a/leetcode_old/product_of_array_except_self/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Product of Array Except Self - -**Difficulty:** Medium -**Topics:** Array, Prefix Sum -**Tags:** grind-75 - -**LeetCode:** [Problem 238](https://leetcode.com/problems/product-of-array-except-self/description/) - -## Problem Description - -Given an integer array `nums`, return an array `answer` such that `answer[i]` is equal to the product of all the elements of `nums` except `nums[i]`. - -The product of any prefix or suffix of `nums` is guaranteed to fit in a 32-bit integer. - -You must write an algorithm that runs in O(n) time and without using the division operation. - -## Examples - -### Example 1: - -``` -Input: nums = [1,2,3,4] -Output: [24,12,8,6] -``` - -### Example 2: - -``` -Input: nums = [-1,1,0,-3,3] -Output: [0,0,9,0,0] -``` - -## Constraints - -- 2 <= nums.length <= 10^5 -- -30 <= nums[i] <= 30 -- The input is generated such that answer[i] is guaranteed to fit in a 32-bit integer. - -**Follow up:** Can you solve the problem in O(1) extra space complexity? (The output array does not count as extra space for space complexity analysis.) diff --git a/leetcode_old/product_of_array_except_self/__init__.py b/leetcode_old/product_of_array_except_self/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/product_of_array_except_self/playground.ipynb b/leetcode_old/product_of_array_except_self/playground.ipynb deleted file mode 100644 index ebfb399..0000000 --- a/leetcode_old/product_of_array_except_self/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "nums = [1, 2, 3, 4]\n", - "expected = [24, 12, 8, 6]" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[24, 12, 8, 6]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().product_except_self(nums)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/product_of_array_except_self/solution.py b/leetcode_old/product_of_array_except_self/solution.py deleted file mode 100644 index 821e548..0000000 --- a/leetcode_old/product_of_array_except_self/solution.py +++ /dev/null @@ -1,25 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(1) - def product_except_self(self, nums: list[int]) -> list[int]: - # Example: nums = [1, 2, 3, 4] - # Expected output: [24, 12, 8, 6] - - n = len(nums) - result = [1] * n # [1, 1, 1, 1] - - # Left pass: result[i] = product of all elements to the left of i - # nums: [1, 2, 3, 4] - # result: [1, 1, 2, 6] (left products) - for i in range(1, n): - result[i] = result[i - 1] * nums[i - 1] - - # Right pass: multiply by product of all elements to the right of i - # right products: [24, 12, 4, 1] - # result: [1*24, 1*12, 2*4, 6*1] = [24, 12, 8, 6] - right = 1 - for i in range(n - 1, -1, -1): - result[i] *= right - right *= nums[i] - - return result diff --git a/leetcode_old/product_of_array_except_self/tests.py b/leetcode_old/product_of_array_except_self/tests.py deleted file mode 100644 index e3532e4..0000000 --- a/leetcode_old/product_of_array_except_self/tests.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestProductOfArrayExceptSelf: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "nums, expected", - [ - ([1, 2, 3, 4], [24, 12, 8, 6]), - ([-1, 1, 0, -3, 3], [0, 0, 9, 0, 0]), - ([2, 3, 4, 5], [60, 40, 30, 24]), - # Edge cases - ([1, 1], [1, 1]), # Minimum length - ([5, 2], [2, 5]), # Two elements - ([0, 0], [0, 0]), # All zeros - ([1, 0], [0, 1]), # Single zero - ([0, 1, 2], [2, 0, 0]), # Zero at start - ([1, 2, 0], [0, 0, 2]), # Zero at end - # Negative numbers - ([-1, -2], [-2, -1]), - ([-1, -2, -3], [6, 3, 2]), - ([1, -2, 3], [-6, 3, -2]), - # All ones - ([1, 1, 1, 1], [1, 1, 1, 1]), - # Large numbers - ([10, 3, 5, 6, 2], [180, 600, 360, 300, 900]), - ], - ) - @logged_test - def test_product_except_self(self, nums: list[int], expected: list[int]): - result = self.solution.product_except_self(nums) - assert result == expected diff --git a/leetcode_old/ransom_note/README.md b/leetcode_old/ransom_note/README.md deleted file mode 100644 index 59c1c12..0000000 --- a/leetcode_old/ransom_note/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Ransom Note - -**Difficulty:** Easy -**Topics:** Hash Table, String, Counting -**Tags:** grind-75 - -**LeetCode:** [Problem 383](https://leetcode.com/problems/ransom-note/description/) - -## Problem Description - -Given two strings `ransomNote` and `magazine`, return `true` if `ransomNote` can be constructed by using the letters from `magazine` and `false` otherwise. - -Each letter in `magazine` can only be used once in `ransomNote`. - -## Examples - -### Example 1: - -``` -Input: ransomNote = "a", magazine = "b" -Output: false -``` - -### Example 2: - -``` -Input: ransomNote = "aa", magazine = "ab" -Output: false -``` - -### Example 3: - -``` -Input: ransomNote = "aa", magazine = "aab" -Output: true -``` - -## Constraints - -- 1 <= ransomNote.length, magazine.length <= 10^5 -- ransomNote and magazine consist of lowercase English letters. diff --git a/leetcode_old/ransom_note/__init__.py b/leetcode_old/ransom_note/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/ransom_note/playground.ipynb b/leetcode_old/ransom_note/playground.ipynb deleted file mode 100644 index 864b296..0000000 --- a/leetcode_old/ransom_note/playground.ipynb +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "ransom_note = \"aa\"\n", - "magazine = \"aab\"\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().can_construct(ransom_note, magazine)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/ransom_note/solution.py b/leetcode_old/ransom_note/solution.py deleted file mode 100644 index f99deef..0000000 --- a/leetcode_old/ransom_note/solution.py +++ /dev/null @@ -1,18 +0,0 @@ -from collections import Counter - - -class Solution: - # Time: O(m + n) where m = magazine length, n = ransom_note length - # Space: O(1) - at most 26 lowercase letters - def can_construct(self, ransom_note: str, magazine: str) -> bool: - if len(ransom_note) > len(magazine): - return False - - magazine_count = Counter(magazine) - - for char in ransom_note: - if magazine_count[char] == 0: - return False - magazine_count[char] -= 1 - - return True diff --git a/leetcode_old/ransom_note/tests.py b/leetcode_old/ransom_note/tests.py deleted file mode 100644 index 90881af..0000000 --- a/leetcode_old/ransom_note/tests.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestRansomNote: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "ransom_note, magazine, expected", - [ - # Original test cases - ("a", "b", False), - ("aa", "ab", False), - ("aa", "aab", True), - ("aab", "baa", True), - # Edge cases - ("", "", True), # Both empty - ("", "abc", True), # Empty ransom note - ("a", "", False), # Empty magazine - ("a", "a", True), # Single char match - ("ab", "a", False), # Ransom longer than magazine - # More complex cases - ("abc", "aabbcc", True), # Multiple of each char - ("aab", "baa", True), # Same chars, different order - ("aaa", "aa", False), # Not enough of same char - ("abcd", "dcba", True), # All different chars - ("hello", "helloworld", True), # Ransom is substring - ("world", "hello", False), # Missing chars - ], - ) - @logged_test - def test_can_construct(self, ransom_note: str, magazine: str, expected: bool): - result = self.solution.can_construct(ransom_note, magazine) - assert result == expected diff --git a/leetcode_old/reverse_linked_list/README.md b/leetcode_old/reverse_linked_list/README.md deleted file mode 100644 index ae96ddd..0000000 --- a/leetcode_old/reverse_linked_list/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Reverse Linked List - -**Difficulty:** Easy -**Topics:** Linked List, Recursion -**Tags:** grind-75 - -**LeetCode:** [Problem 206](https://leetcode.com/problems/reverse-linked-list/description/) - -## Problem Description - -Given the `head` of a singly linked list, reverse the list, and return the reversed list. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2021/02/19/rev1ex1.jpg) - -``` -Input: head = [1,2,3,4,5] -Output: [5,4,3,2,1] -``` - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2021/02/19/rev1ex2.jpg) - -``` -Input: head = [1,2] -Output: [2,1] -``` - -### Example 3: - -``` -Input: head = [] -Output: [] -``` - -## Constraints - -- The number of nodes in the list is the range `[0, 5000]`. -- `-5000 <= Node.val <= 5000` - -**Follow up:** A linked list can be reversed either iteratively or recursively. Could you implement both? diff --git a/leetcode_old/reverse_linked_list/__init__.py b/leetcode_old/reverse_linked_list/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/reverse_linked_list/playground.ipynb b/leetcode_old/reverse_linked_list/playground.ipynb deleted file mode 100644 index 140f09b..0000000 --- a/leetcode_old/reverse_linked_list/playground.ipynb +++ /dev/null @@ -1,174 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import ListNode" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Original: 1 -> 2 -> 3 -> 4 -> 5\n", - "Expected: 5 -> 4 -> 3 -> 2 -> 1\n" - ] - } - ], - "source": [ - "# Example test case: reverse [1,2,3,4,5] to [5,4,3,2,1]\n", - "head_list = [1, 2, 3, 4, 5]\n", - "expected_list = [5, 4, 3, 2, 1]\n", - "\n", - "# Convert lists to linked lists\n", - "head = ListNode.from_list(head_list)\n", - "expected = ListNode.from_list(expected_list)\n", - "print(f\"Original: {head}\")\n", - "print(f\"Expected: {expected}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Result: 5 -> 4 -> 3 -> 2 -> 1\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_0\n", - "\n", - "5\n", - "\n", - "\n", - "\n", - "node_1\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "node_0->node_1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_2\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "node_1->node_2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_3\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "node_2->node_3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "node_4\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "node_3->node_4\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "ListNode([5, 4, 3, 2, 1])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Reverse the linked list: 1->2->3->4->5 becomes 5->4->3->2->1\n", - "result = Solution().reverse_list(head)\n", - "print(f\"Result: {result}\")\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "# Verify the result matches expected output\n", - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/reverse_linked_list/solution.py b/leetcode_old/reverse_linked_list/solution.py deleted file mode 100644 index 58a0cbe..0000000 --- a/leetcode_old/reverse_linked_list/solution.py +++ /dev/null @@ -1,46 +0,0 @@ -from leetcode_py import ListNode - - -class Solution: - # Time: O(n) - # Space: O(1) - def reverse_list(self, head: ListNode[int] | None) -> ListNode[int] | None: - if not head: - return None - - # Iterative approach using three pointers - # Example: [1,2,3] -> [3,2,1] - # - # Initial: prev curr - # None ↓ - # 1 -> 2 -> 3 -> None - # - prev: ListNode[int] | None = None - curr: ListNode[int] | None = head - - while curr: - # Store next node before breaking the link - next_node = curr.next - # - # prev curr next_node - # None ↓ ↓ - # 1 -> 2 -> 3 -> None - # - - # Reverse the current link - curr.next = prev - # None <- 1 2 -> 3 -> None - # prev curr next_node - # - - # Move pointers forward - prev = curr - curr = next_node - # 1 <- 2 3 -> None - # prev curr - # - - # 1 <- 2 <- 3 None - # prev curr - # prev now points to new head of reversed list - return prev diff --git a/leetcode_old/reverse_linked_list/tests.py b/leetcode_old/reverse_linked_list/tests.py deleted file mode 100644 index c8f51a9..0000000 --- a/leetcode_old/reverse_linked_list/tests.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest - -from leetcode_py import ListNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestReverseLinkedList: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "head_list, expected_list", - [ - ([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]), # Basic case - ([1, 2], [2, 1]), # Two nodes - ([1], [1]), # Single node - ([], []), # Empty list - ([1, 2, 3], [3, 2, 1]), # Odd length - ([1, 2, 3, 4], [4, 3, 2, 1]), # Even length - ([-1, -2, -3], [-3, -2, -1]), # Negative values - ([0], [0]), # Zero value - ([5000, -5000], [-5000, 5000]), # Boundary values - ([1, 1, 1], [1, 1, 1]), # Duplicate values - ], - ) - @logged_test - def test_reverse_list(self, head_list: list[int], expected_list: list[int]): - # Convert input list to linked list structure - # e.g., [1,2,3] -> 1->2->3->None - head = ListNode.from_list(head_list) - - # Convert expected result list to linked list for comparison - # e.g., [3,2,1] -> 3->2->1->None - expected = ListNode.from_list(expected_list) - - # Call the reverse_list method to reverse the linked list - # This should transform 1->2->3->None into 3->2->1->None - result = self.solution.reverse_list(head) - - # Verify that the reversed linked list matches expected output - # ListNode supports direct equality comparison - assert result == expected diff --git a/leetcode_old/reverse_linked_list_ii/README.md b/leetcode_old/reverse_linked_list_ii/README.md deleted file mode 100644 index 1f7a098..0000000 --- a/leetcode_old/reverse_linked_list_ii/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Reverse Linked List II - -**Difficulty:** Medium -**Topics:** Linked List -**Tags:** - -**LeetCode:** [Problem 92](https://leetcode.com/problems/reverse-linked-list-ii/description/) - -## Problem Description - -Given the `head` of a singly linked list and two integers `left` and `right` where `left <= right`, reverse the nodes of the list from position `left` to position `right`, and return the reversed list. - -## Examples - -### Example 1: - -``` -Input: head = [1,2,3,4,5], left = 2, right = 4 -Output: [1,4,3,2,5] -``` - -### Example 2: - -``` -Input: head = [5], left = 1, right = 1 -Output: [5] -``` - -## Constraints - -- The number of nodes in the list is n -- 1 <= n <= 500 -- -500 <= Node.val <= 500 -- 1 <= left <= right <= n - -**Follow up:** Could you do it in one pass? diff --git a/leetcode_old/reverse_linked_list_ii/__init__.py b/leetcode_old/reverse_linked_list_ii/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/reverse_linked_list_ii/playground.ipynb b/leetcode_old/reverse_linked_list_ii/playground.ipynb deleted file mode 100644 index bfdc6d9..0000000 --- a/leetcode_old/reverse_linked_list_ii/playground.ipynb +++ /dev/null @@ -1,72 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import ListNode" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "head_list = [1, 2, 3, 4, 5]\n", - "head = ListNode[int].from_list(head_list)\n", - "left, right = 2, 4\n", - "expected = ListNode[int].from_list([1, 4, 3, 2, 5])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().reverse_between(head, left, right)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/reverse_linked_list_ii/solution.py b/leetcode_old/reverse_linked_list_ii/solution.py deleted file mode 100644 index db9a3a0..0000000 --- a/leetcode_old/reverse_linked_list_ii/solution.py +++ /dev/null @@ -1,52 +0,0 @@ -from leetcode_py import ListNode - - -class Solution: - # Time: O(n) - # Space: O(1) - def reverse_between(self, head: ListNode[int] | None, left: int, right: int) -> ListNode[int] | None: - if not head or left == right: - return head - - dummy = ListNode[int](0) - dummy.next = head - prev = dummy - - # Move to position before left - for _ in range(left - 1): - assert prev.next - prev = prev.next - - # Reverse from left to right using iterative approach - # Example: [1,2,3,4,5] left=2, right=4 -> [1,4,3,2,5] - # - # Initial: prev curr - # ↓ ↓ - # 1 -> 2 -> 3 -> 4 -> 5 - # - assert prev.next - curr = prev.next # First node to be reversed (will become last after reversal) - - # Reverse by moving nodes one by one to the front of the section - for _ in range(right - left): - assert curr.next - next_node = curr.next # Node to move to front - # - # prev curr next_node - # ↓ ↓ ↓ - # 1 -> 2 -> 3 -> 4 -> 5 - # - curr.next = next_node.next - # 1 -> 2 -----> 4 -> 5 - # 3 ↗ - # - next_node.next = prev.next - # 1 -> 2 -----> 4 -> 5 - # 3 ↗ - # - prev.next = next_node - # 1 -> 3 -> 2 -> 4 -> 5 - # prev ↑ curr - # next_node - - return dummy.next diff --git a/leetcode_old/reverse_linked_list_ii/tests.py b/leetcode_old/reverse_linked_list_ii/tests.py deleted file mode 100644 index e3ea5b7..0000000 --- a/leetcode_old/reverse_linked_list_ii/tests.py +++ /dev/null @@ -1,24 +0,0 @@ -import pytest - -from leetcode_py import ListNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestReverseLinkedListII: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "head_list, left, right, expected_list", - [([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]), ([5], 1, 1, [5])], - ) - @logged_test - def test_reverse_between( - self, head_list: list[int], left: int, right: int, expected_list: list[int] - ): - head = ListNode[int].from_list(head_list) - expected = ListNode[int].from_list(expected_list) - result = self.solution.reverse_between(head, left, right) - assert result == expected diff --git a/leetcode_old/rotting_oranges/README.md b/leetcode_old/rotting_oranges/README.md deleted file mode 100644 index 36d2c82..0000000 --- a/leetcode_old/rotting_oranges/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Rotting Oranges - -**Difficulty:** Medium -**Topics:** Array, Breadth-First Search, Matrix -**Tags:** grind-75 - -**LeetCode:** [Problem 994](https://leetcode.com/problems/rotting-oranges/description/) - -## Problem Description - -You are given an `m x n` `grid` where each cell can have one of three values: - -- `0` representing an empty cell, -- `1` representing a fresh orange, or -- `2` representing a rotten orange. - -Every minute, any fresh orange that is **4-directionally adjacent** to a rotten orange becomes rotten. - -Return _the minimum number of minutes that must elapse until no cell has a fresh orange_. If _this is impossible, return_ `-1`. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2019/02/16/oranges.png) - -``` -Input: grid = [[2,1,1],[1,1,0],[0,1,1]] -Output: 4 -``` - -### Example 2: - -``` -Input: grid = [[2,1,1],[0,1,1],[1,0,1]] -Output: -1 -``` - -**Explanation:** The orange in the bottom left corner (row 2, column 0) is never rotten, because rotting only happens 4-directionally. - -### Example 3: - -``` -Input: grid = [[0,2]] -Output: 0 -``` - -**Explanation:** Since there are already no fresh oranges at minute 0, the answer is just 0. - -## Constraints - -- `m == grid.length` -- `n == grid[i].length` -- `1 <= m, n <= 10` -- `grid[i][j]` is `0`, `1`, or `2`. diff --git a/leetcode_old/rotting_oranges/__init__.py b/leetcode_old/rotting_oranges/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/rotting_oranges/playground.ipynb b/leetcode_old/rotting_oranges/playground.ipynb deleted file mode 100644 index 7d3f7bf..0000000 --- a/leetcode_old/rotting_oranges/playground.ipynb +++ /dev/null @@ -1,68 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "grid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]\n", - "expected = 4" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().oranges_rotting(grid)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/rotting_oranges/solution.py b/leetcode_old/rotting_oranges/solution.py deleted file mode 100644 index 747d2c9..0000000 --- a/leetcode_old/rotting_oranges/solution.py +++ /dev/null @@ -1,43 +0,0 @@ -from collections import deque - - -class Solution: - # Time: O(m*n) - # Space: O(m*n) - def oranges_rotting(self, grid: list[list[int]]) -> int: - EMPTY, FRESH, ROTTEN = 0, 1, 2 - _ = EMPTY - - m, n = len(grid), len(grid[0]) - queue: deque[tuple[int, int]] = deque() - fresh = 0 - - # Find all rotten oranges and count fresh ones - for i in range(m): - for j in range(n): - if grid[i][j] == ROTTEN: - queue.append((i, j)) - elif grid[i][j] == FRESH: - fresh += 1 - - if fresh == 0: - return 0 - - minutes = 0 - directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] - - while queue: - size = len(queue) - for _ in range(size): - x, y = queue.popleft() - for dx, dy in directions: - nx, ny = x + dx, y + dy - if 0 <= nx < m and 0 <= ny < n and grid[nx][ny] == FRESH: - grid[nx][ny] = ROTTEN - fresh -= 1 - queue.append((nx, ny)) - - if queue: - minutes += 1 - - return minutes if fresh == 0 else -1 diff --git a/leetcode_old/rotting_oranges/tests.py b/leetcode_old/rotting_oranges/tests.py deleted file mode 100644 index cbf8011..0000000 --- a/leetcode_old/rotting_oranges/tests.py +++ /dev/null @@ -1,60 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestTestRottingOranges: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "grid, expected", - [ - # Original examples - ([[2, 1, 1], [1, 1, 0], [0, 1, 1]], 4), - ([[2, 1, 1], [0, 1, 1], [1, 0, 1]], -1), - ([[0, 2]], 0), - # Single cell cases - ([[0]], 0), - ([[1]], -1), - ([[2]], 0), - # Two cell cases - ([[1, 2]], 1), - ([[2, 1]], 1), - ([[0, 1, 2]], 1), - # Multiple rotten sources - ([[2, 2], [1, 1], [0, 0]], 1), - ([[2, 1, 1], [1, 1, 1], [0, 1, 2]], 2), - # All empty grid - ([[0, 0, 0], [0, 0, 0]], 0), - # All rotten grid - ([[2, 2, 2], [2, 2, 2]], 0), - # All fresh grid (impossible) - ([[1, 1, 1], [1, 1, 1]], -1), - # Large grid with barriers - ([[2, 1, 0, 0, 1], [1, 0, 0, 0, 1], [0, 0, 0, 0, 1], [0, 0, 0, 0, 2]], 3), - # Cross pattern - ([[0, 1, 0], [1, 2, 1], [0, 1, 0]], 1), - # Diagonal (no spread) - ([[2, 0, 1], [0, 0, 0], [1, 0, 1]], -1), - # Ring pattern - ([[1, 1, 1], [1, 0, 1], [1, 1, 1]], -1), - # Multiple disconnected fresh groups - ([[2, 1, 0, 1], [0, 0, 0, 0], [1, 0, 0, 2]], -1), - # Linear spread - ([[2, 1, 1, 1, 1]], 4), - # Vertical spread - ([[2], [1], [1], [1]], 3), - # Corner cases - ([[2, 0, 0], [0, 0, 0], [0, 0, 1]], -1), - ([[1, 0, 0], [0, 0, 0], [0, 0, 2]], -1), - # Complex maze-like - ([[2, 1, 1, 0, 1], [1, 0, 1, 0, 1], [1, 1, 1, 0, 2]], 4), - ], - ) - @logged_test - def test_oranges_rotting(self, grid: list[list[int]], expected: int): - result = self.solution.oranges_rotting(grid) - assert result == expected diff --git a/leetcode_old/search_in_rotated_sorted_array/README.md b/leetcode_old/search_in_rotated_sorted_array/README.md deleted file mode 100644 index f74397c..0000000 --- a/leetcode_old/search_in_rotated_sorted_array/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Search in Rotated Sorted Array - -**Difficulty:** Medium -**Topics:** Array, Binary Search -**Tags:** grind-75 - -**LeetCode:** [Problem 33](https://leetcode.com/problems/search-in-rotated-sorted-array/description/) - -## Problem Description - -There is an integer array `nums` sorted in ascending order (with **distinct** values). - -Prior to being passed to your function, `nums` is **possibly left rotated** at an unknown index `k` (`1 <= k < nums.length`) such that the resulting array is `[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]` (**0-indexed**). For example, `[0,1,2,4,5,6,7]` might be left rotated by 3 indices and become `[4,5,6,7,0,1,2]`. - -Given the array `nums` **after** the possible rotation and an integer `target`, return _the index of_ `target` _if it is in_ `nums`_, or_ `-1` _if it is not in_ `nums`. - -You must write an algorithm with `O(log n)` runtime complexity. - -## Examples - -### Example 1: - -``` -Input: nums = [4,5,6,7,0,1,2], target = 0 -Output: 4 -``` - -### Example 2: - -``` -Input: nums = [4,5,6,7,0,1,2], target = 3 -Output: -1 -``` - -### Example 3: - -``` -Input: nums = [1], target = 0 -Output: -1 -``` - -## Constraints - -- `1 <= nums.length <= 5000` -- `-10^4 <= nums[i] <= 10^4` -- All values of `nums` are **unique**. -- `nums` is an ascending array that is possibly rotated. -- `-10^4 <= target <= 10^4` diff --git a/leetcode_old/search_in_rotated_sorted_array/__init__.py b/leetcode_old/search_in_rotated_sorted_array/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/search_in_rotated_sorted_array/playground.ipynb b/leetcode_old/search_in_rotated_sorted_array/playground.ipynb deleted file mode 100644 index 357fb33..0000000 --- a/leetcode_old/search_in_rotated_sorted_array/playground.ipynb +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "nums = [4, 5, 6, 7, 0, 1, 2]\n", - "target = 0\n", - "expected = 4" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "4" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().search(nums, target)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/search_in_rotated_sorted_array/solution.py b/leetcode_old/search_in_rotated_sorted_array/solution.py deleted file mode 100644 index d8af000..0000000 --- a/leetcode_old/search_in_rotated_sorted_array/solution.py +++ /dev/null @@ -1,26 +0,0 @@ -class Solution: - # Time: O(log n) - # Space: O(1) - def search(self, nums: list[int], target: int) -> int: - left, right = 0, len(nums) - 1 - - while left <= right: - mid = (left + right) // 2 - - if nums[mid] == target: - return mid - - # Left half is sorted - if nums[left] <= nums[mid]: - if nums[left] <= target < nums[mid]: - right = mid - 1 - else: - left = mid + 1 - # Right half is sorted - else: - if nums[mid] < target <= nums[right]: - left = mid + 1 - else: - right = mid - 1 - - return -1 diff --git a/leetcode_old/search_in_rotated_sorted_array/tests.py b/leetcode_old/search_in_rotated_sorted_array/tests.py deleted file mode 100644 index 90cc27f..0000000 --- a/leetcode_old/search_in_rotated_sorted_array/tests.py +++ /dev/null @@ -1,41 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestSearchInRotatedSortedArray: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "nums, target, expected", - [ - # Original test cases - ([4, 5, 6, 7, 0, 1, 2], 0, 4), - ([4, 5, 6, 7, 0, 1, 2], 3, -1), - ([1], 0, -1), - ([1], 1, 0), - ([3, 1], 1, 1), - # No rotation (sorted array) - ([1, 2, 3, 4, 5], 3, 2), - ([1, 2, 3, 4, 5], 6, -1), - # Different rotation points - ([6, 7, 0, 1, 2, 4, 5], 0, 2), - ([6, 7, 0, 1, 2, 4, 5], 4, 5), - ([6, 7, 0, 1, 2, 4, 5], 7, 1), - ([2, 3, 4, 5, 6, 7, 0, 1], 0, 6), - # Target at boundaries - ([4, 5, 6, 7, 0, 1, 2], 4, 0), - ([4, 5, 6, 7, 0, 1, 2], 2, 6), - # Two elements - ([2, 1], 1, 1), - ([2, 1], 2, 0), - ([1, 3], 3, 1), - ], - ) - @logged_test - def test_search(self, nums: list[int], target: int, expected: int): - result = self.solution.search(nums, target) - assert result == expected diff --git a/leetcode_old/serialize_and_deserialize_binary_tree/README.md b/leetcode_old/serialize_and_deserialize_binary_tree/README.md deleted file mode 100644 index 8226e6c..0000000 --- a/leetcode_old/serialize_and_deserialize_binary_tree/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Serialize and Deserialize Binary Tree - -**Difficulty:** Hard -**Topics:** String, Tree, Depth-First Search, Breadth-First Search, Design, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 297](https://leetcode.com/problems/serialize-and-deserialize-binary-tree/description/) - -## Problem Description - -Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment. - -Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure. - -**Clarification:** The input/output format is the same as how LeetCode serializes a binary tree. You do not necessarily need to follow this format, so please be creative and come up with different approaches yourself. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2020/09/15/serdeser.jpg) - -``` -Input: root = [1,2,3,null,null,4,5] -Output: [1,2,3,null,null,4,5] -``` - -### Example 2: - -``` -Input: root = [] -Output: [] -``` - -## Constraints - -- The number of nodes in the tree is in the range [0, 10^4]. -- -1000 <= Node.val <= 1000 diff --git a/leetcode_old/serialize_and_deserialize_binary_tree/__init__.py b/leetcode_old/serialize_and_deserialize_binary_tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/serialize_and_deserialize_binary_tree/playground.ipynb b/leetcode_old/serialize_and_deserialize_binary_tree/playground.ipynb deleted file mode 100644 index 36ca758..0000000 --- a/leetcode_old/serialize_and_deserialize_binary_tree/playground.ipynb +++ /dev/null @@ -1,167 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Codec\n", - "\n", - "from leetcode_py import TreeNode" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "root_list = [1, 2, 3, None, None, 4, 5]\n", - "root = TreeNode.from_list(root_list) if root_list else None" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Original: [1, 2, 3, None, None, 4, 5]\n", - "Serialized: 1,2,#,#,3,4,#,#,5,#,#\n", - "Deserialized: [1, 2, 3, None, None, 4, 5]\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "0\n", - "\n", - "1\n", - "\n", - "\n", - "\n", - "1\n", - "\n", - "2\n", - "\n", - "\n", - "\n", - "0->1\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "2\n", - "\n", - "3\n", - "\n", - "\n", - "\n", - "0->2\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "3\n", - "\n", - "4\n", - "\n", - "\n", - "\n", - "2->3\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "4\n", - "\n", - "5\n", - "\n", - "\n", - "\n", - "2->4\n", - "\n", - "\n", - "\n", - "\n", - "\n" - ], - "text/plain": [ - "TreeNode([1, 2, 3, None, None, 4, 5])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "codec = Codec()\n", - "serialized = codec.serialize(root)\n", - "deserialized = codec.deserialize(serialized)\n", - "print(f\"Original: {root.to_list() if root else None}\")\n", - "print(f\"Serialized: {serialized}\")\n", - "print(f\"Deserialized: {deserialized.to_list() if deserialized else None}\")\n", - "deserialized" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "if root is None:\n", - " assert deserialized is None\n", - "else:\n", - " assert deserialized is not None\n", - " assert deserialized.to_list() == root.to_list()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/serialize_and_deserialize_binary_tree/solution.py b/leetcode_old/serialize_and_deserialize_binary_tree/solution.py deleted file mode 100644 index aeb3177..0000000 --- a/leetcode_old/serialize_and_deserialize_binary_tree/solution.py +++ /dev/null @@ -1,81 +0,0 @@ -from leetcode_py import TreeNode - - -class Codec: - # Preorder with Null Markers - # Time: O(n) - # Space: O(n) - def serialize(self, root: TreeNode | None) -> str: - vals = [] - - def dfs(node: TreeNode | None): - if not node: - vals.append("#") - return - vals.append(str(node.val)) - dfs(node.left) - dfs(node.right) - - dfs(root) - return ",".join(vals) - - # Time: O(n) - # Space: O(n) - def deserialize(self, data: str) -> TreeNode | None: - vals = iter(data.split(",")) - - def dfs(): - val = next(vals) - if val == "#": - return None - node = TreeNode(int(val)) - node.left = dfs() - node.right = dfs() - return node - - return dfs() - - -# Binary Tree Serialization Techniques - -# Example Tree: -# 1 -# / \ -# 2 3 -# / \ -# 4 5 - -# 1. Preorder with Null Markers (This Implementation) -# Visit: root → left → right, mark nulls with '#' -# Result: "1,2,#,#,3,4,#,#,5,#,#" -# Pros: Self-contained, unambiguous, O(n) reconstruction -# Cons: Longer string due to null markers - -# 2. Level-order (BFS) with Null Markers -# Visit level by level, mark nulls with '#' -# Result: "1,2,3,#,#,4,5" -# Pros: Simple format like preorder, level-by-level intuitive -# Cons: Still requires queue processing - -# 3. Postorder with Null Markers -# Visit: left → right → root -# Result: "#,#,2,#,#,4,#,#,5,3,1" -# Pros: Bottom-up reconstruction -# Cons: Less intuitive than preorder - -# 4. Inorder + Preorder (Two Arrays) -# Inorder: [2,1,4,3,5], Preorder: [1,2,3,4,5] -# Pros: Works for any binary tree structure -# Cons: Requires two arrays, only works with unique values - -# 5. Parenthetical Preorder -# Same traversal as #1 but with parentheses format: value(left)(right) -# Result: "1(2()())(3(4()())(5()()))" -# Pros: Human readable structure, shows nesting clearly -# Cons: Complex parsing, verbose - -# 6. Parenthetical Postorder -# Same traversal as #3 but with parentheses format: (left)(right)value -# Result: "(()()2)((()()4)(()()5)3)1" -# Pros: Bottom-up readable structure -# Cons: Even more complex parsing diff --git a/leetcode_old/serialize_and_deserialize_binary_tree/tests.py b/leetcode_old/serialize_and_deserialize_binary_tree/tests.py deleted file mode 100644 index f1fb387..0000000 --- a/leetcode_old/serialize_and_deserialize_binary_tree/tests.py +++ /dev/null @@ -1,100 +0,0 @@ -import pytest - -from leetcode_py import TreeNode -from leetcode_py.test_utils import logged_test - -from .solution import Codec - - -class TestSerializeAndDeserializeBinaryTree: - def setup_method(self): - self.codec = Codec() - - @pytest.mark.parametrize( - "root_list", - [ - # Original test cases - ([1, 2, 3, None, None, 4, 5]), - ([]), - ([1]), - ([1, 2]), - ([1, None, 2]), - ([1, 2, 3, 4, 5, 6, 7]), - ([5, 2, 3, None, None, 2, 4, 3, 1]), - # Edge cases - ([0]), # Single node with value 0 - ([-1]), # Single node with negative value - ([1000]), # Single node with max value - ([-1000]), # Single node with min value - # Skewed trees - ([1, 2, None, 3, None, 4, None]), # Left skewed - ([1, None, 2, None, 3, None, 4]), # Right skewed - # Trees with negative values - ([-5, -3, -8, -2, -1, -7, -9]), - ([0, -1, 1, -2, None, None, 2]), - # Larger trees - ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]), - # Trees with mixed null patterns - ([1, None, 2, None, 3, None, 4, None, 5]), - ([1, 2, None, 3, None, 4, None, 5]), - ([5, 4, 7, 3, None, 2, None, -1, None, 9]), - # Duplicate values - ([1, 1, 1, 1, 1, 1, 1]), - ([2, 2, None, 2, None]), - # Complex asymmetric trees - ([10, 5, 15, None, 6, 12, 20, None, None, None, 13, 18, 25]), - ([50, 30, 70, 20, 40, 60, 80, 10, 25, 35, 45]), - ], - ) - @logged_test - def test_serialize_deserialize(self, root_list: list[int | None]): - root = TreeNode.from_list(root_list) if root_list else None - serialized = self.codec.serialize(root) - deserialized = self.codec.deserialize(serialized) - if root is None: - assert deserialized is None - else: - assert deserialized is not None - assert deserialized.to_list() == root.to_list() - - @logged_test - def test_multiple_serialize_deserialize_cycles(self): - """Test that multiple serialize/deserialize cycles preserve the tree""" - root_list = [1, 2, 3, None, None, 4, 5] - root = TreeNode.from_list(root_list) - - # Perform multiple cycles - current = root - for _ in range(3): - serialized = self.codec.serialize(current) - current = self.codec.deserialize(serialized) - - assert current is not None - assert current.to_list() == root_list - - @logged_test - def test_serialization_format(self): - """Test that serialization produces expected string format""" - # Simple tree: [1, 2, 3] - root = TreeNode.from_list([1, 2, 3]) - serialized = self.codec.serialize(root) - - # Should be preorder: root, left, right with # for null - assert serialized == "1,2,#,#,3,#,#" - - # Empty tree - serialized_empty = self.codec.serialize(None) - assert serialized_empty == "#" - - @logged_test - def test_deserialization_edge_cases(self): - """Test deserialization with various string inputs""" - # Single null - assert self.codec.deserialize("#") is None - - # Single node - single = self.codec.deserialize("42,#,#") - assert single is not None - assert single.val == 42 - assert single.left is None - assert single.right is None diff --git a/leetcode_old/sort_colors/README.md b/leetcode_old/sort_colors/README.md deleted file mode 100644 index ada8cf0..0000000 --- a/leetcode_old/sort_colors/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Sort Colors - -**Difficulty:** Medium -**Topics:** Array, Two Pointers, Sorting -**Tags:** grind-75 - -**LeetCode:** [Problem 75](https://leetcode.com/problems/sort-colors/description/) - -## Problem Description - -Given an array `nums` with `n` objects colored red, white, or blue, sort them **in-place** so that objects of the same color are adjacent, with the colors in the order red, white, and blue. - -We will use the integers `0`, `1`, and `2` to represent the color red, white, and blue, respectively. - -You must solve this problem without using the library's sort function. - -## Examples - -### Example 1: - -``` -Input: nums = [2,0,2,1,1,0] -Output: [0,0,1,1,2,2] -``` - -### Example 2: - -``` -Input: nums = [2,0,1] -Output: [0,1,2] -``` - -## Constraints - -- `n == nums.length` -- `1 <= n <= 300` -- `nums[i]` is either `0`, `1`, or `2`. - -**Follow up:** Could you come up with a one-pass algorithm using only constant extra space? diff --git a/leetcode_old/sort_colors/__init__.py b/leetcode_old/sort_colors/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/sort_colors/playground.ipynb b/leetcode_old/sort_colors/playground.ipynb deleted file mode 100644 index 33d81fa..0000000 --- a/leetcode_old/sort_colors/playground.ipynb +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "nums = [2, 0, 2, 1, 1, 0]\n", - "expected = [0, 0, 1, 1, 2, 2]" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[0, 0, 1, 1, 2, 2]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nums_copy = nums.copy()\n", - "Solution().sort_colors(nums_copy)\n", - "nums_copy" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert nums_copy == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/sort_colors/solution.py b/leetcode_old/sort_colors/solution.py deleted file mode 100644 index 003cf4a..0000000 --- a/leetcode_old/sort_colors/solution.py +++ /dev/null @@ -1,19 +0,0 @@ -class Solution: - # Dutch National Flag Algorithm - partitions array into 3 regions using 2 pointers - # Creates: [0s][1s][2s] with left/right boundaries, mid processes unvisited elements - # Time: O(n) - # Space: O(1) - def sort_colors(self, nums: list[int]) -> None: - left = mid = 0 - right = len(nums) - 1 - - while mid <= right: - if nums[mid] == 0: - nums[left], nums[mid] = nums[mid], nums[left] - left += 1 - mid += 1 - elif nums[mid] == 1: - mid += 1 - else: - nums[mid], nums[right] = nums[right], nums[mid] - right -= 1 diff --git a/leetcode_old/sort_colors/tests.py b/leetcode_old/sort_colors/tests.py deleted file mode 100644 index a500d9d..0000000 --- a/leetcode_old/sort_colors/tests.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestSortColors: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "nums, expected", - [ - ([2, 0, 2, 1, 1, 0], [0, 0, 1, 1, 2, 2]), - ([2, 0, 1], [0, 1, 2]), - ([0], [0]), - ([1], [1]), - ([2], [2]), - ([0, 1, 2], [0, 1, 2]), - ], - ) - @logged_test - def test_sort_colors(self, nums: list[int], expected: list[int]): - nums_copy = nums.copy() - self.solution.sort_colors(nums_copy) - assert nums_copy == expected diff --git a/leetcode_old/spiral_matrix/README.md b/leetcode_old/spiral_matrix/README.md deleted file mode 100644 index e2aab0f..0000000 --- a/leetcode_old/spiral_matrix/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Spiral Matrix - -**Difficulty:** Medium -**Topics:** Array, Matrix, Simulation -**Tags:** grind-75 - -**LeetCode:** [Problem 54](https://leetcode.com/problems/spiral-matrix/description/) - -## Problem Description - -Given an `m x n` matrix, return all elements of the matrix in spiral order. - -## Examples - -### Example 1: - - - -``` -Input: matrix = [[1,2,3],[4,5,6],[7,8,9]] -Output: [1,2,3,6,9,8,7,4,5] -``` - -### Example 2: - - - -``` -Input: matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] -Output: [1,2,3,4,8,12,11,10,9,5,6,7] -``` - -## Constraints - -- m == matrix.length -- n == matrix[i].length -- 1 <= m, n <= 10 -- -100 <= matrix[i][j] <= 100 diff --git a/leetcode_old/spiral_matrix/__init__.py b/leetcode_old/spiral_matrix/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/spiral_matrix/playground.ipynb b/leetcode_old/spiral_matrix/playground.ipynb deleted file mode 100644 index c219f95..0000000 --- a/leetcode_old/spiral_matrix/playground.ipynb +++ /dev/null @@ -1,68 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]\n", - "expected = [1, 2, 3, 6, 9, 8, 7, 4, 5]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().spiral_order(matrix)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/spiral_matrix/solution.py b/leetcode_old/spiral_matrix/solution.py deleted file mode 100644 index 65e1113..0000000 --- a/leetcode_old/spiral_matrix/solution.py +++ /dev/null @@ -1,41 +0,0 @@ -class Solution: - # Time: O(m*n) - # Space: O(1) - def spiral_order(self, matrix: list[list[int]]) -> list[int]: - if not matrix or not matrix[0]: - return [] - - # Check if all rows have same length - cols = len(matrix[0]) - for row in matrix: - if len(row) != cols: - raise ValueError("Invalid matrix: all rows must have same length") - - result = [] - top, bottom = 0, len(matrix) - 1 - left, right = 0, cols - 1 - - while top <= bottom and left <= right: - # Right - for c in range(left, right + 1): - result.append(matrix[top][c]) - top += 1 - - # Down - for r in range(top, bottom + 1): - result.append(matrix[r][right]) - right -= 1 - - # Left (if still valid row) - if top <= bottom: - for c in range(right, left - 1, -1): - result.append(matrix[bottom][c]) - bottom -= 1 - - # Up (if still valid column) - if left <= right: - for r in range(bottom, top - 1, -1): - result.append(matrix[r][left]) - left += 1 - - return result diff --git a/leetcode_old/spiral_matrix/tests.py b/leetcode_old/spiral_matrix/tests.py deleted file mode 100644 index 8e3233c..0000000 --- a/leetcode_old/spiral_matrix/tests.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestSpiralMatrix: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "matrix, expected", - [ - # Original cases - ([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [1, 2, 3, 6, 9, 8, 7, 4, 5]), - ([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]], [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]), - # Single element - ([[1]], [1]), - # Single row - ([[1, 2, 3, 4]], [1, 2, 3, 4]), - # Single column - ([[1], [2], [3]], [1, 2, 3]), - # 2x2 matrix - ([[1, 2], [3, 4]], [1, 2, 4, 3]), - # 1x2 matrix - ([[1, 2]], [1, 2]), - # 2x1 matrix - ([[1], [2]], [1, 2]), - # Larger square matrix - ( - [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]], - [1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10], - ), - ], - ) - @logged_test - def test_spiral_order(self, matrix: list[list[int]], expected: list[int]): - result = self.solution.spiral_order(matrix) - assert result == expected diff --git a/leetcode_old/string_to_integer_atoi/README.md b/leetcode_old/string_to_integer_atoi/README.md deleted file mode 100644 index 513a46d..0000000 --- a/leetcode_old/string_to_integer_atoi/README.md +++ /dev/null @@ -1,109 +0,0 @@ -# String to Integer (atoi) - -**Difficulty:** Medium -**Topics:** String -**Tags:** grind-75 - -**LeetCode:** [Problem 8](https://leetcode.com/problems/string-to-integer-atoi/description/) - -## Problem Description - -Implement the `my_atoi(string s)` function, which converts a string to a 32-bit signed integer. - -The algorithm for `my_atoi(string s)` is as follows: - -1. **Whitespace**: Ignore any leading whitespace (` `). -2. **Signedness**: Determine the sign by checking if the next character is `-` or `+`, assuming positivity if neither present. -3. **Conversion**: Read the integer by skipping leading zeros until a non-digit character is encountered or the end of the string is reached. If no digits were read, then the result is 0. -4. **Rounding**: If the integer is out of the 32-bit signed integer range `[-2^31, 2^31 - 1]`, then round the integer to remain in the range. Specifically, integers less than `-2^31` should be rounded to `-2^31`, and integers greater than `2^31 - 1` should be rounded to `2^31 - 1`. - -Return the integer as the final result. - -## Examples - -### Example 1: - -``` -Input: s = "42" -Output: 42 -``` - -**Explanation:** - -``` -The underlined characters are what is read in and the caret is the current reader position. -Step 1: "42" (no characters read because there is no leading whitespace) - ^ -Step 2: "42" (no characters read because there is neither a '-' nor '+') - ^ -Step 3: "42" ("42" is read in) - ^ -``` - -### Example 2: - -``` -Input: s = " -042" -Output: -42 -``` - -**Explanation:** - -``` -Step 1: " -042" (leading whitespace is read and ignored) - ^ -Step 2: " -042" ('-' is read, so the result should be negative) - ^ -Step 3: " -042" ("042" is read in, leading zeros ignored in the result) - ^ -``` - -### Example 3: - -``` -Input: s = "1337c0d3" -Output: 1337 -``` - -**Explanation:** - -``` -Step 1: "1337c0d3" (no characters read because there is no leading whitespace) - ^ -Step 2: "1337c0d3" (no characters read because there is neither a '-' nor '+') - ^ -Step 3: "1337c0d3" ("1337" is read in; reading stops because the next character is a non-digit) - ^ -``` - -### Example 4: - -``` -Input: s = "0-1" -Output: 0 -``` - -**Explanation:** - -``` -Step 1: "0-1" (no characters read because there is no leading whitespace) - ^ -Step 2: "0-1" (no characters read because there is neither a '-' nor '+') - ^ -Step 3: "0-1" ("0" is read in; reading stops because the next character is a non-digit) - ^ -``` - -### Example 5: - -``` -Input: s = "words and 987" -Output: 0 -``` - -**Explanation:** Reading stops at the first non-digit character 'w'. - -## Constraints - -- `0 <= s.length <= 200` -- `s` consists of English letters (lower-case and upper-case), digits (0-9), ` `, `+`, `-`, and `.`. diff --git a/leetcode_old/string_to_integer_atoi/__init__.py b/leetcode_old/string_to_integer_atoi/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/string_to_integer_atoi/playground.ipynb b/leetcode_old/string_to_integer_atoi/playground.ipynb deleted file mode 100644 index 012d088..0000000 --- a/leetcode_old/string_to_integer_atoi/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "s = \"42\"\n", - "expected = 42" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "42" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().my_atoi(s)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/string_to_integer_atoi/solution.py b/leetcode_old/string_to_integer_atoi/solution.py deleted file mode 100644 index 42c5c62..0000000 --- a/leetcode_old/string_to_integer_atoi/solution.py +++ /dev/null @@ -1,30 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(1) - def my_atoi(self, s: str) -> int: - i = 0 - n = len(s) - - # Skip whitespace - while i < n and s[i] == " ": - i += 1 - - if i == n: - return 0 - - # Check sign - sign = 1 - if s[i] in {"+", "-"}: - sign = -1 if s[i] == "-" else 1 - i += 1 - - # Convert digits - result = 0 - while i < n and s[i].isdigit(): - result = result * 10 + int(s[i]) - i += 1 - - result *= sign - - # Clamp to 32-bit range - return max(-(2**31), min(2**31 - 1, result)) diff --git a/leetcode_old/string_to_integer_atoi/tests.py b/leetcode_old/string_to_integer_atoi/tests.py deleted file mode 100644 index b3c2a54..0000000 --- a/leetcode_old/string_to_integer_atoi/tests.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestStringToIntegerAtoi: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "s, expected", - [ - ("42", 42), - (" -042", -42), - ("1337c0d3", 1337), - ("0-1", 0), - ("words and 987", 0), - ("", 0), - (" ", 0), - ("+1", 1), - ("-1", -1), - ("2147483647", 2147483647), - ("-2147483648", -2147483648), - ("2147483648", 2147483647), - ("-2147483649", -2147483648), - # Edge cases - ("+-12", 0), - ("-+12", 0), - ("++1", 0), - ("--1", 0), - (" +0 123", 0), - ("0000000000012345678", 12345678), - ("-000000000000001", -1), - (" +000", 0), - ("123-", 123), - (" 13 5", 13), - (".1", 0), - ("1a33", 1), - (" -0012a42", -12), - ("21474836460", 2147483647), - ("-21474836480", -2147483648), - ], - ) - @logged_test - def test_my_atoi(self, s: str, expected: int): - result = self.solution.my_atoi(s) - assert result == expected diff --git a/leetcode_old/task_scheduler/README.md b/leetcode_old/task_scheduler/README.md deleted file mode 100644 index be7b1aa..0000000 --- a/leetcode_old/task_scheduler/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Task Scheduler - -**Difficulty:** Medium -**Topics:** Array, Hash Table, Greedy, Sorting, Heap (Priority Queue), Counting -**Tags:** grind-75 - -**LeetCode:** [Problem 621](https://leetcode.com/problems/task-scheduler/description/) - -## Problem Description - -You are given an array of CPU `tasks`, each labeled with a letter from A to Z, and a number `n`. Each CPU interval can be idle or allow the completion of one task. Tasks can be completed in any order, but there's a constraint: there has to be a gap of **at least** `n` intervals between two tasks with the same label. - -Return the **minimum** number of CPU intervals required to complete all tasks. - -## Examples - -### Example 1: - -``` -Input: tasks = ["A","A","A","B","B","B"], n = 2 -Output: 8 -``` - -**Explanation:** A possible sequence is: A -> B -> idle -> A -> B -> idle -> A -> B. - -After completing task A, you must wait two intervals before doing A again. The same applies to task B. In the 3rd interval, neither A nor B can be done, so you idle. By the 4th interval, you can do A again as 2 intervals have passed. - -### Example 2: - -``` -Input: tasks = ["A","C","A","B","D","B"], n = 1 -Output: 6 -``` - -**Explanation:** A possible sequence is: A -> B -> C -> D -> A -> B. - -With a cooling interval of 1, you can repeat a task after just one other task. - -### Example 3: - -``` -Input: tasks = ["A","A","A", "B","B","B"], n = 3 -Output: 10 -``` - -**Explanation:** A possible sequence is: A -> B -> idle -> idle -> A -> B -> idle -> idle -> A -> B. - -There are only two types of tasks, A and B, which need to be separated by 3 intervals. This leads to idling twice between repetitions of these tasks. - -## Constraints - -- `1 <= tasks.length <= 10^4` -- `tasks[i]` is an uppercase English letter. -- `0 <= n <= 100` diff --git a/leetcode_old/task_scheduler/__init__.py b/leetcode_old/task_scheduler/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/task_scheduler/playground.ipynb b/leetcode_old/task_scheduler/playground.ipynb deleted file mode 100644 index 7f76881..0000000 --- a/leetcode_old/task_scheduler/playground.ipynb +++ /dev/null @@ -1,88 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "tasks = [\"A\", \"A\", \"A\", \"B\", \"B\", \"B\"]\n", - "n = 2\n", - "expected = 8" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Counter({'A': 3, 'B': 3})\n", - "[-3, -3]\n" - ] - }, - { - "data": { - "text/plain": [ - "8" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().least_interval(tasks, n)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/task_scheduler/solution.py b/leetcode_old/task_scheduler/solution.py deleted file mode 100644 index d6a6f15..0000000 --- a/leetcode_old/task_scheduler/solution.py +++ /dev/null @@ -1,65 +0,0 @@ -import heapq -from collections import Counter, deque - - -class Solution: - # Time: O(T * n + m log m) where T = len(tasks), worst case with many idle periods - # Space: O(m) where m ≤ 26, so O(1) - def least_interval(self, tasks: list[str], n: int) -> int: - counts = Counter(tasks) - max_heap = [-count for count in counts.values()] - heapq.heapify(max_heap) - - step_num = 0 - queue: deque[tuple[int, int]] = deque() # (count, available_time) - - while max_heap or queue: - step_num += 1 - - while queue and queue[0][1] <= step_num: - count, _ = queue.popleft() - heapq.heappush(max_heap, count) - - if max_heap: - count = heapq.heappop(max_heap) - count += 1 # Decrease count (was negative) - if count < 0: # Still has tasks left - queue.append((count, step_num + n + 1)) - - return step_num - - -class SolutionGreedy: - # Time: O(T + m) where T = len(tasks), m = unique tasks ≤ 26, so O(T) - # Space: O(m) where m ≤ 26, so O(1) - def least_interval(self, tasks: list[str], n: int) -> int: - """ - Mathematical approach: - - Key insight: The most frequent task determines the minimum time. - - Example: tasks=["A","A","A","B","B","B"], n=2 - - 1. Find max frequency: max_freq = 3 (A and B both appear 3 times) - 2. Count tasks with max frequency: max_count = 2 (A and B) - 3. Create frame structure: - Frame: A B _ | A B _ | A B - - (max_freq - 1) complete frames of size (n + 1) - - Last frame contains only max frequency tasks - - 4. Calculate minimum intervals: - - Frame intervals: (max_freq - 1) * (n + 1) = 2 * 3 = 6 - - Plus max frequency tasks: 6 + 2 = 8 - - 5. Return max(total_tasks, calculated_min) to handle cases where - we have enough variety to fill all gaps without idle time. - """ - counts = Counter(tasks) - max_freq = max(counts.values()) - max_count = sum(1 for freq in counts.values() if freq == max_freq) - - # Minimum intervals needed based on most frequent tasks - min_intervals = (max_freq - 1) * (n + 1) + max_count - - # Return max to handle cases with sufficient task variety - return max(len(tasks), min_intervals) diff --git a/leetcode_old/task_scheduler/tests.py b/leetcode_old/task_scheduler/tests.py deleted file mode 100644 index c04842f..0000000 --- a/leetcode_old/task_scheduler/tests.py +++ /dev/null @@ -1,30 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution, SolutionGreedy - - -class TestTaskScheduler: - @pytest.mark.parametrize("solution_class", [Solution, SolutionGreedy]) - @pytest.mark.parametrize( - "tasks, n, expected", - [ - (["A", "A", "A", "B", "B", "B"], 2, 8), - (["A", "C", "A", "B", "D", "B"], 1, 6), - (["A", "A", "A", "B", "B", "B"], 3, 10), - (["A", "A", "A", "A", "A", "A", "B", "C", "D", "E", "F", "G"], 2, 16), - (["A"], 2, 1), - ], - ) - @logged_test - def test_least_interval( - self, - tasks: list[str], - n: int, - expected: int, - solution_class: type[Solution | SolutionGreedy], - ): - solution = solution_class() - result = solution.least_interval(tasks, n) - assert result == expected diff --git a/leetcode_old/three_sum/README.md b/leetcode_old/three_sum/README.md deleted file mode 100644 index c031c0e..0000000 --- a/leetcode_old/three_sum/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# 3Sum - -**Difficulty:** Medium -**Topics:** Array, Two Pointers, Sorting -**Tags:** grind-75 - -**LeetCode:** [Problem 15](https://leetcode.com/problems/three-sum/description/) - -## Problem Description - -Given an integer array `nums`, return all the triplets `[nums[i], nums[j], nums[k]]` such that `i != j`, `i != k`, and `j != k`, and `nums[i] + nums[j] + nums[k] == 0`. - -Notice that the solution set must not contain duplicate triplets. - -## Examples - -### Example 1: - -``` -Input: nums = [-1,0,1,2,-1,-4] -Output: [[-1,-1,2],[-1,0,1]] -``` - -**Explanation:** -nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0. -nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0. -nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0. -The distinct triplets are [-1,0,1] and [-1,-1,2]. -Notice that the order of the output and the order of the triplets does not matter. - -### Example 2: - -``` -Input: nums = [0,1,1] -Output: [] -``` - -**Explanation:** The only possible triplet does not sum up to 0. - -### Example 3: - -``` -Input: nums = [0,0,0] -Output: [[0,0,0]] -``` - -**Explanation:** The only possible triplet sums up to 0. - -## Constraints - -- 3 <= nums.length <= 3000 -- -10^5 <= nums[i] <= 10^5 diff --git a/leetcode_old/three_sum/__init__.py b/leetcode_old/three_sum/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/three_sum/playground.ipynb b/leetcode_old/three_sum/playground.ipynb deleted file mode 100644 index 613f39b..0000000 --- a/leetcode_old/three_sum/playground.ipynb +++ /dev/null @@ -1,73 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "nums = [-1, 0, 1, 2, -1, -4]\n", - "expected = [[-1, -1, 2], [-1, 0, 1]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().three_sum(nums)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "# Sort for comparison since order doesn't matter\n", - "result_sorted = [sorted(triplet) for triplet in result]\n", - "expected_sorted = [sorted(triplet) for triplet in expected]\n", - "result_sorted.sort()\n", - "expected_sorted.sort()\n", - "assert result_sorted == expected_sorted" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/three_sum/solution.py b/leetcode_old/three_sum/solution.py deleted file mode 100644 index 6846e00..0000000 --- a/leetcode_old/three_sum/solution.py +++ /dev/null @@ -1,23 +0,0 @@ -class Solution: - # Time: O(n^2) - # Space: O(k) where k is number of unique triplets - def three_sum(self, nums: list[int]) -> list[list[int]]: - nums.sort() - result = set() - - for i in range(len(nums) - 2): - left, right = i + 1, len(nums) - 1 - - while left < right: - total = nums[i] + nums[left] + nums[right] - - if total < 0: - left += 1 - elif total > 0: - right -= 1 - else: - result.add((nums[i], nums[left], nums[right])) - left += 1 - right -= 1 - - return [list(triplet) for triplet in result] diff --git a/leetcode_old/three_sum/tests.py b/leetcode_old/three_sum/tests.py deleted file mode 100644 index 9448085..0000000 --- a/leetcode_old/three_sum/tests.py +++ /dev/null @@ -1,43 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestThreeSum: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "nums, expected", - [ - ([-1, 0, 1, 2, -1, -4], [[-1, -1, 2], [-1, 0, 1]]), - ([0, 1, 1], []), - ([0, 0, 0], [[0, 0, 0]]), - ([-1, 0, 1], [[-1, 0, 1]]), - ([1, 2, -2, -1], []), - ([-2, 0, 1, 1, 2], [[-2, 0, 2], [-2, 1, 1]]), - # Edge cases - ([1, 2, 3], []), # All positive - ([-3, -2, -1], []), # All negative - ([0, 0, 0, 0], [[0, 0, 0]]), # Multiple zeros - ([-1, -1, 2, 2], [[-1, -1, 2]]), # Duplicate pairs - ([3, 0, -2, -1, 1, 2], [[-2, -1, 3], [-2, 0, 2], [-1, 0, 1]]), # Multiple solutions - ( - [-4, -2, -2, -2, 0, 1, 2, 2, 2, 3, 3, 4, 4, 6, 6], - [[-4, -2, 6], [-4, 0, 4], [-4, 1, 3], [-4, 2, 2], [-2, -2, 4], [-2, 0, 2]], - ), # Many duplicates - ([1, -1, 0], [[-1, 0, 1]]), # Simple case - ([2, -1, -1], [[-1, -1, 2]]), # Solution with duplicates - ], - ) - @logged_test - def test_three_sum(self, nums: list[int], expected: list[list[int]]): - result = self.solution.three_sum(nums) - # Sort both result and expected for comparison since order doesn't matter - result_sorted = [sorted(triplet) for triplet in result] - expected_sorted = [sorted(triplet) for triplet in expected] - result_sorted.sort() - expected_sorted.sort() - assert result_sorted == expected_sorted diff --git a/leetcode_old/time_based_key_value_store/README.md b/leetcode_old/time_based_key_value_store/README.md deleted file mode 100644 index fd46220..0000000 --- a/leetcode_old/time_based_key_value_store/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Time Based Key-Value Store - -**Difficulty:** Medium -**Topics:** Hash Table, String, Binary Search, Design -**Tags:** grind-75 - -**LeetCode:** [Problem 981](https://leetcode.com/problems/time-based-key-value-store/description/) - -## Problem Description - -Design a time-based key-value data structure that can store multiple values for the same key at different time stamps and retrieve the key's value at a certain timestamp. - -Implement the `TimeMap` class: - -- `TimeMap()` Initializes the object of the data structure. -- `void set(String key, String value, int timestamp)` Stores the key `key` with the value `value` at the given time `timestamp`. -- `String get(String key, int timestamp)` Returns a value such that `set` was called previously, with `timestamp_prev <= timestamp`. If there are multiple such values, it returns the value associated with the largest `timestamp_prev`. If there are no values, it returns `""`. - -## Examples - -### Example 1: - -``` -Input -["TimeMap", "set", "get", "get", "set", "get", "get"] -[[], ["foo", "bar", 1], ["foo", 1], ["foo", 3], ["foo", "bar2", 4], ["foo", 4], ["foo", 5]] -Output -[null, null, "bar", "bar", null, "bar2", "bar2"] -``` - -**Explanation:** - -``` -TimeMap timeMap = new TimeMap(); -timeMap.set("foo", "bar", 1); // store the key "foo" and value "bar" along with timestamp = 1. -timeMap.get("foo", 1); // return "bar" -timeMap.get("foo", 3); // return "bar", since there is no value corresponding to foo at timestamp 3 and timestamp 2, then the only value is at timestamp 1 is "bar". -timeMap.set("foo", "bar2", 4); // store the key "foo" and value "bar2" along with timestamp = 4. -timeMap.get("foo", 4); // return "bar2" -timeMap.get("foo", 5); // return "bar2" -``` - -## Constraints - -- `1 <= key.length, value.length <= 100` -- `key` and `value` consist of lowercase English letters and digits. -- `1 <= timestamp <= 10^7` -- All the timestamps `timestamp` of `set` are strictly increasing. -- At most `2 * 10^5` calls will be made to `set` and `get`. diff --git a/leetcode_old/time_based_key_value_store/__init__.py b/leetcode_old/time_based_key_value_store/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/time_based_key_value_store/playground.ipynb b/leetcode_old/time_based_key_value_store/playground.ipynb deleted file mode 100644 index d0567d4..0000000 --- a/leetcode_old/time_based_key_value_store/playground.ipynb +++ /dev/null @@ -1,89 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import TimeMap" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "time_map = TimeMap()\n", - "time_map.set(\"foo\", \"bar\", 1)\n", - "result1 = time_map.get(\"foo\", 1)\n", - "result2 = time_map.get(\"foo\", 3)\n", - "time_map.set(\"foo\", \"bar2\", 4)\n", - "result3 = time_map.get(\"foo\", 4)\n", - "result4 = time_map.get(\"foo\", 5)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "get(foo, 1): bar\n", - "get(foo, 3): bar\n", - "get(foo, 4): bar2\n", - "get(foo, 5): bar2\n" - ] - } - ], - "source": [ - "print(f\"get(foo, 1): {result1}\")\n", - "print(f\"get(foo, 3): {result2}\")\n", - "print(f\"get(foo, 4): {result3}\")\n", - "print(f\"get(foo, 5): {result4}\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result1 == \"bar\"\n", - "assert result2 == \"bar\"\n", - "assert result3 == \"bar2\"\n", - "assert result4 == \"bar2\"" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/time_based_key_value_store/solution.py b/leetcode_old/time_based_key_value_store/solution.py deleted file mode 100644 index fcb11cd..0000000 --- a/leetcode_old/time_based_key_value_store/solution.py +++ /dev/null @@ -1,34 +0,0 @@ -class TimeMap: - # Time: O(1) - # Space: O(n) - def __init__( - self, - ) -> None: - self.store: dict[str, list[tuple[int, str]]] = {} - - # Time: O(1) - # Space: O(1) - def set(self, key: str, value: str, timestamp: int) -> None: - if key not in self.store: - self.store[key] = [] - self.store[key].append((timestamp, value)) - - # Time: O(log n) - # Space: O(1) - def get(self, key: str, timestamp: int) -> str: - if key not in self.store: - return "" - - values = self.store[key] - left, right = 0, len(values) - 1 - result = "" - - while left <= right: - mid = (left + right) // 2 - if values[mid][0] <= timestamp: - result = values[mid][1] - left = mid + 1 - else: - right = mid - 1 - - return result diff --git a/leetcode_old/time_based_key_value_store/tests.py b/leetcode_old/time_based_key_value_store/tests.py deleted file mode 100644 index f4c23a0..0000000 --- a/leetcode_old/time_based_key_value_store/tests.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import TimeMap - - -class TestTimeBasedKeyValueStore: - @pytest.mark.parametrize( - "operations, inputs, expected", - [ - ( - ["TimeMap", "set", "get", "get", "set", "get", "get"], - [ - [], - ["foo", "bar", 1], - ["foo", 1], - ["foo", 3], - ["foo", "bar2", 4], - ["foo", 4], - ["foo", 5], - ], - [None, None, "bar", "bar", None, "bar2", "bar2"], - ) - ], - ) - @logged_test - def test_time_map_operations(self, operations: list[str], inputs: list[list], expected: list): - time_map: TimeMap | None = None - result: list[str | None] = [] - for i, op in enumerate(operations): - if op == "TimeMap": - time_map = TimeMap() - result.append(None) - elif op == "set" and time_map is not None: - time_map.set(*inputs[i]) - result.append(None) - elif op == "get" and time_map is not None: - result.append(time_map.get(*inputs[i])) - assert result == expected diff --git a/leetcode_old/trapping_rain_water/README.md b/leetcode_old/trapping_rain_water/README.md deleted file mode 100644 index fc3a317..0000000 --- a/leetcode_old/trapping_rain_water/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Trapping Rain Water - -**Difficulty:** Hard -**Topics:** Array, Two Pointers, Dynamic Programming, Stack, Monotonic Stack -**Tags:** grind-75 - -**LeetCode:** [Problem 42](https://leetcode.com/problems/trapping-rain-water/description/) - -## Problem Description - -Given `n` non-negative integers representing an elevation map where the width of each bar is `1`, compute how much water it can trap after raining. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2018/10/22/rainwatertrap.png) - -``` -Input: height = [0,1,0,2,1,0,1,3,2,1,2,1] -Output: 6 -``` - -**Explanation:** The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. - -### Example 2: - -``` -Input: height = [4,2,0,3,2,5] -Output: 9 -``` - -## Constraints - -- `n == height.length` -- `1 <= n <= 2 * 10^4` -- `0 <= height[i] <= 10^5` diff --git a/leetcode_old/trapping_rain_water/__init__.py b/leetcode_old/trapping_rain_water/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/trapping_rain_water/playground.ipynb b/leetcode_old/trapping_rain_water/playground.ipynb deleted file mode 100644 index be2bf9a..0000000 --- a/leetcode_old/trapping_rain_water/playground.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]\n", - "expected = 6" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "6" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().trap(height)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/trapping_rain_water/solution.py b/leetcode_old/trapping_rain_water/solution.py deleted file mode 100644 index dd84800..0000000 --- a/leetcode_old/trapping_rain_water/solution.py +++ /dev/null @@ -1,52 +0,0 @@ -# class Solution: -# # Time: O(n) -# # Space: O(1) -# def trap(self, height: list[int]) -> int: -# if not height: -# return 0 - -# left, right = 0, len(height) - 1 -# left_max = right_max = water = 0 - -# while left < right: -# if height[left] < height[right]: -# if height[left] >= left_max: -# left_max = height[left] -# else: -# water += left_max - height[left] -# left += 1 -# else: -# if height[right] >= right_max: -# right_max = height[right] -# else: -# water += right_max - height[right] -# right -= 1 - -# return water - - -class Solution: - # Time: O(n) - # Space: O(1) - def trap(self, height: list[int]) -> int: - if not height: - return 0 - - left, right = 0, len(height) - 1 - left_max = right_max = water = 0 - - while left < right: - if height[left] < height[right]: - if height[left] >= left_max: - left_max = height[left] - else: - water += left_max - height[left] - left += 1 - else: - if height[right] >= right_max: - right_max = height[right] - else: - water += right_max - height[right] - right -= 1 - - return water diff --git a/leetcode_old/trapping_rain_water/tests.py b/leetcode_old/trapping_rain_water/tests.py deleted file mode 100644 index 2a3b912..0000000 --- a/leetcode_old/trapping_rain_water/tests.py +++ /dev/null @@ -1,42 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestTrappingRainWater: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "height, expected", - [ - # Original examples - ([0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1], 6), - ([4, 2, 0, 3, 2, 5], 9), - ([3, 0, 2, 0, 4], 7), - # Edge cases - ([], 0), - ([1], 0), - ([1, 2], 0), - ([2, 1], 0), - # No water trapped - ([1, 2, 3, 4, 5], 0), - ([5, 4, 3, 2, 1], 0), - # Simple cases - ([3, 0, 3], 3), - ([2, 1, 2], 1), - ([5, 2, 7, 2, 6, 1, 5, 3, 2, 4], 14), - # All same height - ([3, 3, 3, 3], 0), - # Valley pattern - ([3, 2, 1, 2, 3], 4), - # Multiple peaks - ([0, 2, 0, 4, 0, 3, 0, 4, 0, 2, 0], 13), - ], - ) - @logged_test - def test_trap(self, height: list[int], expected: int): - result = self.solution.trap(height) - assert result == expected diff --git a/leetcode_old/valid_anagram/README.md b/leetcode_old/valid_anagram/README.md deleted file mode 100644 index 1d0cc68..0000000 --- a/leetcode_old/valid_anagram/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Valid Anagram - -**Difficulty:** Easy -**Topics:** Hash Table, String, Sorting -**Tags:** grind-75 - -**LeetCode:** [Problem 242](https://leetcode.com/problems/valid-anagram/description/) - -## Problem Description - -Given two strings `s` and `t`, return `true` if `t` is an anagram of `s`, and `false` otherwise. - -## Examples - -### Example 1: - -``` -Input: s = "anagram", t = "nagaram" -Output: true -``` - -### Example 2: - -``` -Input: s = "rat", t = "car" -Output: false -``` - -## Constraints - -- 1 <= s.length, t.length <= 5 \* 10^4 -- s and t consist of lowercase English letters. - -**Follow up:** What if the inputs contain Unicode characters? How would you adapt your solution to such a case? diff --git a/leetcode_old/valid_anagram/__init__.py b/leetcode_old/valid_anagram/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/valid_anagram/playground.ipynb b/leetcode_old/valid_anagram/playground.ipynb deleted file mode 100644 index d312663..0000000 --- a/leetcode_old/valid_anagram/playground.ipynb +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "s = \"anagram\"\n", - "t = \"nagaram\"\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().is_anagram(s, t)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/valid_anagram/solution.py b/leetcode_old/valid_anagram/solution.py deleted file mode 100644 index ce6f797..0000000 --- a/leetcode_old/valid_anagram/solution.py +++ /dev/null @@ -1,8 +0,0 @@ -from collections import Counter - - -class Solution: - # Time: O(n) - # Space: O(1) - at most 26 unique characters - def is_anagram(self, s: str, t: str) -> bool: - return Counter(s) == Counter(t) diff --git a/leetcode_old/valid_anagram/tests.py b/leetcode_old/valid_anagram/tests.py deleted file mode 100644 index aea8280..0000000 --- a/leetcode_old/valid_anagram/tests.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestValidAnagram: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "s, t, expected", - [ - ("anagram", "nagaram", True), - ("rat", "car", False), - ("listen", "silent", True), - ("hello", "bello", False), - ("", "", True), - ("a", "a", True), - ("a", "b", False), - ("ab", "ba", True), - ("abc", "bca", True), - ("abc", "def", False), - ("aab", "abb", False), - ("aabbcc", "abcabc", True), - ("abcd", "abcde", False), - ("race", "care", True), - ("elbow", "below", True), - ("study", "dusty", True), - ("night", "thing", True), - ("stressed", "desserts", True), - ], - ) - @logged_test - def test_is_anagram(self, s: str, t: str, expected: bool): - result = self.solution.is_anagram(s, t) - assert result == expected diff --git a/leetcode_old/valid_palindrome/README.md b/leetcode_old/valid_palindrome/README.md deleted file mode 100644 index e2444ef..0000000 --- a/leetcode_old/valid_palindrome/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Valid Palindrome - -**Difficulty:** Easy -**Topics:** Two Pointers, String -**Tags:** grind-75 - -**LeetCode:** [Problem 125](https://leetcode.com/problems/valid-palindrome/description/) - -## Problem Description - -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. - -## Examples - -### Example 1: - -``` -Input: s = "A man, a plan, a canal: Panama" -Output: true -``` - -**Explanation:** "amanaplanacanalpanama" is a palindrome. - -### Example 2: - -``` -Input: s = "race a car" -Output: false -``` - -**Explanation:** "raceacar" is not a palindrome. - -### Example 3: - -``` -Input: s = " " -Output: true -``` - -**Explanation:** s is an empty string "" after removing non-alphanumeric characters. Since an empty string reads the same forward and backward, it is a palindrome. - -## Constraints - -- `1 <= s.length <= 2 * 10^5` -- `s` consists only of printable ASCII characters. diff --git a/leetcode_old/valid_palindrome/__init__.py b/leetcode_old/valid_palindrome/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/valid_palindrome/playground.ipynb b/leetcode_old/valid_palindrome/playground.ipynb deleted file mode 100644 index 86aeb30..0000000 --- a/leetcode_old/valid_palindrome/playground.ipynb +++ /dev/null @@ -1,68 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "s = \"A man, a plan, a canal: Panama\"\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().is_palindrome(s)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/valid_palindrome/solution.py b/leetcode_old/valid_palindrome/solution.py deleted file mode 100644 index e2eba71..0000000 --- a/leetcode_old/valid_palindrome/solution.py +++ /dev/null @@ -1,19 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(1) - def is_palindrome(self, s: str) -> bool: - left, right = 0, len(s) - 1 - - while left < right: - while left < right and not s[left].isalnum(): - left += 1 - while left < right and not s[right].isalnum(): - right -= 1 - - if s[left].lower() != s[right].lower(): - return False - - left += 1 - right -= 1 - - return True diff --git a/leetcode_old/valid_palindrome/tests.py b/leetcode_old/valid_palindrome/tests.py deleted file mode 100644 index a029400..0000000 --- a/leetcode_old/valid_palindrome/tests.py +++ /dev/null @@ -1,28 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestValidPalindrome: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "s, expected", - [ - ("A man, a plan, a canal: Panama", True), - ("race a car", False), - (" ", True), - ("", True), - ("a", True), - ("Madam", True), - ("No 'x' in Nixon", True), - ("Mr. Owl ate my metal worm", True), - ], - ) - @logged_test - def test_is_palindrome(self, s: str, expected: bool): - result = self.solution.is_palindrome(s) - assert result == expected diff --git a/leetcode_old/valid_parentheses/README.md b/leetcode_old/valid_parentheses/README.md deleted file mode 100644 index 05d24f0..0000000 --- a/leetcode_old/valid_parentheses/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Valid Parentheses - -**Difficulty:** Easy -**Topics:** String, Stack -**Tags:** grind-75 - -**LeetCode:** [Problem 20](https://leetcode.com/problems/valid-parentheses/description/) - -## Problem Description - -Given a string `s` containing just the characters `'('`, `')'`, `'{'`, `'}'`, `'['` and `']'`, determine if the input string is valid. - -An input string is valid if: - -1. Open brackets must be closed by the same type of brackets. -2. Open brackets must be closed in the correct order. -3. Every close bracket has a corresponding open bracket of the same type. - -## Examples - -### Example 1: - -``` -Input: s = "()" -Output: true -``` - -### Example 2: - -``` -Input: s = "()[]{}" -Output: true -``` - -### Example 3: - -``` -Input: s = "(]" -Output: false -``` - -### Example 4: - -``` -Input: s = "([])" -Output: true -``` - -### Example 5: - -``` -Input: s = "([)]" -Output: false -``` - -## Constraints - -- `1 <= s.length <= 10^4` -- `s` consists of parentheses only `'()[]{}'`. diff --git a/leetcode_old/valid_parentheses/__init__.py b/leetcode_old/valid_parentheses/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/valid_parentheses/playground.ipynb b/leetcode_old/valid_parentheses/playground.ipynb deleted file mode 100644 index e62b442..0000000 --- a/leetcode_old/valid_parentheses/playground.ipynb +++ /dev/null @@ -1,68 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "s = \"()\"\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().is_valid(s)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/valid_parentheses/solution.py b/leetcode_old/valid_parentheses/solution.py deleted file mode 100644 index 779f734..0000000 --- a/leetcode_old/valid_parentheses/solution.py +++ /dev/null @@ -1,14 +0,0 @@ -class Solution: - # Time: O(n) - # Space: O(n) - def is_valid(self, s: str) -> bool: - stack = [] - pairs = {"(": ")", "[": "]", "{": "}"} - - for char in s: - if char in pairs: - stack.append(char) - elif not stack or pairs[stack.pop()] != char: - return False - - return not stack diff --git a/leetcode_old/valid_parentheses/tests.py b/leetcode_old/valid_parentheses/tests.py deleted file mode 100644 index 528dacb..0000000 --- a/leetcode_old/valid_parentheses/tests.py +++ /dev/null @@ -1,46 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestValidParentheses: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "s, expected", - [ - ("()", True), - ("()[]{}", True), - ("(]", False), - ("([])", True), - ("([)]", False), - ("", True), - ("(", False), - (")", False), - ("{[()]}", True), - ("{[(])}", False), - ("(((", False), - (")))", False), - ("()()()", True), - ("({[]})", True), - ("({[}])", False), - ("[[[[[]]]]]", True), - ("[[[[[]]]]", False), - ("{{{{{}}}}}", True), - ("((((((((((", False), - ("))))))))))", False), - ("(){}[]", True), - ("([{}])", True), - ("([{]})", False), - ("(())", True), - ("(()", False), - ("())", False), - ], - ) - @logged_test - def test_is_valid(self, s: str, expected: bool): - result = self.solution.is_valid(s) - assert result == expected diff --git a/leetcode_old/validate_binary_search_tree/README.md b/leetcode_old/validate_binary_search_tree/README.md deleted file mode 100644 index 4b8b99f..0000000 --- a/leetcode_old/validate_binary_search_tree/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Validate Binary Search Tree - -**Difficulty:** Medium -**Topics:** Tree, Depth-First Search, Binary Search Tree, Binary Tree -**Tags:** grind-75 - -**LeetCode:** [Problem 98](https://leetcode.com/problems/validate-binary-search-tree/description/) - -## Problem Description - -Given the `root` of a binary tree, determine if it is a valid binary search tree (BST). - -A **valid BST** is defined as follows: - -- The left subtree of a node contains only nodes with keys **strictly less than** the node's key. -- The right subtree of a node contains only nodes with keys **strictly greater than** the node's key. -- Both the left and right subtrees must also be binary search trees. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2020/12/01/tree1.jpg) - -``` -Input: root = [2,1,3] -Output: true -``` - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2020/12/01/tree2.jpg) - -``` -Input: root = [5,1,4,null,null,3,6] -Output: false -``` - -**Explanation:** The root node's value is 5 but its right child's value is 4. - -## Constraints - -- The number of nodes in the tree is in the range `[1, 10^4]`. -- `-2^31 <= Node.val <= 2^31 - 1` diff --git a/leetcode_old/validate_binary_search_tree/__init__.py b/leetcode_old/validate_binary_search_tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/validate_binary_search_tree/playground.ipynb b/leetcode_old/validate_binary_search_tree/playground.ipynb deleted file mode 100644 index 4b972d5..0000000 --- a/leetcode_old/validate_binary_search_tree/playground.ipynb +++ /dev/null @@ -1,82 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution\n", - "\n", - "from leetcode_py import TreeNode" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "root_list: list[int | None] = [2, 1, 3]\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "root = TreeNode.from_list(root_list)\n", - "result = Solution().is_valid_bst(root)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/validate_binary_search_tree/solution.py b/leetcode_old/validate_binary_search_tree/solution.py deleted file mode 100644 index 3b4dcbf..0000000 --- a/leetcode_old/validate_binary_search_tree/solution.py +++ /dev/null @@ -1,60 +0,0 @@ -from collections import deque - -from leetcode_py import TreeNode - - -class Solution: - @classmethod - def validate(cls, node: TreeNode[int] | None, min_val: float, max_val: float) -> bool: - if not node: - return True - if node.val <= min_val or node.val >= max_val: - return False - return cls.validate(node.left, min_val, node.val) and cls.validate(node.right, node.val, max_val) - - # Time: O(n) - # Space: O(h) - def is_valid_bst(self, root: TreeNode[int] | None) -> bool: - return self.validate(root, float("-inf"), float("inf")) - - -class SolutionDFS: - # Time: O(n) - # Space: O(h) - def is_valid_bst(self, root: TreeNode[int] | None) -> bool: - if not root: - return True - - stack = [(root, float("-inf"), float("inf"))] - - while stack: - node, min_val, max_val = stack.pop() - if node.val <= min_val or node.val >= max_val: - return False - if node.right: - stack.append((node.right, node.val, max_val)) - if node.left: - stack.append((node.left, min_val, node.val)) - - return True - - -class SolutionBFS: - # Time: O(n) - # Space: O(w) where w is max width - def is_valid_bst(self, root: TreeNode[int] | None) -> bool: - if not root: - return True - - queue = deque([(root, float("-inf"), float("inf"))]) - - while queue: - node, min_val, max_val = queue.popleft() - if node.val <= min_val or node.val >= max_val: - return False - if node.right: - queue.append((node.right, node.val, max_val)) - if node.left: - queue.append((node.left, min_val, node.val)) - - return True diff --git a/leetcode_old/validate_binary_search_tree/tests.py b/leetcode_old/validate_binary_search_tree/tests.py deleted file mode 100644 index b03d47e..0000000 --- a/leetcode_old/validate_binary_search_tree/tests.py +++ /dev/null @@ -1,30 +0,0 @@ -import pytest - -from leetcode_py import TreeNode -from leetcode_py.test_utils import logged_test - -from .solution import Solution, SolutionBFS, SolutionDFS - - -class TestValidateBinarySearchTree: - @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS]) - @pytest.mark.parametrize( - "root_list, expected", - [ - ([2, 1, 3], True), - ([5, 1, 4, None, None, 3, 6], False), - ([1], True), - ([1, 1], False), - ], - ) - @logged_test - def test_is_valid_bst( - self, - root_list: list[int | None], - expected: bool, - solution_class: type[Solution | SolutionDFS | SolutionBFS], - ): - solution = solution_class() - root = TreeNode.from_list(root_list) - result = solution.is_valid_bst(root) - assert result == expected diff --git a/leetcode_old/word_break/README.md b/leetcode_old/word_break/README.md deleted file mode 100644 index 7bb5206..0000000 --- a/leetcode_old/word_break/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Word Break - -**Difficulty:** Medium -**Topics:** Array, Hash Table, String, Dynamic Programming, Trie, Memoization -**Tags:** grind-75 - -**LeetCode:** [Problem 139](https://leetcode.com/problems/word-break/description/) - -## Problem Description - -Given a string `s` and a dictionary of strings `wordDict`, return `true` if `s` can be segmented into a space-separated sequence of one or more dictionary words. - -**Note** that the same word in the dictionary may be reused multiple times in the segmentation. - -## Examples - -### Example 1: - -``` -Input: s = "leetcode", wordDict = ["leet","code"] -Output: true -``` - -**Explanation:** Return true because "leetcode" can be segmented as "leet code". - -### Example 2: - -``` -Input: s = "applepenapple", wordDict = ["apple","pen"] -Output: true -``` - -**Explanation:** Return true because "applepenapple" can be segmented as "apple pen apple". -Note that you are allowed to reuse a dictionary word. - -### Example 3: - -``` -Input: s = "catsandog", wordDict = ["cats","dog","sand","and","cat"] -Output: false -``` - -## Constraints - -- `1 <= s.length <= 300` -- `1 <= wordDict.length <= 1000` -- `1 <= wordDict[i].length <= 20` -- `s` and `wordDict[i]` consist of only lowercase English letters. -- All the strings of `wordDict` are **unique**. diff --git a/leetcode_old/word_break/__init__.py b/leetcode_old/word_break/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/word_break/playground.ipynb b/leetcode_old/word_break/playground.ipynb deleted file mode 100644 index 3d5b07d..0000000 --- a/leetcode_old/word_break/playground.ipynb +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "s = \"leetcode\"\n", - "word_dict = [\"leet\", \"code\"]\n", - "expected = True" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().word_break(s, word_dict)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/word_break/solution.py b/leetcode_old/word_break/solution.py deleted file mode 100644 index ed8dc30..0000000 --- a/leetcode_old/word_break/solution.py +++ /dev/null @@ -1,15 +0,0 @@ -class Solution: - # Time: O(n^2) - # Space: O(n) - def word_break(self, s: str, word_dict: list[str]) -> bool: - word_set = set(word_dict) - dp = [False] * (len(s) + 1) - dp[0] = True - - for i in range(1, len(s) + 1): - for j in range(i): - if dp[j] and s[j:i] in word_set: - dp[i] = True - break - - return dp[-1] diff --git a/leetcode_old/word_break/tests.py b/leetcode_old/word_break/tests.py deleted file mode 100644 index f250fa1..0000000 --- a/leetcode_old/word_break/tests.py +++ /dev/null @@ -1,27 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestWordBreak: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "s, word_dict, expected", - [ - ("leetcode", ["leet", "code"], True), - ("applepenapple", ["apple", "pen"], True), - ("catsandog", ["cats", "dog", "sand", "and", "cat"], False), - ("", [], True), - ("a", ["a"], True), - ("ab", ["a", "b"], True), - ("abcd", ["a", "abc", "d"], True), - ], - ) - @logged_test - def test_word_break(self, s: str, word_dict: list[str], expected: bool): - result = self.solution.word_break(s, word_dict) - assert result == expected diff --git a/leetcode_old/word_ladder/README.md b/leetcode_old/word_ladder/README.md deleted file mode 100644 index 32820d3..0000000 --- a/leetcode_old/word_ladder/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Word Ladder - -**Difficulty:** Hard -**Topics:** Hash Table, String, Breadth-First Search -**Tags:** grind-75 - -**LeetCode:** [Problem 127](https://leetcode.com/problems/word-ladder/description/) - -## Problem Description - -A **transformation sequence** from word `beginWord` to word `endWord` using a dictionary `wordList` is a sequence of words `beginWord -> s1 -> s2 -> ... -> sk` such that: - -- Every adjacent pair of words differs by a single letter. -- Every `si` for `1 <= i <= k` is in `wordList`. Note that `beginWord` does not need to be in `wordList`. -- `sk == endWord` - -Given two words, `beginWord` and `endWord`, and a dictionary `wordList`, return the **number of words** in the **shortest transformation sequence** from `beginWord` to `endWord`, or `0` if no such sequence exists. - -## Examples - -### Example 1: - -``` -Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] -Output: 5 -``` - -**Explanation:** One shortest transformation sequence is "hit" -> "hot" -> "dot" -> "dog" -> "cog", which is 5 words long. - -### Example 2: - -``` -Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"] -Output: 0 -``` - -**Explanation:** The endWord "cog" is not in wordList, therefore there is no valid transformation sequence. - -## Constraints - -- 1 <= beginWord.length <= 10 -- endWord.length == beginWord.length -- 1 <= wordList.length <= 5000 -- wordList[i].length == beginWord.length -- beginWord, endWord, and wordList[i] consist of lowercase English letters. -- beginWord != endWord -- All the words in wordList are unique. diff --git a/leetcode_old/word_ladder/__init__.py b/leetcode_old/word_ladder/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/word_ladder/playground.ipynb b/leetcode_old/word_ladder/playground.ipynb deleted file mode 100644 index da0260d..0000000 --- a/leetcode_old/word_ladder/playground.ipynb +++ /dev/null @@ -1,81 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "begin_word = \"hit\"\n", - "end_word = \"cog\"\n", - "word_list = [\"hot\", \"dot\", \"dog\", \"lot\", \"log\", \"cog\"]\n", - "expected = 5" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "execute", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result = Solution().ladder_length(begin_word, end_word, word_list)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/word_ladder/solution.py b/leetcode_old/word_ladder/solution.py deleted file mode 100644 index 7b74aa4..0000000 --- a/leetcode_old/word_ladder/solution.py +++ /dev/null @@ -1,36 +0,0 @@ -class Solution: - # Time: O(M^2 * N) where M is length of each word, N is total number of words - # Space: O(M * N) for the visited sets - def ladder_length(self, begin_word: str, end_word: str, word_list: list[str]) -> int: - if end_word not in word_list: - return 0 - - if begin_word == end_word: - return 1 - - word_set = set(word_list) - begin_set = {begin_word} - end_set = {end_word} - length = 1 - - while begin_set and end_set: - if len(begin_set) > len(end_set): - begin_set, end_set = end_set, begin_set - - next_set = set() - for word in begin_set: - for i in range(len(word)): - for c in "abcdefghijklmnopqrstuvwxyz": - new_word = word[:i] + c + word[i + 1 :] - - if new_word in end_set: - return length + 1 - - if new_word in word_set: - next_set.add(new_word) - word_set.remove(new_word) - - begin_set = next_set - length += 1 - - return 0 diff --git a/leetcode_old/word_ladder/tests.py b/leetcode_old/word_ladder/tests.py deleted file mode 100644 index 0b7682f..0000000 --- a/leetcode_old/word_ladder/tests.py +++ /dev/null @@ -1,42 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestWordLadder: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "begin_word, end_word, word_list, expected", - [ - # Basic cases - ("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"], 5), - ("hit", "cog", ["hot", "dot", "dog", "lot", "log"], 0), - ("a", "c", ["a", "b", "c"], 2), - # Edge cases - ("hot", "dog", ["hot", "dog"], 0), # No intermediate words - ("hot", "hot", ["hot"], 1), # Same word - ("cat", "dog", [], 0), # Empty word list - ("cat", "dog", ["cat"], 0), # End word not in list - # Single character changes - ("a", "b", ["a", "b"], 2), - ("ab", "cd", ["ab", "ad", "cd"], 3), - # Longer paths - ("red", "tax", ["ted", "tex", "red", "tax", "tad", "den", "rex", "pee"], 4), - # Multiple possible paths (should return shortest) - ("cat", "dog", ["cat", "bat", "bag", "dag", "dog", "cag", "cog"], 4), - # No path exists - ("abc", "def", ["abc", "def", "ghi"], 0), - # Direct transformation - ("cat", "bat", ["cat", "bat"], 2), - # Longer word length - ("word", "form", ["word", "worm", "form", "foam", "flam", "flab"], 3), - ], - ) - @logged_test - def test_ladder_length(self, begin_word: str, end_word: str, word_list: list[str], expected: int): - result = self.solution.ladder_length(begin_word, end_word, word_list) - assert result == expected diff --git a/leetcode_old/zero_one_matrix/README.md b/leetcode_old/zero_one_matrix/README.md deleted file mode 100644 index 29b3127..0000000 --- a/leetcode_old/zero_one_matrix/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# 01 Matrix - -**Difficulty:** Medium -**Topics:** Array, Dynamic Programming, Breadth-First Search, Matrix -**Tags:** grind-75 - -**LeetCode:** [Problem 542](https://leetcode.com/problems/zero-one-matrix/description/) - -## Problem Description - -Given an `m x n` binary matrix `mat`, return the distance of the nearest `0` for each cell. - -The distance between two cells sharing a common edge is `1`. - -## Examples - -### Example 1: - -![Example 1](https://assets.leetcode.com/uploads/2021/04/24/01-1-grid.jpg) - -``` -Input: mat = [[0,0,0],[0,1,0],[0,0,0]] -Output: [[0,0,0],[0,1,0],[0,0,0]] -``` - -### Example 2: - -![Example 2](https://assets.leetcode.com/uploads/2021/04/24/01-2-grid.jpg) - -``` -Input: mat = [[0,0,0],[0,1,0],[1,1,1]] -Output: [[0,0,0],[0,1,0],[1,2,1]] -``` - -## Constraints - -- `m == mat.length` -- `n == mat[i].length` -- `1 <= m, n <= 10^4` -- `1 <= m * n <= 10^4` -- `mat[i][j]` is either `0` or `1` -- There is at least one `0` in `mat` - -**Note:** This question is the same as 1765: [Map of Highest Peak](https://leetcode.com/problems/map-of-highest-peak/) diff --git a/leetcode_old/zero_one_matrix/__init__.py b/leetcode_old/zero_one_matrix/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/leetcode_old/zero_one_matrix/playground.ipynb b/leetcode_old/zero_one_matrix/playground.ipynb deleted file mode 100644 index 317b713..0000000 --- a/leetcode_old/zero_one_matrix/playground.ipynb +++ /dev/null @@ -1,68 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "from solution import Solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "setup", - "metadata": {}, - "outputs": [], - "source": [ - "# Example test case\n", - "mat = [[0, 0, 0], [0, 1, 0], [1, 1, 1]]\n", - "expected = [[0, 0, 0], [0, 1, 0], [1, 2, 1]]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "execute", - "metadata": {}, - "outputs": [], - "source": [ - "result = Solution().update_matrix(mat)\n", - "result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "test", - "metadata": {}, - "outputs": [], - "source": [ - "assert result == expected" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "leetcode-py-py3.13", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python3", - "pygments_lexer": "ipython3", - "version": "3.13.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/leetcode_old/zero_one_matrix/solution.py b/leetcode_old/zero_one_matrix/solution.py deleted file mode 100644 index b70c86c..0000000 --- a/leetcode_old/zero_one_matrix/solution.py +++ /dev/null @@ -1,30 +0,0 @@ -from collections import deque - - -class Solution: - # Time: O(m * n) - # Space: O(m * n) - def update_matrix(self, mat: list[list[int]]) -> list[list[int]]: - UNSEEN = -1 - m, n = len(mat), len(mat[0]) - queue: deque[tuple[int, int]] = deque() - - # Mark 1s as UNSEEN and add all 0s to queue - for i in range(m): - for j in range(n): - if mat[i][j] == 0: - queue.append((i, j)) - else: - mat[i][j] = UNSEEN - - # BFS from all 0s simultaneously - directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] - while queue: - row, col = queue.popleft() - for dr, dc in directions: - r, c = row + dr, col + dc - if 0 <= r < m and 0 <= c < n and mat[r][c] == UNSEEN: - mat[r][c] = mat[row][col] + 1 - queue.append((r, c)) - - return mat diff --git a/leetcode_old/zero_one_matrix/tests.py b/leetcode_old/zero_one_matrix/tests.py deleted file mode 100644 index 6b5a4e4..0000000 --- a/leetcode_old/zero_one_matrix/tests.py +++ /dev/null @@ -1,29 +0,0 @@ -import pytest - -from leetcode_py.test_utils import logged_test - -from .solution import Solution - - -class TestZeroOneMatrix: - def setup_method(self): - self.solution = Solution() - - @pytest.mark.parametrize( - "mat, expected", - [ - ([[0, 0, 0], [0, 1, 0], [0, 0, 0]], [[0, 0, 0], [0, 1, 0], [0, 0, 0]]), - ([[0, 0, 0], [0, 1, 0], [1, 1, 1]], [[0, 0, 0], [0, 1, 0], [1, 2, 1]]), - ([[0]], [[0]]), - ([[1, 1, 1], [1, 1, 1], [1, 1, 0]], [[4, 3, 2], [3, 2, 1], [2, 1, 0]]), - ([[0, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], [[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]), - ( - [[1, 0, 1, 1, 0, 0, 1, 0, 0, 1], [0, 1, 1, 0, 1, 0, 1, 0, 1, 1]], - [[1, 0, 1, 1, 0, 0, 1, 0, 0, 1], [0, 1, 1, 0, 1, 0, 1, 0, 1, 2]], - ), - ], - ) - @logged_test - def test_update_matrix(self, mat: list[list[int]], expected: list[list[int]]): - result = self.solution.update_matrix(mat) - assert result == expected From b0cdabef44b532d10e43c381b0e0c2f70e7165d6 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 18:45:42 +0700 Subject: [PATCH 24/39] feat: update mismatch --- leetcode/climbing_stairs/solution.py | 5 +-- leetcode/first_bad_version/solution.py | 2 - leetcode/min_stack/solution.py | 13 ++++++ leetcode/minimum_height_trees/solution.py | 5 ++- leetcode/rotting_oranges/solution.py | 5 ++- .../solution.py | 45 +++++++++++++++++++ leetcode/zero_one_matrix/solution.py | 5 ++- 7 files changed, 69 insertions(+), 11 deletions(-) diff --git a/leetcode/climbing_stairs/solution.py b/leetcode/climbing_stairs/solution.py index 2e990a4..118c6b4 100644 --- a/leetcode/climbing_stairs/solution.py +++ b/leetcode/climbing_stairs/solution.py @@ -2,10 +2,9 @@ class Solution: # Time: O(n) # Space: O(1) + # This follows Fibonacci pattern + # Standard Fib: F(0)=0, F(1)=1, F(2)=1, F(3)=2, F(4)=3, F(5)=5... def climb_stairs(self, n: int) -> int: - - # This follows Fibonacci pattern - # Standard Fib: F(0)=0, F(1)=1, F(2)=1, F(3)=2, F(4)=3, F(5)=5... if n <= 2: return n diff --git a/leetcode/first_bad_version/solution.py b/leetcode/first_bad_version/solution.py index 623e544..0d0fb12 100644 --- a/leetcode/first_bad_version/solution.py +++ b/leetcode/first_bad_version/solution.py @@ -1,7 +1,5 @@ class Solution: - # Time: O(?) - # Space: O(?) def __init__(self, first_bad: int = 1) -> None: self.is_bad_version = lambda version: version >= first_bad diff --git a/leetcode/min_stack/solution.py b/leetcode/min_stack/solution.py index 92ffe03..fc59403 100644 --- a/leetcode/min_stack/solution.py +++ b/leetcode/min_stack/solution.py @@ -28,3 +28,16 @@ def top(self) -> int: # Space: O(1) def get_min(self) -> int: return self.min_stack[-1] + + +# Example walkthrough: push(-2), push(0), push(-3), getMin(), pop(), top(), getMin() +# +# Initial: stack=[], min_stack=[] +# +# push(-2): stack=[-2], min_stack=[-2] (first element, add to both) +# push(0): stack=[-2,0], min_stack=[-2] (0 > -2, don't add to min_stack) +# push(-3): stack=[-2,0,-3], min_stack=[-2,-3] (-3 <= -2, add to min_stack) +# getMin(): return -3 (top of min_stack) +# pop(): stack=[-2,0], min_stack=[-2] (-3 was min, remove from both stacks) +# top(): return 0 (top of main stack) +# getMin(): return -2 (top of min_stack after pop) diff --git a/leetcode/minimum_height_trees/solution.py b/leetcode/minimum_height_trees/solution.py index 37120f7..7ecb55e 100644 --- a/leetcode/minimum_height_trees/solution.py +++ b/leetcode/minimum_height_trees/solution.py @@ -1,10 +1,11 @@ +from collections import defaultdict, deque + + class Solution: # Time: O(V) # Space: O(V) def find_min_height_trees(self, n: int, edges: list[list[int]]) -> list[int]: - from collections import defaultdict, deque - if n == 1: return [0] diff --git a/leetcode/rotting_oranges/solution.py b/leetcode/rotting_oranges/solution.py index 8cdcd5e..bab56fd 100644 --- a/leetcode/rotting_oranges/solution.py +++ b/leetcode/rotting_oranges/solution.py @@ -1,10 +1,11 @@ +from collections import deque + + class Solution: # Time: O(m*n) # Space: O(m*n) def oranges_rotting(self, grid: list[list[int]]) -> int: - from collections import deque - EMPTY, FRESH, ROTTEN = 0, 1, 2 _ = EMPTY diff --git a/leetcode/serialize_and_deserialize_binary_tree/solution.py b/leetcode/serialize_and_deserialize_binary_tree/solution.py index 680e164..14c613c 100644 --- a/leetcode/serialize_and_deserialize_binary_tree/solution.py +++ b/leetcode/serialize_and_deserialize_binary_tree/solution.py @@ -39,3 +39,48 @@ def dfs(): return node return dfs() + + +# Binary Tree Serialization Techniques + +# Example Tree: +# 1 +# / \ +# 2 3 +# / \ +# 4 5 + +# 1. Preorder with Null Markers (This Implementation) +# Visit: root → left → right, mark nulls with '#' +# Result: "1,2,#,#,3,4,#,#,5,#,#" +# Pros: Self-contained, unambiguous, O(n) reconstruction +# Cons: Longer string due to null markers + +# 2. Level-order (BFS) with Null Markers +# Visit level by level, mark nulls with '#' +# Result: "1,2,3,#,#,4,5" +# Pros: Simple format like preorder, level-by-level intuitive +# Cons: Still requires queue processing + +# 3. Postorder with Null Markers +# Visit: left → right → root +# Result: "#,#,2,#,#,4,#,#,5,3,1" +# Pros: Bottom-up reconstruction +# Cons: Less intuitive than preorder + +# 4. Inorder + Preorder (Two Arrays) +# Inorder: [2,1,4,3,5], Preorder: [1,2,3,4,5] +# Pros: Works for any binary tree structure +# Cons: Requires two arrays, only works with unique values + +# 5. Parenthetical Preorder +# Same traversal as #1 but with parentheses format: value(left)(right) +# Result: "1(2()())(3(4()())(5()()))" +# Pros: Human readable structure, shows nesting clearly +# Cons: Complex parsing, verbose + +# 6. Parenthetical Postorder +# Same traversal as #3 but with parentheses format: (left)(right)value +# Result: "(()()2)((()()4)(()()5)3)1" +# Pros: Bottom-up readable structure +# Cons: Even more complex parsing diff --git a/leetcode/zero_one_matrix/solution.py b/leetcode/zero_one_matrix/solution.py index ba59c37..11fdf12 100644 --- a/leetcode/zero_one_matrix/solution.py +++ b/leetcode/zero_one_matrix/solution.py @@ -1,10 +1,11 @@ +from collections import deque + + class Solution: # Time: O(m * n) # Space: O(m * n) def update_matrix(self, mat: list[list[int]]) -> list[list[int]]: - from collections import deque - UNSEEN = -1 m, n = len(mat), len(mat[0]) queue: deque[tuple[int, int]] = deque() From 1e4a5476207ca9ee75f7c3fa317052b91d55a9eb Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 18:56:25 +0700 Subject: [PATCH 25/39] ci: fix reproducibility --- .templates/leetcode/json/min_stack.json | 4 ++-- .templates/leetcode/json/time_based_key_value_store.json | 2 +- Makefile | 2 +- leetcode/min_stack/helpers.py | 5 ++++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.templates/leetcode/json/min_stack.json b/.templates/leetcode/json/min_stack.json index ad8210a..5e902b9 100644 --- a/.templates/leetcode/json/min_stack.json +++ b/.templates/leetcode/json/min_stack.json @@ -16,11 +16,11 @@ }, "readme_constraints": "- `-2^31 <= val <= 2^31 - 1`\n- Methods `pop`, `top` and `getMin` operations will always be called on **non-empty** stacks.\n- At most `3 * 10^4` calls will be made to `push`, `pop`, `top`, and `getMin`.", "readme_additional": "", - "helpers_imports": "", + "helpers_imports": "from typing import Any", "helpers_content": "", "helpers_run_name": "min_stack_operations", "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list[int]])", - "helpers_run_body": " stack: solution_class | None = None\n results: list[int | None] = []\n for i, op in enumerate(operations):\n if op == 'MinStack':\n stack = solution_class()\n results.append(None)\n elif op == 'push' and stack is not None:\n stack.push(inputs[i][0])\n results.append(None)\n elif op == 'pop' and stack is not None:\n stack.pop()\n results.append(None)\n elif op == 'top' and stack is not None:\n results.append(stack.top())\n elif op == 'getMin' and stack is not None:\n results.append(stack.get_min())\n return results", + "helpers_run_body": " stack: Any = None\n results: list[int | None] = []\n for i, op in enumerate(operations):\n if op == 'MinStack':\n stack = solution_class()\n results.append(None)\n elif op == 'push' and stack is not None:\n stack.push(inputs[i][0])\n results.append(None)\n elif op == 'pop' and stack is not None:\n stack.pop()\n results.append(None)\n elif op == 'top' and stack is not None:\n results.append(stack.top())\n elif op == 'getMin' and stack is not None:\n results.append(stack.get_min())\n return results", "helpers_assert_name": "min_stack_operations", "helpers_assert_signature": "(result: list[int | None], expected: list[int | None]) -> bool", "helpers_assert_body": " assert result == expected\n return True", diff --git a/.templates/leetcode/json/time_based_key_value_store.json b/.templates/leetcode/json/time_based_key_value_store.json index 31178eb..529ce24 100644 --- a/.templates/leetcode/json/time_based_key_value_store.json +++ b/.templates/leetcode/json/time_based_key_value_store.json @@ -20,7 +20,7 @@ "helpers_content": "", "helpers_run_name": "time_map_operations", "helpers_run_signature": "(solution_class: type, operations: list[str], inputs: list[list])", - "helpers_run_body": " time_map = None\n results = []\n for i, op in enumerate(operations):\n if op == 'TimeMap':\n time_map = solution_class()\n results.append(None)\n elif op == 'set' and time_map is not None:\n time_map.set(*inputs[i])\n results.append(None)\n elif op == 'get' and time_map is not None:\n results.append(time_map.get(*inputs[i]))\n return results, time_map", + "helpers_run_body": " time_map = None\n results: list[str | None] = []\n for i, op in enumerate(operations):\n if op == 'TimeMap':\n time_map = solution_class()\n results.append(None)\n elif op == 'set' and time_map is not None:\n time_map.set(*inputs[i])\n results.append(None)\n elif op == 'get' and time_map is not None:\n results.append(time_map.get(*inputs[i]))\n return results, time_map", "helpers_assert_name": "time_map_operations", "helpers_assert_signature": "(result: list, expected: list) -> bool", "helpers_assert_body": " results, _ = result\n assert results == expected\n return True", diff --git a/Makefile b/Makefile index 9daca2b..3bdc248 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VERSION = 3.13 -PROBLEM ?= binary_tree_right_side_view +PROBLEM ?= min_stack FORCE ?= 0 COMMA := , diff --git a/leetcode/min_stack/helpers.py b/leetcode/min_stack/helpers.py index 87bd4c6..858f516 100644 --- a/leetcode/min_stack/helpers.py +++ b/leetcode/min_stack/helpers.py @@ -1,5 +1,8 @@ +from typing import Any + + def run_min_stack_operations(solution_class: type, operations: list[str], inputs: list[list[int]]): - stack = None + stack: Any = None results: list[int | None] = [] for i, op in enumerate(operations): if op == "MinStack": From 5eb8d3c265adc877cb3f192e3ac36b16ac928827 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 19:04:49 +0700 Subject: [PATCH 26/39] ci: update cache Graphviz --- .github/workflows/ci-test.yml | 57 ++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 8204474..be1a298 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -36,19 +36,60 @@ jobs: if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-ansi - - name: Cache Graphviz + - name: Cache Graphviz installation id: cache-graphviz uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: - path: /usr/bin/dot - key: graphviz-${{ runner.os }} + path: | + /usr/bin/dot + /usr/bin/neato + /usr/bin/twopi + /usr/bin/circo + /usr/bin/fdp + /usr/bin/sfdp + /usr/bin/patchwork + /usr/bin/osage + /usr/lib/x86_64-linux-gnu/graphviz + /usr/share/graphviz + key: graphviz-${{ runner.os }}-v2 - - name: Install Graphviz - if: steps.cache-graphviz.outputs.cache-hit != 'true' - run: sudo apt-get update && sudo apt-get install -y graphviz + - name: Install Graphviz and Debug + run: | + # ---- DEBUG - REMOVE AFTER FIXING ---- + echo "=== CACHE STATUS ===" + echo "Cache hit: ${{ steps.cache-graphviz.outputs.cache-hit }}" - - name: Run tests - run: make test + echo "\n=== BEFORE INSTALL ===" + ls -la /usr/bin/ | grep -E "(dot|graphviz)" || echo "No graphviz binaries found" + + if [ "${{ steps.cache-graphviz.outputs.cache-hit }}" != "true" ]; then + echo "\n=== INSTALLING GRAPHVIZ ===" + sudo apt-get update + sudo apt-get install -y graphviz + fi + + echo "\n=== AFTER INSTALL ===" + ls -la /usr/bin/ | grep -E "(dot|graphviz)" || echo "No graphviz binaries found" + which dot && dot -V || echo "dot command not found" + + echo "\n=== ENVIRONMENT DEBUG ===" + echo "Python: $(python --version)" + echo "Poetry: $(poetry --version)" + echo "Virtual env: $VIRTUAL_ENV" + + echo "\n=== PROJECT STRUCTURE ===" + ls -la | head -5 + ls -la leetcode/ | head -5 || echo "No leetcode directory" + # ---- END DEBUG ---- + + - name: Run tests with debug + run: | + # ---- DEBUG - REMOVE AFTER FIXING ---- + echo "=== PRE-TEST DEBUG ===" + make -n test + echo "\n=== RUNNING TESTS ===" + # ---- END DEBUG ---- + make test - name: SonarQube Scan uses: SonarSource/sonarqube-scan-action@1a6d90ebcb0e6a6b1d87e37ba693fe453195ae25 # v5.3.1 From b439f00bcacd5e1e8ff253cea4928d595173bf6e Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 19:11:12 +0700 Subject: [PATCH 27/39] ci: update cache Graphviz --- .github/workflows/ci-test.yml | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index be1a298..273f812 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -33,43 +33,31 @@ jobs: key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} - name: Install dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: poetry install --no-interaction --no-ansi - - name: Cache Graphviz installation - id: cache-graphviz + - name: Cache apt packages + id: cache-apt uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: - path: | - /usr/bin/dot - /usr/bin/neato - /usr/bin/twopi - /usr/bin/circo - /usr/bin/fdp - /usr/bin/sfdp - /usr/bin/patchwork - /usr/bin/osage - /usr/lib/x86_64-linux-gnu/graphviz - /usr/share/graphviz - key: graphviz-${{ runner.os }}-v2 + path: /var/cache/apt/archives + key: apt-${{ runner.os }}-graphviz-v1 + restore-keys: | + apt-${{ runner.os }}-graphviz- - name: Install Graphviz and Debug run: | # ---- DEBUG - REMOVE AFTER FIXING ---- echo "=== CACHE STATUS ===" - echo "Cache hit: ${{ steps.cache-graphviz.outputs.cache-hit }}" + echo "APT Cache hit: ${{ steps.cache-apt.outputs.cache-hit }}" echo "\n=== BEFORE INSTALL ===" - ls -la /usr/bin/ | grep -E "(dot|graphviz)" || echo "No graphviz binaries found" + which dot && echo "dot already exists" || echo "dot not found" - if [ "${{ steps.cache-graphviz.outputs.cache-hit }}" != "true" ]; then - echo "\n=== INSTALLING GRAPHVIZ ===" - sudo apt-get update - sudo apt-get install -y graphviz - fi + echo "\n=== INSTALLING GRAPHVIZ ===" + sudo apt-get update + sudo apt-get install -y graphviz echo "\n=== AFTER INSTALL ===" - ls -la /usr/bin/ | grep -E "(dot|graphviz)" || echo "No graphviz binaries found" which dot && dot -V || echo "dot command not found" echo "\n=== ENVIRONMENT DEBUG ===" From bdb5ef4f670dbff225b526bd78a40604da4496df Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 19:17:44 +0700 Subject: [PATCH 28/39] ci: update cache Graphviz --- .github/workflows/ci-test.yml | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 273f812..1f31570 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -35,27 +35,39 @@ jobs: - name: Install dependencies run: poetry install --no-interaction --no-ansi - - name: Cache apt packages - id: cache-apt + - name: Cache Graphviz installation + id: cache-graphviz uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: - path: /var/cache/apt/archives - key: apt-${{ runner.os }}-graphviz-v1 - restore-keys: | - apt-${{ runner.os }}-graphviz- + path: ~/graphviz-cache + key: graphviz-installed-${{ runner.os }}-v3 - name: Install Graphviz and Debug run: | # ---- DEBUG - REMOVE AFTER FIXING ---- echo "=== CACHE STATUS ===" - echo "APT Cache hit: ${{ steps.cache-apt.outputs.cache-hit }}" + echo "Graphviz Cache hit: ${{ steps.cache-graphviz.outputs.cache-hit }}" echo "\n=== BEFORE INSTALL ===" which dot && echo "dot already exists" || echo "dot not found" - echo "\n=== INSTALLING GRAPHVIZ ===" - sudo apt-get update - sudo apt-get install -y graphviz + if [ "${{ steps.cache-graphviz.outputs.cache-hit }}" = "true" ]; then + echo "\n=== RESTORING CACHED GRAPHVIZ ===" + sudo cp -r ~/graphviz-cache/bin/* /usr/bin/ 2>/dev/null || echo "No binaries to restore" + sudo cp -r ~/graphviz-cache/lib/* /usr/lib/ 2>/dev/null || echo "No libraries to restore" + sudo cp -r ~/graphviz-cache/share/* /usr/share/ 2>/dev/null || echo "No shared files to restore" + sudo ldconfig + else + echo "\n=== INSTALLING GRAPHVIZ ===" + sudo apt-get update + sudo apt-get install -y graphviz + + echo "\n=== CACHING GRAPHVIZ ===" + mkdir -p ~/graphviz-cache/{bin,lib,share} + cp /usr/bin/dot /usr/bin/neato /usr/bin/twopi /usr/bin/circo /usr/bin/fdp /usr/bin/sfdp /usr/bin/patchwork /usr/bin/osage ~/graphviz-cache/bin/ 2>/dev/null || echo "Some binaries not found" + cp -r /usr/lib/x86_64-linux-gnu/*graphviz* ~/graphviz-cache/lib/ 2>/dev/null || echo "No graphviz libs found" + cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null || echo "No graphviz share found" + fi echo "\n=== AFTER INSTALL ===" which dot && dot -V || echo "dot command not found" From 71982dc674b22ebc16d9b745d6aff4e170d5a8cf Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 19:23:25 +0700 Subject: [PATCH 29/39] ci: update cache Graphviz --- .github/workflows/ci-test.yml | 53 +++++++++-------------------------- 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 1f31570..01025e3 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -42,54 +42,29 @@ jobs: path: ~/graphviz-cache key: graphviz-installed-${{ runner.os }}-v3 - - name: Install Graphviz and Debug + - name: Install Graphviz run: | - # ---- DEBUG - REMOVE AFTER FIXING ---- - echo "=== CACHE STATUS ===" - echo "Graphviz Cache hit: ${{ steps.cache-graphviz.outputs.cache-hit }}" - - echo "\n=== BEFORE INSTALL ===" - which dot && echo "dot already exists" || echo "dot not found" - + echo "Cache hit: ${{ steps.cache-graphviz.outputs.cache-hit }}" if [ "${{ steps.cache-graphviz.outputs.cache-hit }}" = "true" ]; then - echo "\n=== RESTORING CACHED GRAPHVIZ ===" - sudo cp -r ~/graphviz-cache/bin/* /usr/bin/ 2>/dev/null || echo "No binaries to restore" - sudo cp -r ~/graphviz-cache/lib/* /usr/lib/ 2>/dev/null || echo "No libraries to restore" - sudo cp -r ~/graphviz-cache/share/* /usr/share/ 2>/dev/null || echo "No shared files to restore" + echo "Restoring from cache" + sudo cp ~/graphviz-cache/bin/* /usr/bin/ 2>/dev/null + sudo cp ~/graphviz-cache/lib/* /usr/lib/x86_64-linux-gnu/ 2>/dev/null + sudo cp -r ~/graphviz-cache/share/graphviz /usr/share/ 2>/dev/null sudo ldconfig else - echo "\n=== INSTALLING GRAPHVIZ ===" + echo "Installing fresh" sudo apt-get update sudo apt-get install -y graphviz - - echo "\n=== CACHING GRAPHVIZ ===" mkdir -p ~/graphviz-cache/{bin,lib,share} - cp /usr/bin/dot /usr/bin/neato /usr/bin/twopi /usr/bin/circo /usr/bin/fdp /usr/bin/sfdp /usr/bin/patchwork /usr/bin/osage ~/graphviz-cache/bin/ 2>/dev/null || echo "Some binaries not found" - cp -r /usr/lib/x86_64-linux-gnu/*graphviz* ~/graphviz-cache/lib/ 2>/dev/null || echo "No graphviz libs found" - cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null || echo "No graphviz share found" + cp /usr/bin/{dot,neato,twopi,circo,fdp,sfdp,patchwork,osage} ~/graphviz-cache/bin/ 2>/dev/null + cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut}* ~/graphviz-cache/lib/ 2>/dev/null + cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null fi + echo "Testing dot command:" + which dot && dot -V || echo "dot failed" - echo "\n=== AFTER INSTALL ===" - which dot && dot -V || echo "dot command not found" - - echo "\n=== ENVIRONMENT DEBUG ===" - echo "Python: $(python --version)" - echo "Poetry: $(poetry --version)" - echo "Virtual env: $VIRTUAL_ENV" - - echo "\n=== PROJECT STRUCTURE ===" - ls -la | head -5 - ls -la leetcode/ | head -5 || echo "No leetcode directory" - # ---- END DEBUG ---- - - - name: Run tests with debug - run: | - # ---- DEBUG - REMOVE AFTER FIXING ---- - echo "=== PRE-TEST DEBUG ===" - make -n test - echo "\n=== RUNNING TESTS ===" - # ---- END DEBUG ---- - make test + - name: Run tests + run: make test - name: SonarQube Scan uses: SonarSource/sonarqube-scan-action@1a6d90ebcb0e6a6b1d87e37ba693fe453195ae25 # v5.3.1 From e402e93cdbecbd5e9ec4eaabf566a9b5c97d2f51 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 19:27:50 +0700 Subject: [PATCH 30/39] ci: update cache Graphviz --- .github/workflows/ci-test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 01025e3..43f1176 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -61,7 +61,11 @@ jobs: cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null fi echo "Testing dot command:" - which dot && dot -V || echo "dot failed" + if which dot; then + dot -V || true + else + echo "dot command not found" + fi - name: Run tests run: make test From 0c1ff936ca9328534fb765553f462af0176e9e73 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 19:33:46 +0700 Subject: [PATCH 31/39] ci: update cache Graphviz --- .github/workflows/ci-test.yml | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 43f1176..1b18aa0 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -47,25 +47,36 @@ jobs: echo "Cache hit: ${{ steps.cache-graphviz.outputs.cache-hit }}" if [ "${{ steps.cache-graphviz.outputs.cache-hit }}" = "true" ]; then echo "Restoring from cache" - sudo cp ~/graphviz-cache/bin/* /usr/bin/ 2>/dev/null - sudo cp ~/graphviz-cache/lib/* /usr/lib/x86_64-linux-gnu/ 2>/dev/null - sudo cp -r ~/graphviz-cache/share/graphviz /usr/share/ 2>/dev/null - sudo ldconfig + echo "Copying binaries..." + sudo cp ~/graphviz-cache/bin/* /usr/bin/ 2>/dev/null || echo "Failed to copy binaries" + echo "Copying libraries..." + sudo cp ~/graphviz-cache/lib/* /usr/lib/x86_64-linux-gnu/ 2>/dev/null || echo "Failed to copy libraries" + echo "Copying share files..." + sudo cp -r ~/graphviz-cache/share/graphviz /usr/share/ 2>/dev/null || echo "Failed to copy share files" + echo "Running ldconfig..." + sudo ldconfig || echo "ldconfig failed" else echo "Installing fresh" + echo "Updating package list..." sudo apt-get update + echo "Installing graphviz..." sudo apt-get install -y graphviz + echo "Creating cache directories..." mkdir -p ~/graphviz-cache/{bin,lib,share} - cp /usr/bin/{dot,neato,twopi,circo,fdp,sfdp,patchwork,osage} ~/graphviz-cache/bin/ 2>/dev/null - cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut}* ~/graphviz-cache/lib/ 2>/dev/null - cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null + echo "Caching binaries..." + cp /usr/bin/{dot,neato,twopi,circo,fdp,sfdp,patchwork,osage} ~/graphviz-cache/bin/ 2>/dev/null || echo "Some binaries not found" + echo "Caching libraries..." + cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut}* ~/graphviz-cache/lib/ 2>/dev/null || echo "Some libraries not found" + echo "Caching share files..." + cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null || echo "Share files not found" fi echo "Testing dot command:" if which dot; then - dot -V || true + dot -V || echo "dot -V failed but dot exists" else echo "dot command not found" fi + echo "Install Graphviz step completed" - name: Run tests run: make test From 94401f5bad842a0accf852590a8ab9f1bbeb3d38 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 19:42:01 +0700 Subject: [PATCH 32/39] ci: update cache Graphviz --- .github/workflows/ci-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 1b18aa0..2bbc0a2 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -55,6 +55,8 @@ jobs: sudo cp -r ~/graphviz-cache/share/graphviz /usr/share/ 2>/dev/null || echo "Failed to copy share files" echo "Running ldconfig..." sudo ldconfig || echo "ldconfig failed" + echo "Registering graphviz plugins..." + sudo dot -c || echo "Plugin registration failed" else echo "Installing fresh" echo "Updating package list..." From 865e00dc74cd56c3b2bad2c1118c7a87cac4a464 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 19:49:06 +0700 Subject: [PATCH 33/39] ci: update cache Graphviz --- .github/workflows/ci-test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 2bbc0a2..8d33868 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -53,6 +53,8 @@ jobs: sudo cp ~/graphviz-cache/lib/* /usr/lib/x86_64-linux-gnu/ 2>/dev/null || echo "Failed to copy libraries" echo "Copying share files..." sudo cp -r ~/graphviz-cache/share/graphviz /usr/share/ 2>/dev/null || echo "Failed to copy share files" + echo "Copying plugin directory..." + sudo cp -r ~/graphviz-cache/lib/graphviz /usr/lib/x86_64-linux-gnu/ 2>/dev/null || echo "Failed to copy plugins" echo "Running ldconfig..." sudo ldconfig || echo "ldconfig failed" echo "Registering graphviz plugins..." @@ -69,6 +71,8 @@ jobs: cp /usr/bin/{dot,neato,twopi,circo,fdp,sfdp,patchwork,osage} ~/graphviz-cache/bin/ 2>/dev/null || echo "Some binaries not found" echo "Caching libraries..." cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut}* ~/graphviz-cache/lib/ 2>/dev/null || echo "Some libraries not found" + echo "Caching plugin directory..." + cp -r /usr/lib/x86_64-linux-gnu/graphviz ~/graphviz-cache/lib/ 2>/dev/null || echo "Plugin directory not found" echo "Caching share files..." cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null || echo "Share files not found" fi From 051d649b3061911a16a78d2730eb06f37d09ae11 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 19:54:20 +0700 Subject: [PATCH 34/39] ci: update cache Graphviz --- .github/workflows/ci-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 8d33868..1f8ee48 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -70,7 +70,7 @@ jobs: echo "Caching binaries..." cp /usr/bin/{dot,neato,twopi,circo,fdp,sfdp,patchwork,osage} ~/graphviz-cache/bin/ 2>/dev/null || echo "Some binaries not found" echo "Caching libraries..." - cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut}* ~/graphviz-cache/lib/ 2>/dev/null || echo "Some libraries not found" + cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut,ann,gts}* ~/graphviz-cache/lib/ 2>/dev/null || echo "Some libraries not found" echo "Caching plugin directory..." cp -r /usr/lib/x86_64-linux-gnu/graphviz ~/graphviz-cache/lib/ 2>/dev/null || echo "Plugin directory not found" echo "Caching share files..." From 7971bef0769318287a4da8723c09943fce54a441 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 20:03:29 +0700 Subject: [PATCH 35/39] ci: update cache Graphviz --- .github/workflows/ci-test.yml | 45 +++++++++-------------------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 1f8ee48..cd2a44f 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -40,49 +40,26 @@ jobs: uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 with: path: ~/graphviz-cache - key: graphviz-installed-${{ runner.os }}-v3 + key: graphviz-installed-${{ runner.os }} - name: Install Graphviz run: | - echo "Cache hit: ${{ steps.cache-graphviz.outputs.cache-hit }}" if [ "${{ steps.cache-graphviz.outputs.cache-hit }}" = "true" ]; then - echo "Restoring from cache" - echo "Copying binaries..." - sudo cp ~/graphviz-cache/bin/* /usr/bin/ 2>/dev/null || echo "Failed to copy binaries" - echo "Copying libraries..." - sudo cp ~/graphviz-cache/lib/* /usr/lib/x86_64-linux-gnu/ 2>/dev/null || echo "Failed to copy libraries" - echo "Copying share files..." - sudo cp -r ~/graphviz-cache/share/graphviz /usr/share/ 2>/dev/null || echo "Failed to copy share files" - echo "Copying plugin directory..." - sudo cp -r ~/graphviz-cache/lib/graphviz /usr/lib/x86_64-linux-gnu/ 2>/dev/null || echo "Failed to copy plugins" - echo "Running ldconfig..." - sudo ldconfig || echo "ldconfig failed" - echo "Registering graphviz plugins..." - sudo dot -c || echo "Plugin registration failed" + sudo cp ~/graphviz-cache/bin/* /usr/bin/ 2>/dev/null + sudo cp ~/graphviz-cache/lib/* /usr/lib/x86_64-linux-gnu/ 2>/dev/null + sudo cp -r ~/graphviz-cache/share/graphviz /usr/share/ 2>/dev/null + sudo cp -r ~/graphviz-cache/lib/graphviz /usr/lib/x86_64-linux-gnu/ 2>/dev/null + sudo ldconfig + sudo dot -c else - echo "Installing fresh" - echo "Updating package list..." sudo apt-get update - echo "Installing graphviz..." sudo apt-get install -y graphviz - echo "Creating cache directories..." mkdir -p ~/graphviz-cache/{bin,lib,share} - echo "Caching binaries..." - cp /usr/bin/{dot,neato,twopi,circo,fdp,sfdp,patchwork,osage} ~/graphviz-cache/bin/ 2>/dev/null || echo "Some binaries not found" - echo "Caching libraries..." - cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut,ann,gts}* ~/graphviz-cache/lib/ 2>/dev/null || echo "Some libraries not found" - echo "Caching plugin directory..." - cp -r /usr/lib/x86_64-linux-gnu/graphviz ~/graphviz-cache/lib/ 2>/dev/null || echo "Plugin directory not found" - echo "Caching share files..." - cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null || echo "Share files not found" + cp /usr/bin/{dot,neato,twopi,circo,fdp,sfdp,patchwork,osage} ~/graphviz-cache/bin/ 2>/dev/null + cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut,ann,gts}* ~/graphviz-cache/lib/ 2>/dev/null + cp -r /usr/lib/x86_64-linux-gnu/graphviz ~/graphviz-cache/lib/ 2>/dev/null + cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null fi - echo "Testing dot command:" - if which dot; then - dot -V || echo "dot -V failed but dot exists" - else - echo "dot command not found" - fi - echo "Install Graphviz step completed" - name: Run tests run: make test From 445d8f2b59453a6144031a952b71b6900bd6b561 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 20:08:17 +0700 Subject: [PATCH 36/39] ci: update cache Graphviz --- .github/workflows/ci-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index cd2a44f..329939c 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -55,10 +55,10 @@ jobs: sudo apt-get update sudo apt-get install -y graphviz mkdir -p ~/graphviz-cache/{bin,lib,share} - cp /usr/bin/{dot,neato,twopi,circo,fdp,sfdp,patchwork,osage} ~/graphviz-cache/bin/ 2>/dev/null - cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut,ann,gts}* ~/graphviz-cache/lib/ 2>/dev/null - cp -r /usr/lib/x86_64-linux-gnu/graphviz ~/graphviz-cache/lib/ 2>/dev/null - cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null + cp /usr/bin/{dot,neato,twopi,circo,fdp,sfdp,patchwork,osage} ~/graphviz-cache/bin/ 2>/dev/null || true + cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut,ann,gts}* ~/graphviz-cache/lib/ 2>/dev/null || true + cp -r /usr/lib/x86_64-linux-gnu/graphviz ~/graphviz-cache/lib/ 2>/dev/null || true + cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null || true fi - name: Run tests From 6bbf9ac0bb1a6536d6ba0a2c993e764a0776225c Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 20:14:05 +0700 Subject: [PATCH 37/39] ci: update cache Graphviz --- .github/workflows/ci-test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 329939c..7526068 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -45,10 +45,10 @@ jobs: - name: Install Graphviz run: | if [ "${{ steps.cache-graphviz.outputs.cache-hit }}" = "true" ]; then - sudo cp ~/graphviz-cache/bin/* /usr/bin/ 2>/dev/null - sudo cp ~/graphviz-cache/lib/* /usr/lib/x86_64-linux-gnu/ 2>/dev/null - sudo cp -r ~/graphviz-cache/share/graphviz /usr/share/ 2>/dev/null - sudo cp -r ~/graphviz-cache/lib/graphviz /usr/lib/x86_64-linux-gnu/ 2>/dev/null + sudo cp ~/graphviz-cache/bin/* /usr/bin/ 2>/dev/null || true + sudo cp ~/graphviz-cache/lib/* /usr/lib/x86_64-linux-gnu/ 2>/dev/null || true + sudo cp -r ~/graphviz-cache/share/graphviz /usr/share/ 2>/dev/null || true + sudo cp -r ~/graphviz-cache/lib/graphviz /usr/lib/x86_64-linux-gnu/ 2>/dev/null || true sudo ldconfig sudo dot -c else From 0564d279a6a28c7b13066f21675c749529fc5426 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 20:22:35 +0700 Subject: [PATCH 38/39] refactor: remove unecessary md files --- .amazonq/plans/find_next_problem.py | 35 ----------------------------- 1 file changed, 35 deletions(-) delete mode 100644 .amazonq/plans/find_next_problem.py diff --git a/.amazonq/plans/find_next_problem.py b/.amazonq/plans/find_next_problem.py deleted file mode 100644 index d91b37d..0000000 --- a/.amazonq/plans/find_next_problem.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 -"""Find next problem to update from json_old to json.""" - -from pathlib import Path - - -def find_next_problem(): - """Return next problem from json_old that's not in json.""" - json_old_dir = Path(".templates/leetcode/json_old") - json_dir = Path(".templates/leetcode/json") - - if not json_old_dir.exists(): - return "json_old directory not found" - - if not json_dir.exists(): - return "json directory not found" - - # Get all problems in json_old - old_problems = {f.stem for f in json_old_dir.glob("*.json")} - - # Get all problems in json - new_problems = {f.stem for f in json_dir.glob("*.json")} - - # Find problems that need updating - missing_problems = old_problems - new_problems - - if not missing_problems: - return "All problems updated!" - - # Return first missing problem (sorted for consistency) - return sorted(missing_problems)[0] - - -if __name__ == "__main__": - print(find_next_problem()) From e2e4704f4bceba188c061e5e16dfe25f2421de70 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Fri, 12 Sep 2025 20:24:31 +0700 Subject: [PATCH 39/39] refactor: remove unecessary md files --- .../plans/migrate_problems_to_new_template.md | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 .amazonq/plans/migrate_problems_to_new_template.md diff --git a/.amazonq/plans/migrate_problems_to_new_template.md b/.amazonq/plans/migrate_problems_to_new_template.md deleted file mode 100644 index 2153a13..0000000 --- a/.amazonq/plans/migrate_problems_to_new_template.md +++ /dev/null @@ -1,30 +0,0 @@ -# Migrate Problems to New Template Format - -Migrate problems from `.templates/leetcode/json_old/` to `.templates/leetcode/json/` using the new template system. - -## Migration Steps - -**Note**: Commands require UI confirmation popup - click "Run" when prompted for `make` commands. - -1. **Create JSON**: Analyze old format, create new template in `.templates/leetcode/json/` (reference `.templates/leetcode/examples/` for format) -2. **Generate**: `make p-gen PROBLEM=` -3. **Lint**: `make p-lint PROBLEM=` (fix JSON if fails, regenerate with `FORCE=1`) -4. **Enhance tests**: Review test coverage - check if cases cover edge scenarios: - - **Check old tests**: Examine `leetcode_old//tests.py` for comprehensive test coverage - - **Update JSON**: Add relevant/generalizable test cases to JSON template (exclude problem-specific error handling) - - Boundary values (min/max constraints, empty inputs) - - Different input sizes/lengths - - Zero/null cases, single elements - - Error conditions, special values - - If only 2-3 basic cases, add comprehensive edge cases (update JSON → regenerate with `FORCE=1` → lint → re-copy solution → test) -5. **Implement**: Look at `leetcode_old/` code - copy solution with all comments/notes - - **Important**: Re-copy solution after `p-gen` since it overwrites with TODO placeholders - - **Multiple solutions**: If old code has alternative implementations (e.g., Solution, SolutionDFS, SolutionBFS), - add parametrize to test all classes (see lru_cache as example) -6. **Test**: `make p-test PROBLEM=` (edit JSON and revalidate for reproducibility if needed) - -## Progress - -```bash -python .amazonq/plans/find_next_problem.py # Next problem or "All problems updated!" -```