diff --git a/.amazonq/plans/02-cookiecutter-template-improvement.md b/.amazonq/plans/02-cookiecutter-template-improvement.md
new file mode 100644
index 0000000..acc78a5
--- /dev/null
+++ b/.amazonq/plans/02-cookiecutter-template-improvement.md
@@ -0,0 +1,266 @@
+# Plan: Improve Cookiecutter Template for LeetCode Problems
+
+## Overview
+
+Improve the cookiecutter template to be more general and cover all LeetCode question types, including special cases like LRU Cache that use custom class names and multiple methods.
+
+## Current Issues Identified
+
+1. **Hard-coded class name**: Template assumes `Solution` class but some problems use custom classes (e.g., `LRUCache`)
+2. **Single method assumption**: Template assumes one method but some problems have multiple methods (`__init__`, `get`, `put`)
+3. **Complex test setup**: Current template doesn't handle operation-based testing for design problems
+4. **Import handling**: Need better solution import structure
+5. **Template parameter explosion**: Too many derived parameters that could be simplified
+
+## Target Structure
+
+```
+.templates/leetcode/
+├── {{cookiecutter.problem_name}}/
+│ ├── __init__.py
+│ ├── solution.py
+│ ├── tests.py
+│ ├── README.md
+│ └── playground.ipynb
+└── cookiecutter.json
+```
+
+## Phase 1: Analyze Problem Types
+
+### 1.1 Universal Template Variables
+
+- `solution_imports`: Required imports for solution.py (empty, TreeNode, ListNode, etc.)
+- `test_imports`: All import lines for tests.py (pytest, loguru, TreeNode, test_utils, solution class)
+- `solution_class_name`: Dynamic class name (Solution, LRUCache, etc.)
+- `readme_description`: Problem description for README (supports HTML including images, preserves code snippets like `x`, `y`, `some_params`)
+- `readme_examples`: Examples for README (supports HTML including images, typically uses code blocks for input/output)
+- `readme_constraints`: Constraints for README
+- `readme_additional`: Additional README sections
+- `solution_methods`: List of methods with parameters/return types
+- `test_methods`: List of test methods with parametrize and body
+- `test_helper_methods`: List of test helper methods (setup_method, teardown_method, utility methods, etc.)
+
+## Phase 2: Create New cookiecutter.json
+
+### 2.1 Complete Template Variables Example
+
+````json
+{
+ "problem_name": "two_sum",
+ "solution_class_name": "Solution",
+ "problem_number": "1",
+ "problem_title": "Two Sum",
+ "difficulty": "Easy",
+ "topics": "Array, Hash Table",
+ "tags": ["grind-75"],
+
+ "readme_description": "Given an array of integers `nums` and an integer `target`, return indices of the two numbers such that they add up to `target`.\n\n
\n\nYou may assume that each input would have exactly one solution, and you may not use the same element twice.",
+ "readme_examples": [
+ {
+ "content": "```\nInput: nums = [2,7,11,15], target = 9\nOutput: [0,1]\n```\n**Explanation:** Because nums[0] + nums[1] == 9, we return [0, 1]."
+ },
+ {
+ "content": "
\n\n```\nInput: root = [4,2,7,1,3,6,9]\nOutput: [4,7,2,9,6,3,1]\n```\n**Explanation:** The tree is inverted as shown in the image above."
+ }
+ ],
+ "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": "",
+
+ "solution_imports": "",
+ "solution_methods": [
+ {
+ "name": "two_sum",
+ "parameters": "nums: list[int], target: int",
+ "return_type": "list[int]",
+ "dummy_return": "[]"
+ }
+ ],
+
+ "test_imports": "import pytest\nfrom loguru import logger\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "test_helper_methods": [
+ {
+ "name": "setup_method",
+ "parameters": "",
+ "body": "self.solution = Solution()"
+ }
+ ],
+ "test_methods": [
+ {
+ "name": "test_two_sum",
+ "parametrize": "nums, target, expected",
+ "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"
+ }
+ ],
+
+ "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"
+}
+````
+
+## Phase 3: Update Template Files
+
+### 3.1 solution.py Template
+
+```python
+{{cookiecutter.solution_imports}}
+
+class {{cookiecutter.solution_class_name}}:
+ {% for method in cookiecutter.solution_methods %}
+ # Time: O(?)
+ # Space: O(?)
+ def {{method.name}}(self, {{method.parameters}}) -> {{method.return_type}}:
+ # TODO: Implement {{method.name}}
+ return {{method.dummy_return}}
+
+ {% endfor %}
+```
+
+### 3.2 tests.py Template
+
+```python
+{{cookiecutter.test_imports}}
+
+class Test{{cookiecutter.solution_class_name}}:
+ {% for method in cookiecutter.test_helper_methods %}
+ def {{method.name}}(self{% if method.parameters %}, {{method.parameters}}{% endif %}):
+ {{method.body}}
+
+ {% endfor %}
+
+ {% for method in cookiecutter.test_methods %}
+ @pytest.mark.parametrize("{{method.parametrize}}", {{method.test_cases}})
+ @logged_test
+ def {{method.name}}(self, {{method.parametrize}}):
+ {{method.body}}
+
+ {% endfor %}
+```
+
+### 3.3 README.md Template
+
+```markdown
+# {{cookiecutter.problem_title}}
+
+**Difficulty:** {{cookiecutter.difficulty}}
+**Topics:** {{cookiecutter.topics}}
+**Tags:** {{cookiecutter.tags | join(', ')}}
+{% if cookiecutter.problem_number %}
+**LeetCode:** [Problem {{cookiecutter.problem_number}}](https://leetcode.com/problems/{{cookiecutter.problem_name.replace('_', "-")}}/description/)
+{% endif %}
+
+## Problem Description
+
+{{cookiecutter.readme_description}}
+
+## Examples
+
+{% for example in cookiecutter.readme_examples %}
+
+### Example {{loop.index}}:
+
+{{example.content}}
+{% endfor %}
+
+## Constraints
+
+{{cookiecutter.readme_constraints}}
+
+{% if cookiecutter.readme_additional %}
+{{cookiecutter.readme_additional}}
+{% endif %}
+```
+
+### 3.4 playground.ipynb Template
+
+```json
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "id": "imports",
+ "source": ["{{cookiecutter.playground_imports}}"]
+ },
+ {
+ "cell_type": "code",
+ "id": "setup",
+ "source": ["{{cookiecutter.playground_test_case}}"]
+ },
+ {
+ "cell_type": "code",
+ "id": "execute",
+ "source": ["{{cookiecutter.playground_execution}}"]
+ },
+ {
+ "cell_type": "code",
+ "id": "test",
+ "source": ["{{cookiecutter.playground_assertion}}"]
+ }
+ ]
+}
+```
+
+## Phase 4: Simplify Parameter Generation
+
+### 4.1 Reduce Template Complexity
+
+- Remove derived parameters like `param_names`, `input_description`, etc.
+- Generate these dynamically in the template using Jinja2 filters
+- Focus on core data: methods, test_cases, problem metadata
+
+### 4.2 Smart Defaults
+
+- Auto-generate method names from problem names
+- Auto-generate class names from problem names
+- Default to "basic" problem type unless specified
+
+## Phase 5: Testing & Validation
+
+### 5.1 Test with Existing Problems
+
+- Generate all current problems using new template
+- Compare output with existing generated files
+- Ensure no regression in functionality
+
+### 5.2 Test Special Cases
+
+- LRU Cache (design problem)
+- Tree problems (TreeNode imports)
+- Linked list problems (ListNode imports)
+- Matrix problems (2D arrays)
+
+## Phase 6: Integration
+
+### 6.1 Update Generation Scripts
+
+- Modify `gen.py` to work with new template structure
+- Update Makefile commands
+- Ensure backward compatibility with existing JSON files
+
+### 6.2 Documentation
+
+- Update problem creation guide
+- Create examples for each problem type
+- Document new template variables
+
+## Success Criteria
+
+1. ✅ Single cookiecutter template handles all problem types
+2. ✅ Reduced template complexity (fewer derived parameters)
+3. ✅ Support for design problems with multiple methods
+4. ✅ Proper imports for tree/linked list problems
+5. ✅ Clean, maintainable template structure
+6. ✅ All existing problems can be regenerated without issues
+7. ✅ New problem types can be easily added
+
+## Implementation Order
+
+1. Create new `cookiecutter.json` structure
+2. Update template files with conditional logic
+3. Test with basic problems (Two Sum)
+4. Test with design problems (LRU Cache)
+5. Test with tree/linked list problems
+6. Validate all existing problems
+7. Update generation scripts and documentation
diff --git a/.amazonq/plans/03-regenerate-all-problems.md b/.amazonq/plans/03-regenerate-all-problems.md
new file mode 100644
index 0000000..e993f26
--- /dev/null
+++ b/.amazonq/plans/03-regenerate-all-problems.md
@@ -0,0 +1,228 @@
+# Plan: Regenerate All Problems with Fresh LeetCode Data
+
+## Overview
+
+Scrape fresh data from LeetCode for existing problems and generate new JSON files using the new universal cookiecutter template, ensuring all generated code passes linting.
+
+## Current State Analysis
+
+- Old JSON files: `.templates/leetcode/old/template/json/*.json` (for problem names only)
+- Scraper: `.templates/leetcode/scrape.py` (existing)
+- New template: `.templates/leetcode/cookiecutter.json` + templates
+- Target: `.templates/leetcode/json/*.json` (new format with fresh data)
+- Generated problems: `leetcode/*/`
+
+## Phase 1: Extract Problem Names
+
+### 1.1 Get Problem Names from Old JSON
+
+```bash
+ls .templates/leetcode/old/template/json/
+```
+
+Expected problems:
+
+- container_with_most_water.json
+- insert_interval.json
+- invert_binary_tree.json
+- lru_cache.json
+- reverse_linked_list_ii.json
+- spiral_matrix.json
+
+### 1.2 Extract Problem Numbers/Slugs
+
+From old JSON files, extract:
+
+- Problem numbers (for scraping by number)
+- Problem names/slugs (for scraping by slug)
+
+## Phase 2: Create Fresh Scraping Script
+
+### 2.1 Use Existing Scraper
+
+Following the problem creation rules:
+
+```bash
+# Fetch by number
+poetry run python .templates/leetcode/scrape.py -n 1
+
+# Fetch by slug
+poetry run python .templates/leetcode/scrape.py -s "two-sum"
+```
+
+### 2.2 Scraping Script: `scrape_all_problems.py`
+
+✅ **Created** - Uses existing scraper to fetch fresh data for all problems
+
+## Phase 3: Execute Fresh Scraping
+
+### 3.1 Run Scraping Script
+
+```bash
+cd .templates/leetcode
+python scrape_all_problems.py
+```
+
+### 3.2 Transform JSON to New Template Format
+
+The scraper creates JSON in new template format automatically.
+
+### 3.3 Verify New JSON Files
+
+```bash
+ls .templates/leetcode/json/
+# Should show all freshly scraped JSON files
+```
+
+## Phase 4: Test Generation Pipeline
+
+### 4.1 Test Single Problem Generation
+
+```bash
+make p-gen PROBLEM=container_with_most_water FORCE=1
+```
+
+### 4.2 Verify Generated Structure
+
+```bash
+ls leetcode/container_with_most_water/
+# Should show: __init__.py, solution.py, tests.py, README.md, playground.ipynb
+```
+
+### 4.3 Test Linting on Single Problem
+
+```bash
+make lint
+# Should pass without errors
+```
+
+## Phase 5: Full Regeneration
+
+### 5.1 Update Makefile (if needed)
+
+Verify `gen-all-problems` target works with new template:
+
+```makefile
+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
+ @echo "Deleting existing problems..."
+ @rm -rf leetcode/*/
+ @echo "Generating all problems..."
+ @for json_file in .templates/leetcode/json/*.json; do \
+ problem=$$(basename "$$json_file" .json); \
+ echo "Generating: $$problem"; \
+ poetry run python .templates/leetcode/gen.py "$$json_file" --force; \
+ done
+```
+
+### 5.2 Run Full Regeneration
+
+```bash
+make gen-all-problems FORCE=1
+```
+
+### 5.3 Verify All Problems Generated
+
+```bash
+ls leetcode/
+# Should show all problem directories
+```
+
+## Phase 6: Validation & Iteration
+
+### 6.1 Run Linting on All Problems
+
+```bash
+make lint
+```
+
+### 6.2 Fix Issues (Iterative Process)
+
+**If linting fails:**
+
+1. **Identify Issue Type:**
+ - Template formatting issues → Fix template files
+ - JSON data issues → Re-scrape or manually fix JSON
+ - Import issues → Fix template imports
+
+2. **Fix and Regenerate:**
+
+ ```bash
+ # Fix issue in template or JSON
+ make gen-all-problems FORCE=1
+ make lint
+ ```
+
+3. **Repeat until clean:**
+ - Continue iteration until `make lint` passes
+ - Ensure reproducible generation
+
+### 6.3 Common Issues & Fixes
+
+**Template Issues:**
+
+- Indentation problems → Fix Jinja2 `indent` filters in templates
+- Missing imports → Update template import generation
+- Method signatures → Fix solution_methods in JSON
+
+**JSON Issues:**
+
+- Missing required fields → Re-scrape with updated scraper
+- Incorrect data types → Fix scraper output format
+- Test case format → Update scraper test case generation
+
+## Phase 7: Validation Testing
+
+### 7.1 Test Individual Problems
+
+```bash
+make p-test PROBLEM=container_with_most_water
+make p-test PROBLEM=lru_cache
+make p-test PROBLEM=invert_binary_tree
+```
+
+### 7.2 Run Full Test Suite
+
+```bash
+make test
+```
+
+### 7.3 Verify Notebooks Work
+
+- Check playground.ipynb files can be opened
+- Verify cell content is properly formatted
+
+## Success Criteria
+
+1. ✅ All old JSON files migrated to new format
+2. ✅ `make gen-all-problems` works with new template
+3. ✅ All generated problems pass `make lint`
+4. ✅ All generated problems pass `make test`
+5. ✅ Generation is reproducible (no manual fixes needed)
+6. ✅ All problem types work (basic, design, tree, linked list)
+
+## Implementation Order
+
+1. **Create scraping script** - Get fresh data from LeetCode
+2. **Run fresh scraping** - Generate new JSON files
+3. **Test single problem** - Verify pipeline works
+4. **Fix template issues** - Ensure clean generation
+5. **Run full regeneration** - Generate all problems
+6. **Iterate on linting** - Fix issues until clean
+7. **Final validation** - Test all problems work
+
+## Rollback Plan
+
+If issues arise:
+
+1. Keep old template in `.templates/leetcode/old/`
+2. Can revert to old generation method
+3. New template is additive, doesn't break existing workflow
+
+## Notes
+
+- Focus on reproducible generation without manual intervention
+- Prioritize linting compliance over perfect formatting
+- Document any template limitations discovered during migration
+- Ensure backward compatibility with existing JSON structure
diff --git a/.amazonq/plans/cookiecutter-template-plan.md b/.amazonq/plans/cookiecutter-template-plan.md
index 8e18a7c..ae762c2 100644
--- a/.amazonq/plans/cookiecutter-template-plan.md
+++ b/.amazonq/plans/cookiecutter-template-plan.md
@@ -157,7 +157,7 @@
"constraints": "Formatted constraints",
"parameters": "typed_params: list[int]",
"return_type": "TreeNode | None",
- "imports": "from leetcode_py.tree_node import TreeNode",
+ "imports": "from leetcode_py import TreeNode",
"test_cases": [{"args": [...], "expected": ...}]
}
```
diff --git a/.amazonq/rules/problem-creation.md b/.amazonq/rules/problem-creation.md
index 9407ef3..0cc55af 100644
--- a/.amazonq/rules/problem-creation.md
+++ b/.amazonq/rules/problem-creation.md
@@ -77,13 +77,13 @@ Required fields for `.templates/leetcode/json/{problem_name}.json`:
### Tree Problems
-- Add `"imports": "from leetcode_py.tree_node import TreeNode"`
+- Add `"imports": "from leetcode_py import TreeNode"`
- Use `TreeNode | None` for nullable tree parameters
- Test setup: `root = TreeNode.from_list(root_list)`
### Linked List Problems
-- Add `"imports": "from leetcode_py.list_node import ListNode"`
+- Add `"imports": "from leetcode_py import ListNode"`
- Use `ListNode | None` for nullable list parameters
- Test setup: `head = ListNode.from_list(head_list)`
diff --git a/.templates/leetcode/cookiecutter.json b/.templates/leetcode/cookiecutter.json
index e44999d..bf9c13a 100644
--- a/.templates/leetcode/cookiecutter.json
+++ b/.templates/leetcode/cookiecutter.json
@@ -1,41 +1,59 @@
{
"problem_name": "two_sum",
- "class_name": "TwoSum",
- "method_name": "two_sum",
+ "solution_class_name": "Solution",
"problem_number": "1",
"problem_title": "Two Sum",
"difficulty": "Easy",
"topics": "Array, Hash Table",
"_tags": { "list": ["grind-75"] },
- "problem_description": "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.",
- "_examples": {
+
+ "readme_description": "Given an array of integers `nums` and an integer `target`, return indices of the two numbers such that they add up to `target`.",
+ "_readme_examples": {
"list": [
- { "input": "nums = [2,7,11,15], target = 9", "output": "[0,1]" },
- { "input": "nums = [3,2,4], target = 6", "output": "[1,2]" },
- { "input": "nums = [3,3], target = 6", "output": "[0,1]" }
+ {
+ "content": "```\nInput: nums = [2,7,11,15], target = 9\nOutput: [0,1]\n```\n**Explanation:** Because nums[0] + nums[1] == 9, we return [0, 1]."
+ }
]
},
- "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.",
- "parameters": "nums: list[int], target: int",
- "return_type": "list[int]",
- "dummy_return": "[]",
- "imports": "",
- "test_setup": "",
- "test_logging": "",
- "_test_cases": {
+ "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": "",
+
+ "solution_imports": "",
+ "_solution_methods": {
"list": [
- { "args": [[2, 7, 11, 15], 9], "expected": [0, 1] },
- { "args": [[3, 2, 4], 6], "expected": [1, 2] },
- { "args": [[3, 3], 6], "expected": [0, 1] }
+ {
+ "name": "two_sum",
+ "parameters": "nums: list[int], target: int",
+ "return_type": "list[int]",
+ "dummy_return": "[]"
+ }
]
},
- "param_names": "nums, target, expected",
- "param_names_with_types": "nums: list[int], target: int, expected: list[int]",
- "input_description": "nums={nums}, target={target}",
- "input_params": "nums, target",
- "expected_param": "expected",
- "method_args": "nums, target",
- "test_input_setup": "nums = [2, 7, 11, 15]\ntarget = 9",
- "expected_output_setup": "expected = [0, 1]",
- "assertion_code": "assert result == expected"
+
+ "test_imports": "import pytest\nfrom loguru import logger\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "_test_helper_methods": {
+ "list": [
+ {
+ "name": "setup_method",
+ "parameters": "",
+ "body": "self.solution = Solution()"
+ }
+ ]
+ },
+ "_test_methods": {
+ "list": [
+ {
+ "name": "test_two_sum",
+ "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"
+ }
+ ]
+ },
+
+ "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"
}
diff --git a/.templates/leetcode/examples/README.md b/.templates/leetcode/examples/README.md
index 0f59d7c..2678b3a 100644
--- a/.templates/leetcode/examples/README.md
+++ b/.templates/leetcode/examples/README.md
@@ -1,71 +1,109 @@
-# LeetCode Template Examples
+# LeetCode Problem Template Examples
-Reference templates for creating new LeetCode problems. **Copy from these examples** - don't create from scratch.
+This directory contains comprehensive JSON5 template examples for different types of LeetCode problems. These examples serve as references when creating new problems using the universal cookiecutter template.
-## Usage
+## Template Types
-1. **Choose the right template** based on problem type
-2. **Copy the entire structure** to `.templates/leetcode/json/{problem_name}.json`
-3. **Update all fields** with your problem's data
-4. **Generate**: `make p-gen PROBLEM=your_problem`
+### 1. `basic.json5` - Basic Algorithm Problems
-## Templates
+**Use for:** Array, string, number, hash table problems
+**Examples:** Container With Most Water, Two Sum, Valid Palindrome
+**Key features:**
-### `basic.json5`
+- Simple `Solution` class with single method
+- Standard test parametrization
+- Basic playground setup
-- **Use for**: Array, string, number, hash table problems
-- **Examples**: Two Sum, Valid Anagram, Contains Duplicate
-- **Features**: Simple parameters, direct assertions
+### 2. `tree.json5` - Binary Tree Problems
-### `tree.json5`
+**Use for:** Binary tree, BST, tree traversal problems
+**Examples:** Invert Binary Tree, Maximum Depth, Serialize Tree
+**Key features:**
-- **Use for**: Binary tree problems
-- **Examples**: Invert Binary Tree, Maximum Depth, Same Tree
-- **Features**: TreeNode import, array-to-tree conversion, tree logging
+- `TreeNode` imports and conversions
+- `TreeNode.from_list()` and `TreeNode.to_list()` in tests
+- Tree visualization support
-### `linked_list.json5`
+### 3. `linked_list.json5` - Linked List Problems
-- **Use for**: Linked list problems
-- **Examples**: Reverse Linked List, Merge Two Lists, Cycle Detection
-- **Features**: ListNode import, array-to-list conversion, list logging
+**Use for:** Singly/doubly linked list problems
+**Examples:** Reverse Linked List, Merge Lists, Detect Cycle
+**Key features:**
-### `string.json5`
+- `ListNode` imports and conversions
+- `ListNode.from_list()` and `ListNode.to_list()` in tests
+- Arrow visualization support
-- **Use for**: String manipulation problems
-- **Examples**: Valid Palindrome, Longest Substring, Anagrams
-- **Features**: String parameters, boolean/string returns
+### 4. `design.json5` - Data Structure Design Problems
-### `matrix.json5`
+**Use for:** Design problems requiring custom classes
+**Examples:** LRU Cache, Implement Trie, Design HashMap
+**Key features:**
-- **Use for**: 2D array/matrix problems
-- **Examples**: Rotate Image, Spiral Matrix, Set Matrix Zeroes
-- **Features**: Matrix parameters, in-place operation testing
+- Custom class names (not `Solution`)
+- Multiple methods including `__init__`
+- Complex operation sequence testing
+- Type annotations for complex test logic
-## Key Fields
+### 5. `matrix.json5` - 2D Array/Matrix Problems
-### Required Core Fields
+**Use for:** Matrix manipulation, 2D array problems
+**Examples:** Spiral Matrix, Rotate Image, Search 2D Matrix
+**Key features:**
-- `problem_name`, `class_name`, `method_name`
-- `problem_number`, `problem_title`, `difficulty`, `topics`
-- `problem_description`, `examples`, `constraints`
-- `parameters`, `return_type`, `dummy_return`
+- 2D array type annotations (`list[list[int]]`)
+- Visual examples with images
+- Matrix-specific test cases
-### Test Configuration
+## Usage Guidelines
-- `test_cases`: Array of `{args, expected}` objects
-- `param_names`: Parameter names for test methods
-- `test_setup`: Code to convert test data (e.g., arrays to TreeNode)
-- `assertion_code`: How to compare result with expected
+### Problem Type Detection
-### Notebook Setup
+1. **Basic**: Single algorithm, simple input/output
+2. **Tree**: Mentions "tree", "node", uses tree terminology
+3. **Linked List**: Mentions "linked list", "node", list operations
+4. **Design**: "Design", "Implement", multiple operations
+5. **Matrix**: "matrix", "2D array", "grid", visual layout
-- `test_input_setup`: Code for notebook input cell
-- `expected_output_setup`: Code for notebook expected cell
-- `imports`: Required imports (TreeNode, ListNode, etc.)
+### Key Template Fields
-## Rules
+#### Required Fields
-1. **Copy structure exactly** - all fields are required
-2. **Use modern Python syntax**: `list[int]`, `TreeNode | None`
-3. **Match existing patterns** - see current JSON files for reference
-4. **Test thoroughly** - run `make lint` and `make p-test` after generation
+- `problem_name`: snake_case identifier
+- `solution_class_name`: "Solution" or custom class name
+- `problem_number`: LeetCode number as string
+- `problem_title`: Exact LeetCode title
+- `difficulty`: "Easy", "Medium", or "Hard"
+- `topics`: Comma-separated topic string
+- `solution_methods`: Array of method definitions
+
+#### Important Patterns
+
+- **Type Hints**: Use modern syntax (`list[int]`, `dict[str, int]`, `Type | None`)
+- **Method Names**: Always snake_case
+- **Test Cases**: String representation of Python data structures
+- **Imports**: Include necessary helper classes (TreeNode, ListNode)
+
+### Template Selection Process
+
+1. Identify problem type from description/title
+2. Choose appropriate template from examples
+3. Customize fields for specific problem
+4. Ensure imports match problem requirements
+5. Verify test setup matches data structures used
+
+## Validation
+
+All templates are validated against:
+
+- Cookiecutter template compatibility
+- Linting requirements (black, isort, ruff, mypy)
+- Test framework integration
+- Notebook JSON format compliance
+
+## Notes
+
+- JSON5 format allows comments for documentation
+- All examples are based on working, tested templates
+- Templates are designed for the universal cookiecutter system
+- Examples include both simple and complex problem patterns
diff --git a/.templates/leetcode/examples/basic.json5 b/.templates/leetcode/examples/basic.json5
index 4ff8099..a1caf73 100644
--- a/.templates/leetcode/examples/basic.json5
+++ b/.templates/leetcode/examples/basic.json5
@@ -1,61 +1,73 @@
{
- // Basic problem template for array/string/number problems
- // Copy this structure when creating new basic problems
+ // Basic problem template - for array, string, number problems
+ // Example: Container With Most Water, Spiral Matrix
- // REQUIRED: Core identifiers
- "problem_name": "two_sum",
- "class_name": "TwoSum",
- "method_name": "two_sum",
+ // === 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
- // REQUIRED: Problem metadata
- "problem_number": "1", // OPTIONAL: omit if no LeetCode number
- "problem_title": "Two Sum",
- "difficulty": "Easy",
- "topics": "Array, Hash Table",
+ // === 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.",
- // OPTIONAL: Problem categorization
- "tags": ["grind-75"],
-
- // REQUIRED: Problem description
- "problem_description": "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.",
-
- // REQUIRED: Examples
- "examples": [
- { "input": "nums = [2,7,11,15], target = 9", "output": "[0,1]" },
- { "input": "nums = [3,2,4], target = 6", "output": "[1,2]" },
- { "input": "nums = [3,3], target = 6", "output": "[0,1]" }
+ "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```"
+ }
],
- // REQUIRED: Constraints
- "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.",
-
- // REQUIRED: Method signature
- "parameters": "nums: list[int], target: int",
- "return_type": "list[int]",
- "dummy_return": "[]",
+ "readme_constraints": "- n == height.length\n- 2 <= n <= 10^5\n- 0 <= height[i] <= 10^4",
+ "readme_additional": "", // Optional: additional notes, follow-up questions
- // REQUIRED: Imports (empty for basic problems)
- "imports": "",
-
- // REQUIRED: Test cases
- "test_cases": [
- { "args": [[2, 7, 11, 15], 9], "expected": [0, 1] },
- { "args": [[3, 2, 4], 6], "expected": [1, 2] },
- { "args": [[3, 3], 6], "expected": [0, 1] }
+ // === SOLUTION TEMPLATE ===
+ "solution_imports": "", // Empty for basic problems, add imports if needed
+ "solution_methods": [
+ {
+ "name": "max_area", // snake_case method name
+ "parameters": "height: list[int]", // Modern Python type hints (list[int], not List[int])
+ "return_type": "int", // Return type annotation
+ "dummy_return": "0" // Default return value (auto-set by generator)
+ }
],
- // REQUIRED: Test configuration
- "param_names": "nums, target, expected",
- "param_names_with_types": "nums: list[int], target: int, expected: list[int]",
- "input_description": "nums={nums}, target={target}",
- "input_params": "nums, target",
- "expected_param": "expected",
- "method_args": "nums, target",
- "test_setup": "",
- "test_logging": "",
- "assertion_code": "assert result == expected",
+ // === TEST CONFIGURATION ===
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
+ "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"
+ }
+ ],
- // REQUIRED: Notebook setup
- "test_input_setup": "# Example test case\nnums = [2, 7, 11, 15]\ntarget = 9",
- "expected_output_setup": "expected = [0, 1]"
+ // === PLAYGROUND NOTEBOOK ===
+ "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/examples/design.json5 b/.templates/leetcode/examples/design.json5
new file mode 100644
index 0000000..324dead
--- /dev/null
+++ b/.templates/leetcode/examples/design.json5
@@ -0,0 +1,81 @@
+{
+ // Design problem template - for data structure design problems
+ // Example: LRU Cache
+ // Key differences: Custom class name, multiple methods, complex test setup
+
+ // === 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_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 ===
+ // IMPORTANT: Design playground uses operation sequences like tests
+ "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/examples/linked_list.json5 b/.templates/leetcode/examples/linked_list.json5
index 44e7ad3..964be4f 100644
--- a/.templates/leetcode/examples/linked_list.json5
+++ b/.templates/leetcode/examples/linked_list.json5
@@ -1,62 +1,76 @@
{
- // Linked list problem template
- // Use this for problems involving ListNode structures
+ // Linked List problem template - for linked list problems
+ // Example: Reverse Linked List II
+ // Key differences: ListNode imports, list-specific test setup
- // REQUIRED: Core identifiers
- problem_name: "reverse_linked_list_ii",
- class_name: "ReverseLinkedListII",
- method_name: "reverse_between",
+ // === PROBLEM IDENTIFICATION ===
+ problem_name: "reverse_linked_list_ii", // snake_case: used for directory/file names
+ solution_class_name: "Solution", // Always "Solution" for algorithm problems
+ problem_number: "92", // LeetCode problem number as string
+ problem_title: "Reverse Linked List II", // Exact title from LeetCode
+ difficulty: "Medium", // Easy, Medium, Hard
+ topics: "Linked List", // Linked list related topics
+ tags: ["grind-75"], // Optional: common problem set tags
- // REQUIRED: Problem metadata
- problem_number: "92", // OPTIONAL: omit if no LeetCode number
- problem_title: "Reverse Linked List II",
- difficulty: "Medium",
- topics: "Linked List",
+ // === 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: "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.",
- // OPTIONAL: Problem categorization
- tags: [],
-
- // REQUIRED: Problem 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.",
-
- // REQUIRED: Examples
- examples: [
- { input: "head = [1,2,3,4,5], left = 2, right = 4", output: "[1,4,3,2,5]" },
- { input: "head = [5], left = 1, right = 1", output: "[5]" },
+ 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```",
+ },
],
- // REQUIRED: Constraints
- constraints: "- The number of nodes in the list is n.\n- 1 <= n <= 500\n- -500 <= Node.val <= 500\n- 1 <= left <= right <= n",
-
- // REQUIRED: Method signature
- parameters: "head: ListNode | None, left: int, right: int",
- return_type: "ListNode | None",
- dummy_return: "None",
-
- // REQUIRED: ListNode import for linked list problems
- imports: "from leetcode_py import ListNode",
+ 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?", // Optional follow-up questions
- // REQUIRED: Test cases
- test_cases: [
- { args: [[1, 2, 3, 4, 5], 2, 4], expected: [1, 4, 3, 2, 5] },
- { args: [[5], 1, 1], expected: [5] },
- { args: [[1, 2, 3], 1, 3], expected: [3, 2, 1] },
+ // === SOLUTION TEMPLATE ===
+ // IMPORTANT: Linked list problems need ListNode import
+ solution_imports: "from leetcode_py import ListNode",
+ solution_methods: [
+ {
+ name: "reverse_between", // snake_case method name
+ parameters: "head: ListNode | None, left: int, right: int", // Use ListNode | None for nullable parameters
+ return_type: "ListNode | None", // Modern union syntax
+ dummy_return: "None", // None for linked list problems
+ },
],
- // REQUIRED: Test parameters (use expected_list for linked list problems)
- param_names: "head_list, left, right, expected_list",
- param_names_with_types: "head_list: list[int], left: int, right: int, expected_list: list[int]",
- input_description: "head_list={head_list}, left={left}, right={right}",
- input_params: "head, left, right",
- expected_param: "expected",
- method_args: "head, left, right",
-
- // REQUIRED: Linked list-specific test setup
- test_setup: "head = ListNode.from_list(head_list)\nexpected = ListNode.from_list(expected_list)",
- test_logging: 'logger.success(f"Got result: {result.to_list() if result else []}")',
- assertion_code: "assert result == expected",
+ // === TEST CONFIGURATION ===
+ // IMPORTANT: Linked list tests need ListNode import and special test setup
+ test_imports: "import pytest\n\nfrom leetcode_py import ListNode\nfrom leetcode_py.test_utils import logged_test\n\nfrom .solution import Solution",
+ test_helper_methods: [
+ {
+ name: "setup_method",
+ parameters: "",
+ body: "self.solution = Solution()",
+ },
+ ],
+ test_methods: [
+ {
+ name: "test_reverse_between",
+ parametrize: "head_list, left, right, expected_list", // Use *_list naming for list array inputs
+ 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])]",
+ // IMPORTANT: Linked list test body converts arrays to ListNode and compares objects directly
+ body: "head = ListNode.from_list(head_list)\nexpected = ListNode.from_list(expected_list)\nresult = self.solution.reverse_between(head, left, right)\nassert result == expected",
+ },
+ ],
- // REQUIRED: Notebook setup for linked list problems
- test_input_setup: "# Example test case\nhead = ListNode.from_list([1, 2, 3, 4, 5])\nleft = 2\nright = 4",
- expected_output_setup: "expected = ListNode.from_list([1, 4, 3, 2, 5])",
+ // === PLAYGROUND NOTEBOOK ===
+ // IMPORTANT: Linked list playground needs ListNode import and conversion
+ 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.from_list(head_list)\nleft, right = 2, 4\nexpected = ListNode.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/examples/matrix.json5 b/.templates/leetcode/examples/matrix.json5
index 968558e..174b0a9 100644
--- a/.templates/leetcode/examples/matrix.json5
+++ b/.templates/leetcode/examples/matrix.json5
@@ -1,59 +1,73 @@
{
- // Matrix problem template
- // Use this for 2D array/matrix problems
+ // Matrix problem template - for 2D array/matrix problems
+ // Example: Spiral Matrix
+ // Key differences: 2D array parameters, often have visual examples with images
- // REQUIRED: Core identifiers
- "problem_name": "rotate_image",
- "class_name": "RotateImage",
- "method_name": "rotate",
+ // === PROBLEM IDENTIFICATION ===
+ "problem_name": "spiral_matrix", // snake_case: used for directory/file names
+ "solution_class_name": "Solution", // Always "Solution" for algorithm problems
+ "problem_number": "54", // LeetCode problem number as string
+ "problem_title": "Spiral Matrix", // Exact title from LeetCode
+ "difficulty": "Medium", // Easy, Medium, Hard
+ "topics": "Array, Matrix, Simulation", // Matrix-related topics
+ "tags": ["grind-75"], // Optional: common problem set tags
- // REQUIRED: Problem metadata
- "problem_number": "48", // OPTIONAL: omit if no LeetCode number
- "problem_title": "Rotate Image",
- "difficulty": "Medium",
- "topics": "Array, Math, Matrix",
+ // === 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": "Given an `m x n` matrix, return all elements of the matrix in spiral order.",
- // OPTIONAL: Problem categorization
- "tags": ["grind-75"],
-
- // REQUIRED: Problem description
- "problem_description": "You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).\n\nYou have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.",
-
- // REQUIRED: Examples
- "examples": [
- { "input": "matrix = [[1,2,3],[4,5,6],[7,8,9]]", "output": "[[7,4,1],[8,5,2],[9,6,3]]" },
- { "input": "matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]", "output": "[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]" }
+ "readme_examples": [
+ {
+ // IMPORTANT: Matrix problems often have visual diagrams - include images
+ "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```"
+ }
],
- // REQUIRED: Constraints
- "constraints": "- n == matrix.length == matrix[i].length\n- 1 <= n <= 20\n- -1000 <= matrix[i][j] <= 1000",
-
- // REQUIRED: Method signature
- "parameters": "matrix: list[list[int]]",
- "return_type": "None",
- "dummy_return": "None",
+ "readme_constraints": "- m == matrix.length\\n- n == matrix[i].length\\n- 1 <= m, n <= 10\\n- -100 <= matrix[i][j] <= 100",
+ "readme_additional": "",
- // REQUIRED: Imports (empty for matrix problems)
- "imports": "",
-
- // REQUIRED: Test cases (for in-place operations, test the modified matrix)
- "test_cases": [
- { "args": [[[1,2,3],[4,5,6],[7,8,9]]], "expected": [[7,4,1],[8,5,2],[9,6,3]] },
- { "args": [[[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]], "expected": [[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]] }
+ // === SOLUTION TEMPLATE ===
+ "solution_imports": "", // Usually empty for matrix problems
+ "solution_methods": [
+ {
+ "name": "spiral_order", // snake_case method name
+ "parameters": "matrix: list[list[int]]", // 2D array type annotation
+ "return_type": "list[int]", // Usually returns flattened result
+ "dummy_return": "[]" // Empty list for array returns
+ }
],
- // REQUIRED: Test configuration
- "param_names": "matrix, expected",
- "param_names_with_types": "matrix: list[list[int]], expected: list[list[int]]",
- "input_description": "matrix={matrix}",
- "input_params": "matrix",
- "expected_param": "expected",
- "method_args": "matrix",
- "test_setup": "",
- "test_logging": "",
- "assertion_code": "assert matrix == expected",
+ // === TEST CONFIGURATION ===
+ "test_imports": "import pytest\\nfrom leetcode_py.test_utils import logged_test\\nfrom .solution import Solution",
+ "test_helper_methods": [
+ {
+ "name": "setup_method",
+ "parameters": "",
+ "body": "self.solution = Solution()"
+ }
+ ],
+ "test_methods": [
+ {
+ "name": "test_spiral_order",
+ "parametrize": "matrix, expected", // Simple matrix, expected pattern
+ "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"
+ }
+ ],
- // REQUIRED: Notebook setup
- "test_input_setup": "# Example test case\nmatrix = [[1,2,3],[4,5,6],[7,8,9]]",
- "expected_output_setup": "expected = [[7,4,1],[8,5,2],[9,6,3]]"
+ // === PLAYGROUND NOTEBOOK ===
+ "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/examples/string.json5 b/.templates/leetcode/examples/string.json5
deleted file mode 100644
index ce3be95..0000000
--- a/.templates/leetcode/examples/string.json5
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- // String problem template
- // Use this for string manipulation problems
-
- // REQUIRED: Core identifiers
- "problem_name": "valid_palindrome",
- "class_name": "ValidPalindrome",
- "method_name": "is_palindrome",
-
- // REQUIRED: Problem metadata
- "problem_number": "125", // OPTIONAL: omit if no LeetCode number
- "problem_title": "Valid Palindrome",
- "difficulty": "Easy",
- "topics": "Two Pointers, String",
-
- // OPTIONAL: Problem categorization
- "tags": ["grind-75"],
-
- // REQUIRED: Problem 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.\n\nGiven a string s, return true if it is a palindrome, or false otherwise.",
-
- // REQUIRED: Examples
- "examples": [
- { "input": "s = \"A man, a plan, a canal: Panama\"", "output": "true" },
- { "input": "s = \"race a car\"", "output": "false" },
- { "input": "s = \" \"", "output": "true" }
- ],
-
- // REQUIRED: Constraints
- "constraints": "- 1 <= s.length <= 2 * 10^5\n- s consists only of printable ASCII characters.",
-
- // REQUIRED: Method signature
- "parameters": "s: str",
- "return_type": "bool",
- "dummy_return": "False",
-
- // REQUIRED: Imports (empty for string problems)
- "imports": "",
-
- // REQUIRED: Test cases
- "test_cases": [
- { "args": ["A man, a plan, a canal: Panama"], "expected": true },
- { "args": ["race a car"], "expected": false },
- { "args": [" "], "expected": true }
- ],
-
- // REQUIRED: Test configuration
- "param_names": "s, expected",
- "param_names_with_types": "s: str, expected: bool",
- "input_description": "s=\"{s}\"",
- "input_params": "s",
- "expected_param": "expected",
- "method_args": "s",
- "test_setup": "",
- "test_logging": "",
- "assertion_code": "assert result == expected",
-
- // REQUIRED: Notebook setup
- "test_input_setup": "# Example test case\ns = \"A man, a plan, a canal: Panama\"",
- "expected_output_setup": "expected = True"
-}
diff --git a/.templates/leetcode/examples/tree.json5 b/.templates/leetcode/examples/tree.json5
index caa6826..0046492 100644
--- a/.templates/leetcode/examples/tree.json5
+++ b/.templates/leetcode/examples/tree.json5
@@ -1,63 +1,80 @@
{
- // Tree problem template for binary tree problems
- // Use this for problems involving TreeNode structures
+ // Tree problem template - for binary tree problems
+ // Example: Invert Binary Tree
+ // Key differences: TreeNode imports, tree-specific test setup
- // REQUIRED: Core identifiers
- problem_name: "invert_binary_tree",
- class_name: "InvertBinaryTree",
- method_name: "invert_tree",
+ // === PROBLEM IDENTIFICATION ===
+ problem_name: "invert_binary_tree", // snake_case: used for directory/file names
+ solution_class_name: "Solution", // Always "Solution" for algorithm problems
+ problem_number: "226", // LeetCode problem number as string
+ problem_title: "Invert Binary Tree", // Exact title from LeetCode
+ difficulty: "Easy", // Easy, Medium, Hard
+ topics: "Tree, Depth-First Search, Breadth-First Search, Binary Tree", // Tree-related topics
+ tags: ["grind-75"], // Optional: common problem set tags
- // REQUIRED: Problem metadata
- problem_number: "226", // OPTIONAL: omit if no LeetCode number
- problem_title: "Invert Binary Tree",
- difficulty: "Easy",
- topics: "Tree, Depth-First Search, Breadth-First Search, Binary Tree",
+ // === 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: "Given the `root` of a binary tree, invert the tree, and return its root.",
- // OPTIONAL: Problem categorization
- tags: ["grind-75"],
-
- // REQUIRED: Problem description
- problem_description: "Given the root of a binary tree, invert the tree, and return its root.",
-
- // REQUIRED: Examples (tree problems show array representation)
- examples: [
- { input: "root = [4,2,7,1,3,6,9]", output: "[4,7,2,9,6,3,1]" },
- { input: "root = [2,1,3]", output: "[2,3,1]" },
- { input: "root = []", output: "[]" },
+ readme_examples: [
+ {
+ // Tree problems often have visual examples with images
+ 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```",
+ },
],
- // REQUIRED: Constraints
- constraints: "- The number of nodes in the tree is in the range [0, 100].\n- -100 <= Node.val <= 100",
-
- // REQUIRED: Method signature (TreeNode | None for nullable tree parameters)
- parameters: "root: TreeNode | None",
- return_type: "TreeNode | None",
- dummy_return: "None",
-
- // REQUIRED: TreeNode import for tree problems
- imports: "from leetcode_py import TreeNode",
+ readme_constraints: "- The number of nodes in the tree is in the range [0, 100]\n- -100 <= Node.val <= 100",
+ readme_additional: "",
- // REQUIRED: Test cases (use array representation for tree inputs/outputs)
- test_cases: [
- { args: [[4, 2, 7, 1, 3, 6, 9]], expected: [4, 7, 2, 9, 6, 3, 1] },
- { args: [[2, 1, 3]], expected: [2, 3, 1] },
- { args: [[]], expected: [] },
+ // === SOLUTION TEMPLATE ===
+ // IMPORTANT: Tree problems need TreeNode import
+ solution_imports: "from leetcode_py import TreeNode",
+ solution_methods: [
+ {
+ name: "invert_tree", // snake_case method name
+ parameters: "root: TreeNode | None", // Use TreeNode | None for nullable tree parameters
+ return_type: "TreeNode | None", // Modern union syntax (not Optional[TreeNode])
+ dummy_return: "None", // None for tree problems
+ },
],
- // REQUIRED: Test parameters (use expected_list for tree problems)
- param_names: "root_list, expected_list",
- param_names_with_types: "root_list: list[int | None], expected_list: list[int | None]",
- input_description: "root_list={root_list}",
- input_params: "root",
- expected_param: "expected",
- method_args: "root",
-
- // REQUIRED: Tree-specific test setup (converts arrays to TreeNode objects)
- test_setup: "root = TreeNode.from_list(root_list)\nexpected = TreeNode.from_list(expected_list)",
- test_logging: 'logger.success(f"Got result: {result.to_list() if result else []}")',
- assertion_code: "assert result == expected",
+ // === TEST CONFIGURATION ===
+ // IMPORTANT: Tree tests need TreeNode import and special test setup
+ test_imports: "import pytest\n\nfrom leetcode_py import TreeNode\nfrom leetcode_py.test_utils import logged_test\n\nfrom .solution import Solution",
+ test_helper_methods: [
+ {
+ name: "setup_method",
+ parameters: "",
+ body: "self.solution = Solution()",
+ },
+ ],
+ test_methods: [
+ {
+ name: "test_invert_tree",
+ parametrize: "root_list, expected_list", // Use *_list naming for tree array inputs
+ 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]), ([], [])]",
+ // IMPORTANT: Tree test body converts arrays to TreeNode and compares objects directly
+ body: "root = TreeNode.from_list(root_list)\nexpected = TreeNode.from_list(expected_list)\nresult = self.solution.invert_tree(root)\nassert result == expected",
+ },
+ ],
- // REQUIRED: Notebook setup for tree problems
- test_input_setup: "# Example test case\nroot = TreeNode.from_list([4, 2, 7, 1, 3, 6, 9])",
- expected_output_setup: "expected = TreeNode.from_list([4, 7, 2, 9, 6, 3, 1])",
+ // === PLAYGROUND NOTEBOOK ===
+ // IMPORTANT: Tree playground needs TreeNode import and conversion
+ 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.from_list(root_list)\nexpected = TreeNode.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/container_with_most_water.json b/.templates/leetcode/json/container_with_most_water.json
index b974e9c..603b7ce 100644
--- a/.templates/leetcode/json/container_with_most_water.json
+++ b/.templates/leetcode/json/container_with_most_water.json
@@ -1,42 +1,44 @@
{
"problem_name": "container_with_most_water",
- "class_name": "ContainerWithMostWater",
- "method_name": "max_area",
+ "solution_class_name": "Solution",
"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": [
+ "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": [
{
- "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."
+ "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```"
},
- { "input": "height = [1,1]", "output": "1" }
+ { "content": "```\nInput: height = [1,1]\nOutput: 1\n```" }
],
- "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 }
+ "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_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"
+ }
],
- "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"
+ "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/insert_interval.json b/.templates/leetcode/json/insert_interval.json
index 935cfd2..abf1918 100644
--- a/.templates/leetcode/json/insert_interval.json
+++ b/.templates/leetcode/json/insert_interval.json
@@ -1,68 +1,46 @@
{
"problem_name": "insert_interval",
- "class_name": "InsertInterval",
- "method_name": "insert",
+ "solution_class_name": "Solution",
"problem_number": "57",
"problem_title": "Insert Interval",
"difficulty": "Medium",
"topics": "Array",
"tags": ["grind-75"],
- "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 new_interval = [start, end] that represents the start and end of another interval.\n\nInsert new_interval 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.",
- "examples": [
- { "input": "intervals = [[1,3],[6,9]], new_interval = [2,5]", "output": "[[1,5],[6,9]]" },
+ "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": [
{
- "input": "intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], new_interval = [4,8]",
- "output": "[[1,2],[3,10],[12,16]]"
+ "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```"
}
],
- "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- new_interval.length == 2\n- 0 <= start <= end <= 10^5",
- "parameters": "intervals: list[list[int]], new_interval: list[int]",
- "return_type": "list[list[int]]",
- "dummy_return": "[]",
- "imports": "",
- "test_cases": [
+ "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": [
{
- "args": [
- [
- [1, 3],
- [6, 9]
- ],
- [2, 5]
- ],
- "expected": [
- [1, 5],
- [6, 9]
- ]
- },
+ "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_helper_methods": [
+ { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
+ ],
+ "test_methods": [
{
- "args": [
- [
- [1, 2],
- [3, 5],
- [6, 7],
- [8, 10],
- [12, 16]
- ],
- [4, 8]
- ],
- "expected": [
- [1, 2],
- [3, 10],
- [12, 16]
- ]
- },
- { "args": [[], [5, 7]], "expected": [[5, 7]] },
- { "args": [[[1, 5]], [2, 3]], "expected": [[1, 5]] }
+ "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"
+ }
],
- "param_names": "intervals, new_interval, expected",
- "param_names_with_types": "intervals: list[list[int]], new_interval: list[int], expected: list[list[int]]",
- "input_description": "intervals={intervals}, new_interval={new_interval}",
- "input_params": "intervals, new_interval",
- "expected_param": "expected",
- "method_args": "intervals, new_interval",
- "test_setup": "",
- "test_logging": "",
- "assertion_code": "assert result == expected",
- "test_input_setup": "# Example test case\nintervals = [[1,3],[6,9]]\nnew_interval = [2,5]",
- "expected_output_setup": "expected = [[1,5],[6,9]]"
+ "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/invert_binary_tree.json b/.templates/leetcode/json/invert_binary_tree.json
index d9e26dc..1fae254 100644
--- a/.templates/leetcode/json/invert_binary_tree.json
+++ b/.templates/leetcode/json/invert_binary_tree.json
@@ -1,37 +1,43 @@
{
"problem_name": "invert_binary_tree",
- "class_name": "InvertBinaryTree",
- "method_name": "invert_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"],
- "problem_description": "Given the root of a binary tree, invert the tree, and return its root.",
- "examples": [
- { "input": "root = [4,2,7,1,3,6,9]", "output": "[4,7,2,9,6,3,1]" },
- { "input": "root = [2,1,3]", "output": "[2,3,1]" },
- { "input": "root = []", "output": "[]" }
+ "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```" }
],
- "constraints": "- The number of nodes in the tree is in the range [0, 100].\n- -100 <= Node.val <= 100",
- "parameters": "root: TreeNode | None",
- "return_type": "TreeNode | None",
- "dummy_return": "None",
- "imports": "from leetcode_py import TreeNode",
- "test_cases": [
- { "args": [[4, 2, 7, 1, 3, 6, 9]], "expected": [4, 7, 2, 9, 6, 3, 1] },
- { "args": [[2, 1, 3]], "expected": [2, 3, 1] },
- { "args": [[]], "expected": [] }
+ "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 | None",
+ "return_type": "TreeNode | None",
+ "dummy_return": "None"
+ }
],
- "param_names": "root_list, expected_list",
- "param_names_with_types": "root_list: list[int | None], expected_list: list[int | None]",
- "input_description": "root_list={root_list}",
- "input_params": "root",
- "expected_param": "expected",
- "method_args": "root",
- "test_setup": "root = TreeNode.from_list(root_list)\nexpected = TreeNode.from_list(expected_list)",
- "test_logging": "logger.success(f\"Got result: {result.to_list() if result else []}\")",
- "assertion_code": "assert result == expected",
- "test_input_setup": "# Example test case\nroot = TreeNode.from_list([4, 2, 7, 1, 3, 6, 9])",
- "expected_output_setup": "expected = TreeNode.from_list([4, 7, 2, 9, 6, 3, 1])"
+ "test_imports": "import pytest\n\nfrom leetcode_py import TreeNode\nfrom leetcode_py.test_utils import logged_test\n\nfrom .solution import Solution",
+ "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.from_list(root_list)\nexpected = TreeNode.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.from_list(root_list)\nexpected = TreeNode.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/lru_cache.json b/.templates/leetcode/json/lru_cache.json
index 3792eab..4609b1a 100644
--- a/.templates/leetcode/json/lru_cache.json
+++ b/.templates/leetcode/json/lru_cache.json
@@ -1,42 +1,43 @@
{
"problem_name": "lru_cache",
- "class_name": "LRUCache",
- "method_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", "top-interview"],
- "problem_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.",
- "examples": [
+ "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": [
{
- "input": "[\"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]]",
- "output": "[null, null, null, 1, null, -1, null, -1, 3, 4]"
+ "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```"
}
],
- "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.",
- "parameters": "capacity: int",
- "return_type": "None",
- "dummy_return": "None",
- "imports": "",
- "test_cases": [
+ "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" },
{
- "args": [
- ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"],
- [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
- ],
- "expected": [null, null, null, 1, null, -1, null, -1, 3, 4]
+ "name": "put",
+ "parameters": "key: int, value: int",
+ "return_type": "None",
+ "dummy_return": ""
}
],
- "param_names": "operations, inputs, expected",
- "param_names_with_types": "operations: list[str], inputs: list[list[int]], expected: list[int | None]",
- "input_description": "operations={operations}, inputs={inputs}",
- "input_params": "operations, inputs",
- "expected_param": "expected",
- "method_args": "operations, inputs",
- "test_setup": "cache = None\nresult = []\nfor i, op in enumerate(operations):\n if op == \"LRUCache\":\n cache = LRUCache(inputs[i][0])\n result.append(None)\n elif op == \"get\":\n result.append(cache.get(inputs[i][0]))\n elif op == \"put\":\n cache.put(inputs[i][0], inputs[i][1])\n result.append(None)",
- "test_logging": "logger.info(f\"Testing LRU Cache with operations: {operations}\")\nlogger.info(f\"Inputs: {inputs}\")\nlogger.info(f\"Expected: {expected}\")\nlogger.info(f\"Result: {result}\")",
- "assertion_code": "assert result == expected",
- "test_input_setup": "operations = [\"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]]",
- "expected_output_setup": "expected = [None, None, None, 1, None, -1, None, -1, 3, 4]"
+ "test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import 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/reverse_linked_list_ii.json b/.templates/leetcode/json/reverse_linked_list_ii.json
index e71f081..dd50623 100644
--- a/.templates/leetcode/json/reverse_linked_list_ii.json
+++ b/.templates/leetcode/json/reverse_linked_list_ii.json
@@ -1,35 +1,42 @@
{
- "assertion_code": "assert result == expected",
- "class_name": "ReverseLinkedListII",
- "constraints": "- The number of nodes in the list is n.\n- 1 <= n <= 500\n- -500 <= Node.val <= 500\n- 1 <= left <= right <= n",
- "difficulty": "Medium",
- "examples": [
- { "input": "head = [1,2,3,4,5], left = 2, right = 4", "output": "[1,4,3,2,5]" },
- { "input": "head = [5], left = 1, right = 1", "output": "[5]" }
- ],
- "expected_output_setup": "expected = ListNode.from_list([1, 4, 3, 2, 5])",
- "expected_param": "expected",
- "imports": "from leetcode_py import ListNode",
- "input_description": "head_list={head_list}, left={left}, right={right}",
- "input_params": "head, left, right",
- "method_args": "head, left, right",
- "method_name": "reverse_between",
- "param_names": "head_list, left, right, expected_list",
- "param_names_with_types": "head_list: list[int], left: int, right: int, expected_list: list[int]",
- "parameters": "head: ListNode | None, left: int, right: int",
- "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.",
+ "problem_name": "reverse_linked_list_ii",
+ "solution_class_name": "Solution",
"problem_number": "92",
"problem_title": "Reverse Linked List II",
- "problem_name": "reverse_linked_list_ii",
- "return_type": "ListNode | None",
- "dummy_return": "None",
- "test_cases": [
- { "args": [[1, 2, 3, 4, 5], 2, 4], "expected": [1, 4, 3, 2, 5] },
- { "args": [[5], 1, 1], "expected": [5] },
- { "args": [[1, 2, 3], 1, 3], "expected": [3, 2, 1] }
+ "difficulty": "Medium",
+ "topics": "Linked List",
+ "tags": ["grind-75"],
+ "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 | None, left: int, right: int",
+ "return_type": "ListNode | 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_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.from_list(head_list)\nexpected = ListNode.from_list(expected_list)\nresult = self.solution.reverse_between(head, left, right)\nassert result == expected"
+ }
],
- "test_input_setup": "# Example test case\nhead = ListNode.from_list([1, 2, 3, 4, 5])\nleft = 2\nright = 4",
- "test_logging": "logger.success(f\"Got result: {result.to_list() if result else []}\")",
- "test_setup": "head = ListNode.from_list(head_list)\nexpected = ListNode.from_list(expected_list)",
- "topics": "Linked List"
+ "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.from_list(head_list)\nleft, right = 2, 4\nexpected = ListNode.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/spiral_matrix.json b/.templates/leetcode/json/spiral_matrix.json
index d4b7952..22c44db 100644
--- a/.templates/leetcode/json/spiral_matrix.json
+++ b/.templates/leetcode/json/spiral_matrix.json
@@ -1,66 +1,46 @@
{
"problem_name": "spiral_matrix",
- "class_name": "SpiralMatrix",
- "method_name": "spiral_order",
+ "solution_class_name": "Solution",
"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]" },
+ "readme_description": "Given an `m x n` matrix, return all elements of the matrix in spiral order.",
+ "readme_examples": [
{
- "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]"
+ "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```"
}
],
- "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]
- },
+ "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": [
{
- "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] },
+ "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_helper_methods": [
+ { "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
+ ],
+ "test_methods": [
{
- "args": [
- [
- [1, 2],
- [3, 4]
- ]
- ],
- "expected": [1, 2, 4, 3]
+ "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"
}
],
- "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]"
+ "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/{{cookiecutter.problem_name}}/README.md b/.templates/leetcode/{{cookiecutter.problem_name}}/README.md
index 35ace9b..f68afe6 100644
--- a/.templates/leetcode/{{cookiecutter.problem_name}}/README.md
+++ b/.templates/leetcode/{{cookiecutter.problem_name}}/README.md
@@ -1,31 +1,31 @@
-# {{cookiecutter.problem_number}}. {{cookiecutter.problem_title}}
+# {{cookiecutter.problem_title}}
**Difficulty:** {{cookiecutter.difficulty}}
**Topics:** {{cookiecutter.topics}}
**Tags:** {% for _, tags in cookiecutter._tags | dictsort %}{{ tags | join(', ') }}{% endfor %}
-{%- if cookiecutter.problem_number %}
+{% if cookiecutter.problem_number %}
**LeetCode:** [Problem {{cookiecutter.problem_number}}](https://leetcode.com/problems/{{cookiecutter.problem_name.replace('_', "-")}}/description/)
-{%- endif %}
+{% endif %}
## Problem Description
-{{cookiecutter.problem_description}}
+{{cookiecutter.readme_description}}
## Examples
-{%- for _, examples in cookiecutter._examples | dictsort %}
+{%- for _, examples in cookiecutter._readme_examples | dictsort %}
{%- for example in examples %}
-### Example {{ loop.index }}:
-
-```
-Input: {{ example.input }}
-Output: {{ example.output }}
-```
+### Example {{loop.index}}:
+{{example.content}}
{%- endfor %}
{%- endfor %}
## Constraints
-{{cookiecutter.constraints}}
+{{cookiecutter.readme_constraints}}
+
+{% if cookiecutter.readme_additional %}
+{{cookiecutter.readme_additional}}
+{% endif %}
diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb b/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb
index cf7f6af..ac1863b 100644
--- a/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb
+++ b/.templates/leetcode/{{cookiecutter.problem_name}}/playground.ipynb
@@ -6,11 +6,7 @@
"id": "imports",
"metadata": {},
"outputs": [],
- "source": [
- "from solution import Solution"{%- if cookiecutter.imports and cookiecutter.imports.strip() %},
- "",
- "{{cookiecutter.imports}}"{%- endif %}
- ]
+ "source": ["{{ cookiecutter.playground_imports | replace('\n', '\\n') }}"]
},
{
"cell_type": "code",
@@ -18,11 +14,7 @@
"id": "setup",
"metadata": {},
"outputs": [],
- "source": [
- {% for line in cookiecutter.test_input_setup.split('\n') %}"{{line}}"{%- if not loop.last %},
- {% endif %}{% endfor %},
- "{{cookiecutter.expected_output_setup}}"
- ]
+ "source": ["{{ cookiecutter.playground_test_case | replace('\n', '\\n') }}"]
},
{
"cell_type": "code",
@@ -30,10 +22,7 @@
"id": "execute",
"metadata": {},
"outputs": [],
- "source": [
- "result = Solution().{{cookiecutter.method_name}}({{cookiecutter.method_args}})",
- "result"
- ]
+ "source": ["{{ cookiecutter.playground_execution | replace('\n', '\\n') }}"]
},
{
"cell_type": "code",
@@ -41,9 +30,7 @@
"id": "test",
"metadata": {},
"outputs": [],
- "source": [
- "{{cookiecutter.assertion_code}}"
- ]
+ "source": ["{{ cookiecutter.playground_assertion | replace('\n', '\\n') }}"]
}
],
"metadata": {
diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py b/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py
index 92302e5..d731510 100644
--- a/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py
+++ b/.templates/leetcode/{{cookiecutter.problem_name}}/solution.py
@@ -1,9 +1,13 @@
-{{cookiecutter.imports}}
+{{cookiecutter.solution_imports}}
-
-class Solution:
+class {{cookiecutter.solution_class_name}}:
+ {%- for _, methods in cookiecutter._solution_methods | dictsort %}
+ {%- for method in methods %}
# Time: O(?)
# Space: O(?)
- def {{cookiecutter.method_name}}(self, {{cookiecutter.parameters}}) -> {{cookiecutter.return_type}}:
- # TODO: Implement solution
- return {{cookiecutter.dummy_return}}
+ def {{method.name}}(self, {{method.parameters}}) -> {{method.return_type}}:
+ # TODO: Implement {{method.name}}
+ return {{method.dummy_return}}
+
+ {%- endfor %}
+ {%- endfor %}
diff --git a/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py b/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py
index 22a9024..0125dbb 100644
--- a/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py
+++ b/.templates/leetcode/{{cookiecutter.problem_name}}/tests.py
@@ -1,42 +1,21 @@
-import pytest
-from loguru import logger
+{{cookiecutter.test_imports}}
-{{cookiecutter.imports}}
-from leetcode_py.test_utils import logged_test
-from .solution import Solution
+class Test{{cookiecutter.solution_class_name}}:
+ {%- for _, helper_methods in cookiecutter._test_helper_methods | dictsort %}
+ {%- for method in helper_methods %}
+ def {{method.name}}(self{% if method.parameters %}, {{method.parameters}}{% endif %}):
+ {{method.body | indent(8, first=False)}}
+ {%- endfor %}
+ {%- endfor %}
-class Test{{cookiecutter.class_name}}:
- def setup_method(self):
- self.solution = Solution()
-
- @pytest.mark.parametrize(
- "{{cookiecutter.param_names}}",
- [
- {%- for _, test_cases in cookiecutter._test_cases | dictsort %}
- {%- for test_case in test_cases %}
- ({% for arg in test_case.args %}{% if arg is string %}"{{ arg }}"{% else %}{{ arg }}{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}, {{ test_case.expected }}),
- {%- 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 test_{{cookiecutter.method_name}}(self, {{cookiecutter.param_names_with_types}}):
- logger.info(f"Testing with {{cookiecutter.input_description}}")
- {%- if cookiecutter.test_setup %}
- {%- for line in cookiecutter.test_setup.split('\n') %}
- {{line}}
- {%- endfor %}
- {%- endif %}
- result = self.solution.{{cookiecutter.method_name}}({{cookiecutter.input_params}})
- {%- if cookiecutter.test_logging %}
- {{cookiecutter.test_logging}}
- {%- else %}
- logger.success(f"Got result: {result}")
- {%- endif %}
- {%- if cookiecutter.assertion_code %}
- {{cookiecutter.assertion_code}}
- {%- else %}
- assert result == {{cookiecutter.expected_param}}
- {%- endif %}
+ 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 273c4fa..e9b4dc7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
PYTHON_VERSION = 3.13
-PROBLEM ?= lru_cache
+PROBLEM ?= invert_binary_tree
FORCE ?= 0
sync_submodules:
diff --git a/leetcode/container_with_most_water/README.md b/leetcode/container_with_most_water/README.md
index be54e14..78a4c7b 100644
--- a/leetcode/container_with_most_water/README.md
+++ b/leetcode/container_with_most_water/README.md
@@ -1,13 +1,14 @@
-# 11. Container With Most Water
+# 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]).
+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.
@@ -19,11 +20,12 @@ Notice that you may not slant the container.
### 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:
diff --git a/leetcode/container_with_most_water/example1.png b/leetcode/container_with_most_water/example1.png
deleted file mode 100644
index d565b18..0000000
Binary files a/leetcode/container_with_most_water/example1.png and /dev/null differ
diff --git a/leetcode/container_with_most_water/playground.ipynb b/leetcode/container_with_most_water/playground.ipynb
index 1f25e22..48e3823 100644
--- a/leetcode/container_with_most_water/playground.ipynb
+++ b/leetcode/container_with_most_water/playground.ipynb
@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
@@ -12,7 +12,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -24,29 +24,17 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "execute",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "49"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().max_area(height)\n",
- "result"
+ "result = Solution().max_area(height)\nresult"
]
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "test",
"metadata": {},
"outputs": [],
@@ -69,7 +57,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
+ "nbconvert_exporter": "python3",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
diff --git a/leetcode/container_with_most_water/tests.py b/leetcode/container_with_most_water/tests.py
index 751ac27..455bc6f 100644
--- a/leetcode/container_with_most_water/tests.py
+++ b/leetcode/container_with_most_water/tests.py
@@ -1,28 +1,18 @@
import pytest
-from loguru import logger
from leetcode_py.test_utils import logged_test
from .solution import Solution
-class TestContainerWithMostWater:
+class TestSolution:
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),
- ],
+ "height, expected", [([1, 8, 6, 2, 5, 4, 8, 3, 7], 49), ([1, 1], 1), ([1, 2, 1], 2)]
)
@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/insert_interval/README.md b/leetcode/insert_interval/README.md
index 40384cf..aff876d 100644
--- a/leetcode/insert_interval/README.md
+++ b/leetcode/insert_interval/README.md
@@ -1,32 +1,34 @@
-# 57. Insert Interval
+# 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 new_interval = [start, end] that represents the start and end of another interval.
+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 new_interval 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).
+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.
+Return `intervals` after the insertion.
## Examples
### Example 1:
```
-Input: intervals = [[1,3],[6,9]], new_interval = [2,5]
+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]], new_interval = [4,8]
+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
@@ -34,6 +36,6 @@ Output: [[1,2],[3,10],[12,16]]
- 0 <= intervals.length <= 10^4
- intervals[i].length == 2
- 0 <= starti <= endi <= 10^5
-- intervals is sorted by starti in ascending order.
-- new_interval.length == 2
+- intervals is sorted by starti in ascending order
+- newInterval.length == 2
- 0 <= start <= end <= 10^5
diff --git a/leetcode/insert_interval/playground.ipynb b/leetcode/insert_interval/playground.ipynb
index f9d36a7..783109a 100644
--- a/leetcode/insert_interval/playground.ipynb
+++ b/leetcode/insert_interval/playground.ipynb
@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
@@ -12,7 +12,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -25,29 +25,17 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "execute",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[[1, 5], [6, 9]]"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().insert(intervals, new_interval)\n",
- "result"
+ "result = Solution().insert(intervals, new_interval)\nresult"
]
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "test",
"metadata": {},
"outputs": [],
@@ -70,7 +58,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
+ "nbconvert_exporter": "python3",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
diff --git a/leetcode/insert_interval/tests.py b/leetcode/insert_interval/tests.py
index 7025338..26dbbb4 100644
--- a/leetcode/insert_interval/tests.py
+++ b/leetcode/insert_interval/tests.py
@@ -1,12 +1,11 @@
import pytest
-from loguru import logger
from leetcode_py.test_utils import logged_test
from .solution import Solution
-class TestInsertInterval:
+class TestSolution:
def setup_method(self):
self.solution = Solution()
@@ -15,15 +14,11 @@ def setup_method(self):
[
([[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]]),
],
)
@logged_test
def test_insert(
self, intervals: list[list[int]], new_interval: list[int], expected: list[list[int]]
):
- logger.info(f"Testing with intervals={intervals}, new_interval={new_interval}")
result = self.solution.insert(intervals, new_interval)
- logger.success(f"Got result: {result}")
assert result == expected
diff --git a/leetcode/invert_binary_tree/README.md b/leetcode/invert_binary_tree/README.md
index 0cc21ed..a2b7ded 100644
--- a/leetcode/invert_binary_tree/README.md
+++ b/leetcode/invert_binary_tree/README.md
@@ -1,13 +1,14 @@
-# 226. Invert Binary Tree
+# 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.
+Given the `root` of a binary tree, invert the tree, and return its root.
## Examples
@@ -34,5 +35,5 @@ Output: []
## Constraints
-- The number of nodes in the tree is in the range [0, 100].
+- The number of nodes in the tree is in the range [0, 100]
- -100 <= Node.val <= 100
diff --git a/leetcode/invert_binary_tree/playground.ipynb b/leetcode/invert_binary_tree/playground.ipynb
index 1156392..b63bfcd 100644
--- a/leetcode/invert_binary_tree/playground.ipynb
+++ b/leetcode/invert_binary_tree/playground.ipynb
@@ -6,251 +6,31 @@
"id": "imports",
"metadata": {},
"outputs": [],
- "source": [
- "from solution import Solution\n",
- "\n",
- "from leetcode_py import TreeNode"
- ]
+ "source": ["from solution import Solution\n\nfrom leetcode_py import TreeNode"]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\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])\n",
- "root"
- ]
+ "outputs": [],
+ "source": ["# Example test case\nroot_list: list[int | None] = [4, 2, 7, 1, 3, 6, 9]\nroot = TreeNode.from_list(root_list)\nexpected = TreeNode.from_list([4, 7, 2, 9, 6, 3, 1])"]
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "execute",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\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 = Solution().invert_tree(root)\n",
- "result"
- ]
+ "outputs": [],
+ "source": ["result = Solution().invert_tree(root)\nresult"]
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "test",
"metadata": {},
"outputs": [],
- "source": [
- "assert result == expected"
- ]
+ "source": ["assert result == expected"]
}
],
"metadata": {
@@ -267,7 +47,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
+ "nbconvert_exporter": "python3",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
diff --git a/leetcode/invert_binary_tree/tests.py b/leetcode/invert_binary_tree/tests.py
index 69cd938..5c7d537 100644
--- a/leetcode/invert_binary_tree/tests.py
+++ b/leetcode/invert_binary_tree/tests.py
@@ -1,37 +1,26 @@
import pytest
-from loguru import logger
from leetcode_py import TreeNode
from leetcode_py.test_utils import logged_test
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:
+class TestSolution:
@pytest.mark.parametrize("solution_class", [Solution, SolutionDFS, SolutionBFS])
- @pytest.mark.parametrize("root_list, expected_list", test_cases)
+ @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,
- solution_class: type[Solution | SolutionDFS | SolutionBFS],
root_list: list[int | None],
expected_list: list[int | None],
+ solution_class: type[Solution | SolutionDFS | SolutionBFS],
):
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 = solution.invert_tree(root)
- logger.success(f"Got result: {result.to_list() if result else []}")
assert result == expected
diff --git a/leetcode/lru_cache/README.md b/leetcode/lru_cache/README.md
index 01c5496..7037dbc 100644
--- a/leetcode/lru_cache/README.md
+++ b/leetcode/lru_cache/README.md
@@ -1,30 +1,45 @@
-# 146. LRU Cache
+# LRU Cache
**Difficulty:** Medium
**Topics:** Hash Table, Linked List, Design, Doubly-Linked List
-**Tags:** grind-75, top-interview
+**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:
+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.
+- `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.
+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"]
+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]
+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
@@ -32,4 +47,4 @@ Output: [null, null, null, 1, null, -1, null, -1, 3, 4]
- 1 <= capacity <= 3000
- 0 <= key <= 10^4
- 0 <= value <= 10^5
-- At most 2 \* 10^5 calls will be made to get and put.
+- At most 2 \* 10^5 calls will be made to get and put
diff --git a/leetcode/lru_cache/playground.ipynb b/leetcode/lru_cache/playground.ipynb
index 2e9dae1..3d277f1 100644
--- a/leetcode/lru_cache/playground.ipynb
+++ b/leetcode/lru_cache/playground.ipynb
@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
@@ -12,11 +12,12 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "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]"
@@ -24,44 +25,33 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "execute",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[None, None, None, 1, None, -1, None, -1, 3, 4]"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "cache: LRUCache | None = None\n",
- "result: list[int | None] = []\n",
+ "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",
- " result.append(None)\n",
+ " results.append(None)\n",
" elif op == \"get\" and cache is not None:\n",
- " result.append(cache.get(inputs[i][0]))\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",
- " result.append(None)\n",
- "result"
+ " results.append(None)\n",
+ "results"
]
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "test",
"metadata": {},
"outputs": [],
"source": [
- "assert result == expected"
+ "assert results == expected"
]
}
],
@@ -79,7 +69,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
+ "nbconvert_exporter": "python3",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
diff --git a/leetcode/lru_cache/tests.py b/leetcode/lru_cache/tests.py
index 93b17a6..ad8b5e4 100644
--- a/leetcode/lru_cache/tests.py
+++ b/leetcode/lru_cache/tests.py
@@ -1,5 +1,4 @@
import pytest
-from loguru import logger
from leetcode_py.test_utils import logged_test
@@ -14,26 +13,20 @@ class TestLRUCache:
["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(self, operations: list[str], inputs: list[list[int]], expected: list[int | None]):
- logger.info(f"Testing LRU Cache with operations: {operations}")
- logger.info(f"Inputs: {inputs}")
- logger.info(f"Expected: {expected}")
-
cache: LRUCache | None = None
- result: list[int | None] = []
+ results: list[int | None] = []
for i, op in enumerate(operations):
if op == "LRUCache":
cache = LRUCache(inputs[i][0])
- result.append(None)
+ results.append(None)
elif op == "get" and cache is not None:
- result.append(cache.get(inputs[i][0]))
+ results.append(cache.get(inputs[i][0]))
elif op == "put" and cache is not None:
cache.put(inputs[i][0], inputs[i][1])
- result.append(None)
-
- logger.info(f"Result: {result}")
- assert result == expected
+ results.append(None)
+ assert results == expected
diff --git a/leetcode/reverse_linked_list_ii/README.md b/leetcode/reverse_linked_list_ii/README.md
index 2a9c21a..7c005a6 100644
--- a/leetcode/reverse_linked_list_ii/README.md
+++ b/leetcode/reverse_linked_list_ii/README.md
@@ -1,13 +1,14 @@
-# 92. Reverse Linked List II
+# Reverse Linked List II
**Difficulty:** Medium
**Topics:** Linked List
**Tags:** grind-75
+
**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.
+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
@@ -27,7 +28,9 @@ Output: [5]
## Constraints
-- The number of nodes in the list is n.
+- 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/playground.ipynb b/leetcode/reverse_linked_list_ii/playground.ipynb
index 7b68b1f..87d95ac 100644
--- a/leetcode/reverse_linked_list_ii/playground.ipynb
+++ b/leetcode/reverse_linked_list_ii/playground.ipynb
@@ -6,60 +6,31 @@
"id": "imports",
"metadata": {},
"outputs": [],
- "source": [
- "from solution import Solution\n",
- "\n",
- "from leetcode_py import ListNode"
- ]
+ "source": ["from solution import Solution\n\nfrom leetcode_py import ListNode"]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
- "source": [
- "# Example test case\n",
- "head = ListNode.from_list([1, 2, 3, 4, 5])\n",
- "left = 2\n",
- "right = 4\n",
- "expected = ListNode.from_list([1, 4, 3, 2, 5])"
- ]
+ "source": ["# Example test case\nhead_list = [1, 2, 3, 4, 5]\nhead = ListNode.from_list(head_list)\nleft, right = 2, 4\nexpected = ListNode.from_list([1, 4, 3, 2, 5])"]
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "execute",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "1 -> 4 -> 3 -> 2 -> 5"
- ],
- "text/plain": [
- "ListNode([1, 4, 3, 2, 5])"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "result = Solution().reverse_between(head, left, right)\n",
- "result"
- ]
+ "outputs": [],
+ "source": ["result = Solution().reverse_between(head, left, right)\nresult"]
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "test",
"metadata": {},
"outputs": [],
- "source": [
- "assert result == expected"
- ]
+ "source": ["assert result == expected"]
}
],
"metadata": {
diff --git a/leetcode/reverse_linked_list_ii/tests.py b/leetcode/reverse_linked_list_ii/tests.py
index d363d84..156793f 100644
--- a/leetcode/reverse_linked_list_ii/tests.py
+++ b/leetcode/reverse_linked_list_ii/tests.py
@@ -1,5 +1,4 @@
import pytest
-from loguru import logger
from leetcode_py import ListNode
from leetcode_py.test_utils import logged_test
@@ -7,31 +6,19 @@
from .solution import Solution
-class TestReverseLinkedListII:
+class TestSolution:
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]),
- ([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]),
- ],
+ [([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]
):
- logger.info(f"Testing with head_list={head_list}, left={left}, right={right}")
head = ListNode.from_list(head_list)
expected = ListNode.from_list(expected_list)
result = self.solution.reverse_between(head, left, right)
- logger.success(f"Got result: {result.to_list() if result else []}")
assert result == expected
diff --git a/leetcode/spiral_matrix/README.md b/leetcode/spiral_matrix/README.md
index 65ecc16..e2aab0f 100644
--- a/leetcode/spiral_matrix/README.md
+++ b/leetcode/spiral_matrix/README.md
@@ -1,38 +1,31 @@
-# 54. Spiral Matrix
+# 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.
+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]
```
diff --git a/leetcode/spiral_matrix/playground.ipynb b/leetcode/spiral_matrix/playground.ipynb
index bbf61a8..318e556 100644
--- a/leetcode/spiral_matrix/playground.ipynb
+++ b/leetcode/spiral_matrix/playground.ipynb
@@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
@@ -12,7 +12,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "setup",
"metadata": {},
"outputs": [],
@@ -24,29 +24,17 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "execute",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[1, 2, 3, 6, 9, 8, 7, 4, 5]"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
- "result = Solution().spiral_order(matrix)\n",
- "result"
+ "result = Solution().spiral_order(matrix)\nresult"
]
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "test",
"metadata": {},
"outputs": [],
@@ -69,7 +57,7 @@
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
- "nbconvert_exporter": "python",
+ "nbconvert_exporter": "python3",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
diff --git a/leetcode/spiral_matrix/tests.py b/leetcode/spiral_matrix/tests.py
index 72f6660..38c4eea 100644
--- a/leetcode/spiral_matrix/tests.py
+++ b/leetcode/spiral_matrix/tests.py
@@ -1,12 +1,11 @@
import pytest
-from loguru import logger
from leetcode_py.test_utils import logged_test
from .solution import Solution
-class TestSpiralMatrix:
+class TestSolution:
def setup_method(self):
self.solution = Solution()
@@ -15,39 +14,9 @@ def setup_method(self):
[
([[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)
diff --git a/leetcode_py/test_utils.py b/leetcode_py/test_utils.py
index 2eba88d..84817b5 100644
--- a/leetcode_py/test_utils.py
+++ b/leetcode_py/test_utils.py
@@ -2,9 +2,34 @@
from loguru import logger
-# # Configure logger to disable backtrace
-# logger.remove()
-# logger.add(sys.stderr, backtrace=False)
+
+def _format_arg(arg):
+ """Format a single argument for logging."""
+ if isinstance(arg, type):
+ return f"solution={arg.__name__}"
+ return repr(arg)
+
+
+def _format_kwarg(k, v):
+ """Format a keyword argument for logging."""
+ if isinstance(v, type):
+ return f"{k}={v.__name__}"
+ return f"{k}={repr(v)}"
+
+
+def _parse_test_params(args, kwargs):
+ """Parse test parameters, skipping self and formatting cleanly."""
+ params: list[str] = []
+
+ # Skip first arg if it's 'self' (test class instance)
+ start_idx = (
+ 1 if args and hasattr(args[0], "__class__") and "Test" in args[0].__class__.__name__ else 0
+ )
+
+ params.extend(_format_arg(arg) for arg in args[start_idx:])
+ params.extend(_format_kwarg(k, v) for k, v in kwargs.items())
+
+ return params
def logged_test(func):
@@ -13,6 +38,14 @@ def logged_test(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("")
+
+ params = _parse_test_params(args, kwargs)
+
+ if params:
+ logger.debug(f"Running {func.__name__}({', '.join(params)})")
+ else:
+ logger.debug(f"Running {func.__name__}()")
+
try:
result = func(*args, **kwargs)
logger.debug("Test passed! ✨")
diff --git a/leetcode_py/tools/generator.py b/leetcode_py/tools/generator.py
index ebacbff..b18c4d1 100644
--- a/leetcode_py/tools/generator.py
+++ b/leetcode_py/tools/generator.py
@@ -136,7 +136,13 @@ def auto_set_dummy_return(self, data: Dict[str, Any]) -> Dict[str, Any]:
def convert_arrays_to_nested(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Convert arrays to cookiecutter-friendly nested format."""
extra_context = data.copy()
- array_fields = ["examples", "test_cases", "tags"]
+ array_fields = [
+ "tags",
+ "readme_examples",
+ "solution_methods",
+ "test_helper_methods",
+ "test_methods",
+ ]
for field in array_fields:
if field in data and isinstance(data[field], list):
extra_context[f"_{field}"] = {"list": data[field]}
diff --git a/tests/tools/test_generator.py b/tests/tools/test_generator.py
index f8020c0..9dd62ec 100644
--- a/tests/tools/test_generator.py
+++ b/tests/tools/test_generator.py
@@ -61,48 +61,48 @@ def test_auto_set_dummy_return_no_return_type(self):
def test_convert_arrays_to_nested(self):
"""Test converting arrays to nested format."""
data: dict[str, Any] = {
- "examples": [{"input": "test"}],
+ "readme_examples": [{"content": "test"}],
"tags": ["grind-75"],
"other_field": "value",
}
result = self.generator.convert_arrays_to_nested(data)
- assert "_examples" in result
- assert result["_examples"] == {"list": [{"input": "test"}]}
+ assert "_readme_examples" in result
+ assert result["_readme_examples"] == {"list": [{"content": "test"}]}
assert "_tags" in result
assert result["_tags"] == {"list": ["grind-75"]}
- assert "examples" not in result
+ assert "readme_examples" not in result
assert "tags" not in result
assert result["other_field"] == "value"
def test_convert_arrays_to_nested_partial_arrays(self):
"""Test converting only some arrays to nested format."""
data: dict[str, Any] = {
- "examples": [{"input": "test"}],
- "test_cases": [[1, 2, 3]],
+ "solution_methods": [{"name": "test"}],
+ "test_methods": [[1, 2, 3]],
"other_list": ["not", "converted"], # Not in array_fields
"string_field": "value",
}
result = self.generator.convert_arrays_to_nested(data)
- assert "_examples" in result
- assert "_test_cases" in result
+ assert "_solution_methods" in result
+ assert "_test_methods" in result
assert "other_list" in result # Should remain unchanged
assert result["other_list"] == ["not", "converted"]
assert result["string_field"] == "value"
def test_convert_arrays_to_nested_non_list_values(self):
"""Test converting arrays when field exists but is not a list."""
- data: dict[str, Any] = {"examples": "not a list", "tags": None, "test_cases": 123}
+ data: dict[str, Any] = {"readme_examples": "not a list", "tags": None, "solution_methods": 123}
result = self.generator.convert_arrays_to_nested(data)
# Non-list values should remain unchanged
- assert result["examples"] == "not a list"
+ assert result["readme_examples"] == "not a list"
assert result["tags"] is None
- assert result["test_cases"] == 123
+ assert result["solution_methods"] == 123
def test_check_overwrite_permission_force(self):
"""Test overwrite permission with force flag."""