From be6f513e91d55445eb1c707765f38a8dc8d64ad5 Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Sat, 30 Aug 2025 22:03:52 +0700 Subject: [PATCH 1/2] feat: add more problems --- .../json/container_with_most_water.json | 42 +++++++ .templates/leetcode/json/spiral_matrix.json | 66 +++++++++++ Makefile | 2 +- README.md | 6 +- leetcode/container_with_most_water/README.md | 40 +++++++ .../container_with_most_water/__init__.py | 0 .../container_with_most_water/example1.png | Bin 0 -> 29310 bytes .../playground.ipynb | 79 +++++++++++++ .../container_with_most_water/solution.py | 17 +++ leetcode/container_with_most_water/tests.py | 28 +++++ leetcode/invert_binary_tree/playground.ipynb | 110 +++++++++++++++++- leetcode/invert_binary_tree/solution.py | 49 +++++++- leetcode/invert_binary_tree/tests.py | 38 +++--- leetcode/reverse_linked_list_ii/solution.py | 28 ++++- leetcode/reverse_linked_list_ii/tests.py | 6 + leetcode/spiral_matrix/README.md | 45 +++++++ leetcode/spiral_matrix/__init__.py | 0 leetcode/spiral_matrix/playground.ipynb | 79 +++++++++++++ leetcode/spiral_matrix/solution.py | 41 +++++++ leetcode/spiral_matrix/tests.py | 51 ++++++++ 20 files changed, 700 insertions(+), 27 deletions(-) create mode 100644 .templates/leetcode/json/container_with_most_water.json create mode 100644 .templates/leetcode/json/spiral_matrix.json 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/example1.png 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/tests.py create mode 100644 leetcode/spiral_matrix/README.md create mode 100644 leetcode/spiral_matrix/__init__.py create mode 100644 leetcode/spiral_matrix/playground.ipynb create mode 100644 leetcode/spiral_matrix/solution.py create mode 100644 leetcode/spiral_matrix/tests.py 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..b974e9c --- /dev/null +++ b/.templates/leetcode/json/container_with_most_water.json @@ -0,0 +1,42 @@ +{ + "problem_name": "container_with_most_water", + "class_name": "ContainerWithMostWater", + "method_name": "max_area", + "problem_number": "11", + "problem_title": "Container With Most Water", + "difficulty": "Medium", + "topics": "Array, Two Pointers, Greedy", + "tags": ["grind-75"], + "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 ith 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.", + "examples": [ + { + "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." + }, + { "input": "height = [1,1]", "output": "1" } + ], + "constraints": "- n == height.length\n- 2 <= n <= 10^5\n- 0 <= height[i] <= 10^4", + "parameters": "height: list[int]", + "return_type": "int", + "dummy_return": "0", + "imports": "", + "test_cases": [ + { "args": [[1, 8, 6, 2, 5, 4, 8, 3, 7]], "expected": 49 }, + { "args": [[1, 1]], "expected": 1 }, + { "args": [[1, 2, 1]], "expected": 2 }, + { "args": [[2, 3, 4, 5, 18, 17, 6]], "expected": 17 }, + { "args": [[1, 2, 4, 3]], "expected": 4 } + ], + "param_names": "height, expected", + "param_names_with_types": "height: list[int], expected: int", + "input_description": "height={height}", + "input_params": "height", + "expected_param": "expected", + "method_args": "height", + "test_setup": "", + "test_logging": "", + "assertion_code": "assert result == expected", + "test_input_setup": "# Example test case\nheight = [1, 8, 6, 2, 5, 4, 8, 3, 7]", + "expected_output_setup": "expected = 49" +} diff --git a/.templates/leetcode/json/spiral_matrix.json b/.templates/leetcode/json/spiral_matrix.json new file mode 100644 index 0000000..d4b7952 --- /dev/null +++ b/.templates/leetcode/json/spiral_matrix.json @@ -0,0 +1,66 @@ +{ + "problem_name": "spiral_matrix", + "class_name": "SpiralMatrix", + "method_name": "spiral_order", + "problem_number": "54", + "problem_title": "Spiral Matrix", + "difficulty": "Medium", + "topics": "Array, Matrix, Simulation", + "tags": ["grind-75"], + "problem_description": "Given an m x n matrix, return all elements of the matrix in spiral order.", + "examples": [ + { "input": "matrix = [[1,2,3],[4,5,6],[7,8,9]]", "output": "[1,2,3,6,9,8,7,4,5]" }, + { + "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- n == matrix[i].length\n- 1 <= m, n <= 10\n- -100 <= matrix[i][j] <= 100", + "parameters": "matrix: list[list[int]]", + "return_type": "list[int]", + "dummy_return": "[]", + "imports": "", + "test_cases": [ + { + "args": [ + [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9] + ] + ], + "expected": [1, 2, 3, 6, 9, 8, 7, 4, 5] + }, + { + "args": [ + [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12] + ] + ], + "expected": [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7] + }, + { "args": [[[1]]], "expected": [1] }, + { + "args": [ + [ + [1, 2], + [3, 4] + ] + ], + "expected": [1, 2, 4, 3] + } + ], + "param_names": "matrix, expected", + "param_names_with_types": "matrix: list[list[int]], expected: list[int]", + "input_description": "matrix={matrix}", + "input_params": "matrix", + "expected_param": "expected", + "method_args": "matrix", + "test_setup": "", + "test_logging": "", + "assertion_code": "assert result == expected", + "test_input_setup": "# Example test case\nmatrix = [[1,2,3],[4,5,6],[7,8,9]]", + "expected_output_setup": "expected = [1,2,3,6,9,8,7,4,5]" +} diff --git a/Makefile b/Makefile index a3a1721..3bc7bbb 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VERSION = 3.13 -PROBLEM ?= insert_interval +PROBLEM ?= spiral_matrix FORCE ?= 0 sync_submodules: diff --git a/README.md b/README.md index 00249b4..ecacdf2 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,8 @@ Premium LeetCode practice repository with Python solutions, algorithm templates, ## 📋 Prerequisites -- Python 3.9+ -- make -- git -- Optional: Graphviz for tree visualizations +- Python 3.13+ +- make, git, Graphviz, poetry ## 🛠️ Installation diff --git a/leetcode/container_with_most_water/README.md b/leetcode/container_with_most_water/README.md new file mode 100644 index 0000000..be54e14 --- /dev/null +++ b/leetcode/container_with_most_water/README.md @@ -0,0 +1,40 @@ +# 11. 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 ith 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: + +![Example1](example1.png) + +``` +Input: height = [1,8,6,2,5,4,8,3,7] +Output: 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/example1.png b/leetcode/container_with_most_water/example1.png new file mode 100644 index 0000000000000000000000000000000000000000..d565b18cf13061971b0dd6f8fabf522cbbfd9ca5 GIT binary patch literal 29310 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%0AXQXlFYKjtk0ibDFrdKr3cVdqUwQl<-qmnJf&IHO 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 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/tests.py b/leetcode/container_with_most_water/tests.py new file mode 100644 index 0000000..751ac27 --- /dev/null +++ b/leetcode/container_with_most_water/tests.py @@ -0,0 +1,28 @@ +import pytest +from loguru import logger + +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", + [ + ([1, 8, 6, 2, 5, 4, 8, 3, 7], 49), + ([1, 1], 1), + ([1, 2, 1], 2), + ([2, 3, 4, 5, 18, 17, 6], 17), + ([1, 2, 4, 3], 4), + ], + ) + @logged_test + def test_max_area(self, height: list[int], expected: int): + logger.info(f"Testing with height={height}") + result = self.solution.max_area(height) + logger.success(f"Got result: {result}") + assert result == expected diff --git a/leetcode/invert_binary_tree/playground.ipynb b/leetcode/invert_binary_tree/playground.ipynb index 3ba0c7d..62707c1 100644 --- a/leetcode/invert_binary_tree/playground.ipynb +++ b/leetcode/invert_binary_tree/playground.ipynb @@ -17,11 +17,115 @@ "execution_count": 2, "id": "setup", "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", + "2\n", + "\n", + "\n", + "\n", + "0->1\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "4\n", + "\n", + "7\n", + "\n", + "\n", + "\n", + "0->4\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "2\n", + "\n", + "1\n", + "\n", + "\n", + "\n", + "1->2\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "3\n", + "\n", + "3\n", + "\n", + "\n", + "\n", + "1->3\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "5\n", + "\n", + "6\n", + "\n", + "\n", + "\n", + "4->5\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "6\n", + "\n", + "9\n", + "\n", + "\n", + "\n", + "4->6\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "TreeNode([4, 2, 7, 1, 3, 6, 9])" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Example test case\n", "root = TreeNode.from_list([4, 2, 7, 1, 3, 6, 9])\n", - "expected = TreeNode.from_list([4, 7, 2, 9, 6, 3, 1])" + "expected = TreeNode.from_list([4, 7, 2, 9, 6, 3, 1])\n", + "root" ] }, { @@ -163,7 +267,7 @@ "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/invert_binary_tree/solution.py b/leetcode/invert_binary_tree/solution.py index 5df0b83..3a6481e 100644 --- a/leetcode/invert_binary_tree/solution.py +++ b/leetcode/invert_binary_tree/solution.py @@ -1,12 +1,59 @@ +from collections import deque + from leetcode_py.tree_node 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) + # Space: O(h) where h is height of tree def invert_tree(self, root: TreeNode | None) -> TreeNode | 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 | None) -> TreeNode | None: + if not root: + return None + + stack: list[TreeNode | 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 | None) -> TreeNode | None: + if not root: + return None + + queue: deque[TreeNode | 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/invert_binary_tree/tests.py index 4d2ba0a..71c84d6 100644 --- a/leetcode/invert_binary_tree/tests.py +++ b/leetcode/invert_binary_tree/tests.py @@ -4,26 +4,34 @@ from leetcode_py.test_utils import logged_test from leetcode_py.tree_node import TreeNode -from .solution import Solution +from .solution import Solution, SolutionBFS, SolutionDFS +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]), +] -class TestInvertBinaryTree: - def setup_method(self): - self.solution = Solution() - @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]), - ([], []), - ], - ) +class TestInvertBinaryTree: + @pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS]) + @pytest.mark.parametrize("root_list, expected_list", test_cases) @logged_test - def test_invert_tree(self, root_list: list[int | None], expected_list: list[int | None]): - logger.info(f"Testing with root_list={root_list}") + def test_invert_tree( + self, + solution_class: type[Solution | SolutionDFS | SolutionBFS], + root_list: list[int | None], + expected_list: list[int | None], + ): + solution = solution_class() + logger.info(f"Testing {solution_class.__name__} with root_list={root_list}") root = TreeNode.from_list(root_list) expected = TreeNode.from_list(expected_list) - result = self.solution.invert_tree(root) + result = solution.invert_tree(root) logger.success(f"Got result: {result.to_list() if result else []}") assert result == expected diff --git a/leetcode/reverse_linked_list_ii/solution.py b/leetcode/reverse_linked_list_ii/solution.py index 2b7e88b..0830bd0 100644 --- a/leetcode/reverse_linked_list_ii/solution.py +++ b/leetcode/reverse_linked_list_ii/solution.py @@ -17,14 +17,36 @@ def reverse_between(self, head: ListNode | None, left: int, right: int) -> ListN assert prev.next prev = prev.next - # Reverse from left to right + # 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 + 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 + 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/tests.py b/leetcode/reverse_linked_list_ii/tests.py index 23eb3b7..31c2e8b 100644 --- a/leetcode/reverse_linked_list_ii/tests.py +++ b/leetcode/reverse_linked_list_ii/tests.py @@ -17,6 +17,12 @@ def setup_method(self): ([1, 2, 3, 4, 5], 2, 4, [1, 4, 3, 2, 5]), ([5], 1, 1, [5]), ([1, 2, 3], 1, 3, [3, 2, 1]), + ([1, 2], 1, 2, [2, 1]), + ([7, 3, 9, 2, 8], 1, 5, [8, 2, 9, 3, 7]), + ([4, 6, 1, 9, 3], 3, 3, [4, 6, 1, 9, 3]), + ([2, 8, 5, 1, 7, 4], 2, 5, [2, 7, 1, 5, 8, 4]), + ([9, 5, 2, 6], 1, 1, [9, 5, 2, 6]), + ([3, 7, 1, 8], 4, 4, [3, 7, 1, 8]), ], ) @logged_test diff --git a/leetcode/spiral_matrix/README.md b/leetcode/spiral_matrix/README.md new file mode 100644 index 0000000..65ecc16 --- /dev/null +++ b/leetcode/spiral_matrix/README.md @@ -0,0 +1,45 @@ +# 54. 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: + +``` +1 → 2 → 3 + ↓ +4 → 5 6 +↑ ↓ +7 ← 8 ← 9 + +Input: matrix = [[1,2,3],[4,5,6],[7,8,9]] +Output: [1,2,3,6,9,8,7,4,5] +``` + +### Example 2: + +``` +1 → 2 → 3 → 4 + ↓ +5 → 6 → 7 8 +↑ ↓ +9 ← 10← 11← 12 + +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/playground.ipynb b/leetcode/spiral_matrix/playground.ipynb new file mode 100644 index 0000000..bbf61a8 --- /dev/null +++ b/leetcode/spiral_matrix/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", + "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": 3, + "id": "execute", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 2, 3, 6, 9, 8, 7, 4, 5]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = Solution().spiral_order(matrix)\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/spiral_matrix/solution.py b/leetcode/spiral_matrix/solution.py new file mode 100644 index 0000000..65e1113 --- /dev/null +++ b/leetcode/spiral_matrix/solution.py @@ -0,0 +1,41 @@ +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/tests.py b/leetcode/spiral_matrix/tests.py new file mode 100644 index 0000000..ee7741b --- /dev/null +++ b/leetcode/spiral_matrix/tests.py @@ -0,0 +1,51 @@ +import pytest +from loguru import logger + +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", + [ + ([[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], [3, 4]], [1, 2, 4, 3]), + ([[1, 2, 3]], [1, 2, 3]), + ([[1], [2], [3]], [1, 2, 3]), + ([[1, 2], [3, 4], [5, 6]], [1, 2, 4, 6, 5, 3]), + ([[1, 2, 3, 4, 5]], [1, 2, 3, 4, 5]), + ([[1], [2], [3], [4], [5]], [1, 2, 3, 4, 5]), + ([[1, 2, 3, 4], [5, 6, 7, 8]], [1, 2, 3, 4, 8, 7, 6, 5]), + ], + ) + @logged_test + def test_spiral_order(self, matrix: list[list[int]], expected: list[int]): + logger.info(f"Testing with matrix={matrix}") + result = self.solution.spiral_order(matrix) + logger.success(f"Got result: {result}") + assert result == expected + + +class TestSpiralMatrixInvalid: + def setup_method(self): + self.solution = Solution() + + @pytest.mark.parametrize( + "matrix", + [ + [[1, 2, 3], [4, 5], [6, 7, 8]], + [[1], [2, 3], [4, 5, 6]], + [[1, 2], [3, 4, 5]], + [[1, 2, 3, 4], [5, 6]], + ], + ) + def test_invalid_matrix(self, matrix: list[list[int]]): + with pytest.raises(ValueError, match="Invalid matrix: all rows must have same length"): + self.solution.spiral_order(matrix) From 3b90e99d33ef5b89bd55497c57cd0ef0e2f1952a Mon Sep 17 00:00:00 2001 From: Wisaroot Lertthaweedech Date: Sat, 30 Aug 2025 22:05:54 +0700 Subject: [PATCH 2/2] test: add more test --- leetcode/spiral_matrix/tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/leetcode/spiral_matrix/tests.py b/leetcode/spiral_matrix/tests.py index ee7741b..72f6660 100644 --- a/leetcode/spiral_matrix/tests.py +++ b/leetcode/spiral_matrix/tests.py @@ -23,6 +23,8 @@ def setup_method(self): ([[1, 2, 3, 4, 5]], [1, 2, 3, 4, 5]), ([[1], [2], [3], [4], [5]], [1, 2, 3, 4, 5]), ([[1, 2, 3, 4], [5, 6, 7, 8]], [1, 2, 3, 4, 8, 7, 6, 5]), + ([], []), + ([[]], []), ], ) @logged_test