diff --git a/.cursor/.dev/1-update-cookiecutter-test-template.md b/.cursor/.dev/1-update-cookiecutter-test-template.md deleted file mode 100644 index 2c57cc4..0000000 --- a/.cursor/.dev/1-update-cookiecutter-test-template.md +++ /dev/null @@ -1,166 +0,0 @@ -# Plan: Update CookieCutter Test Template to Use For Loop Instead of Parametrize - -## Overview - -Update the cookiecutter template for `test_solution.py` to use a for loop approach for test cases instead of the current `@pytest.mark.parametrize` decorator. This will make the test structure more explicit and easier to read. - -## Current State Analysis - -### Current Template Structure - -- Uses `@pytest.mark.parametrize` decorator with `method.parametrize` and `method.test_cases` -- Test cases are stored as a single string in JSON: `"[([1, 2, 3], [1, 2, 3], True), ...]"` -- Generated test method signature includes all parameters: `(self, p_list: list[int | None], q_list: list[int | None], expected: bool)` - -### Current JSON Structure - -```json -"_test_methods": { - "list": [ - { - "name": "test_is_same_tree", - "signature": "(self, p_list: list[int | None], q_list: list[int | None], expected: bool)", - "parametrize": "p_list, q_list, expected", - "test_cases": "[([1, 2, 3], [1, 2, 3], True), ([1, 2], [1, None, 2], False), ...]", - "body": "result = run_is_same_tree(Solution, p_list, q_list)\nassert_is_same_tree(result, expected)" - } - ] -} -``` - -## Target State - -### New Template Structure - -- Keep `@pytest.mark.parametrize` decorator -- Use a for loop to iterate through individual test cases from the list -- Test method signature remains the same -- Only change: `test_cases` becomes a list instead of a string - -### New JSON Structure - -```json -"_test_methods": { - "list": [ - { - "name": "test_is_same_tree", - "signature": "(self, p_list: list[int | None], q_list: list[int | None], expected: bool)", - "parametrize": "p_list, q_list, expected", - "test_cases": { - "list": [ - "([1, 2, 3], [1, 2, 3], True)", - "([1, 2], [1, None, 2], False)", - "([1, 2, 1], [1, 1, 2], False)" - ] - }, - "body": "result = run_is_same_tree(Solution, p_list, q_list)\nassert_is_same_tree(result, expected)" - } - ] -} -``` - -## Implementation Plan - -### Phase 1: Update CookieCutter Template - -1. **Update `test_solution.py` template** - - Change from `{{method.test_cases}}` to `{{method.test_cases.list | join(', ')}}` - - This follows the existing pattern used by other list fields in the template - - Template line 30: `@pytest.mark.parametrize("{{method.parametrize}}", [{{method.test_cases.list | join(', ')}}])` - - **✅ Validated:** Correctly follows existing "list" pattern used by `_tags`, `_readme_examples`, etc. - -### Phase 2: Create JSON Migration Script - -1. **Create `migrate_test_cases.py` script** - - Parse existing JSON files in `leetcode_py/cli/resources/leetcode/json/problems/` - - Convert `test_cases` string to `{"list": ["test_case1", "test_case2", ...]}` format - - Keep all other fields exactly the same - - Update only the `test_cases` field structure - -### Phase 3: Update JSON Files - -1. **Run migration script on all existing problem JSON files** - - Process all 107 JSON files in the problems directory - - Create backup of original files - - Validate migrated JSON structure - -### Phase 4: Testing and Validation - -1. **Test generated templates** - - Generate a test problem using updated template - - Verify test cases run correctly with parametrize approach - - Ensure JSON list format works with pytest parametrize - -2. **Comprehensive validation with all problems** - - Copy additional LeetCode problems to `.cache/leetcode` (if not already present) - - Regenerate ALL problems from new design using updated template - - Copy existing solutions from cache to regenerated problems - - Run tests on all regenerated problems to ensure they still pass - - Verify no regressions in test functionality - - Document any issues found and fix them - -### Phase 5: Update Documentation - -1. **Update problem creation documentation** - - Update `.cursor/commands/problem-creation.md` to reflect new JSON structure - - Change `test_cases` format from string to `{"list": [...]}` in examples - - Update template examples to show new format - - Add note about migration for existing problems -2. **Update any other related documentation** - - Check for other docs that reference `test_cases` format - - Update examples in README or other guides if needed - -## Benefits of New Approach - -1. **Cleaner JSON Structure**: Test cases as `{"list": [...]}` object instead of single string -2. **Better Maintainability**: Easier to edit individual test cases in JSON files -3. **No Parsing Issues**: Avoids tuple conversion and complex JSON parsing -4. **Consistency**: Aligns with other JSON list fields in the structure -5. **Template Consistency**: Uses same pattern as other list fields (`| join(', ')`) - -## Files to Modify - -### Template Files - -- `leetcode_py/cli/resources/leetcode/{{cookiecutter.problem_name}}/test_solution.py` - -### Migration Script - -- `migrate_test_cases.py` (new file) - -### JSON Files - -- All files in `leetcode_py/cli/resources/leetcode/json/problems/` (107 files) - -### Generation Code - -- **No changes needed** - The `test_cases` field is created manually, not by automated code -- The existing "list" pattern is already established and working correctly -- Phase 4 removed after investigation showed no automated generation code exists - -## Implementation Steps - -1. ✅ Create this plan document -2. ✅ Update cookiecutter template -3. ✅ Create migration script -4. ✅ Run migration on all JSON files -5. ✅ Test updated template generation -6. ✅ Validate all tests still work -7. 🔄 Update documentation (separate step - do not combine with implementation) - -## Risk Mitigation - -1. **Backup Strategy**: Create backups of all JSON files before migration -2. **Incremental Testing**: Test migration on a few files first -3. **Rollback Plan**: Keep original template and migration script for rollback -4. **Validation**: Ensure all existing tests still pass after migration - -## Success Criteria - -1. All existing JSON files successfully migrated to new list format -2. Generated test templates work with JSON array format for test_cases -3. All existing tests continue to pass with parametrize approach -4. JSON structure is cleaner and more maintainable -5. Template generation works correctly for new problems -6. **All regenerated problems pass their tests** (comprehensive validation) -7. No regressions in test functionality across the entire problem set diff --git a/.cursor/.dev/next_problem.py b/.cursor/.dev/next_problem.py index 7adc9e6..ef5f071 100644 --- a/.cursor/.dev/next_problem.py +++ b/.cursor/.dev/next_problem.py @@ -6,6 +6,7 @@ # Import the problem lists sys.path.append(str(Path(__file__).parent.parent.parent)) from problem_lists import available_lists +from problem_lists.unscrapable import get_unscrapable_numbers from problem_lists.utils import get_existing_problems @@ -15,6 +16,7 @@ def get_next_problem(tag_names=None): tag_names = list(available_lists.keys()) existing_problems = get_existing_problems() + unscrapable_numbers = get_unscrapable_numbers() # Find the list with the lowest missing problems best_list = None @@ -24,7 +26,8 @@ def get_next_problem(tag_names=None): for tag_name, problem_tuples in available_lists.items(): if tag_name in tag_names: problem_numbers = {num for num, _ in problem_tuples} - missing = problem_numbers - existing_problems + # Exclude unscrapable problems from missing problems + missing = problem_numbers - existing_problems - unscrapable_numbers missing_count = len(missing) if missing_count > 0 and missing_count < min_missing: diff --git a/.cursor/.dev/problem_lists/unscrapable.py b/.cursor/.dev/problem_lists/unscrapable.py new file mode 100644 index 0000000..82f54a0 --- /dev/null +++ b/.cursor/.dev/problem_lists/unscrapable.py @@ -0,0 +1,28 @@ +# Unscrapable Problems List +# Problems that cannot be scraped due to being premium, having API issues, or other technical limitations + +# Format: (problem_number, problem_name) +UNSCRAPABLE_PROBLEMS = [ + (252, "meeting-rooms"), + (253, "meeting-rooms-ii"), + (261, "graph-valid-tree"), + (271, "encode-and-decode-strings"), + (323, "number-of-connected-components-in-an-undirected-graph"), + # Add more unscrapable problems as discovered +] + + +# Helper function to check if a problem is unscrapable +def is_unscrapable(problem_number: int) -> bool: + """Check if a problem number is in the unscrapable list.""" + return any(num == problem_number for num, _ in UNSCRAPABLE_PROBLEMS) + + +def is_unscrapable_by_name(problem_name: str) -> bool: + """Check if a problem name is in the unscrapable list.""" + return any(name == problem_name for _, name in UNSCRAPABLE_PROBLEMS) + + +def get_unscrapable_numbers() -> set[int]: + """Get all unscrapable problem numbers as a set.""" + return {num for num, _ in UNSCRAPABLE_PROBLEMS} diff --git a/.cursor/.dev/update_tags.json b/.cursor/.dev/update_tags.json deleted file mode 100644 index 503c7c3..0000000 --- a/.cursor/.dev/update_tags.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "neetcode-150": [ - "binary_tree_maximum_path_sum", - "find_minimum_in_rotated_sorted_array", - "number_of_1_bits", - "reorder_list", - "reverse_bits" - ], - "algo-master-75": ["binary_tree_maximum_path_sum"], - "blind-75": [ - "binary_tree_maximum_path_sum", - "find_minimum_in_rotated_sorted_array", - "number_of_1_bits", - "reorder_list", - "reverse_bits" - ] -} diff --git a/.cursor/.dev/update_tags.py b/.cursor/.dev/update_tags.py index e725c52..67c2c5c 100644 --- a/.cursor/.dev/update_tags.py +++ b/.cursor/.dev/update_tags.py @@ -67,13 +67,14 @@ def update_tags(tag_names=None): # Only include missing problems in the update if missing_problems: update_tags[tag_name] = sorted(missing_problems) - print( - f"{tag_name}: Missing {len(missing_problems)} problems: {sorted(missing_problems)}" - ) + missing_list = sorted(missing_problems) + print(f"{tag_name}: Missing {len(missing_problems)} problems: {missing_list}") if removed_problems: + removed_list = sorted(removed_problems) print( - f"{tag_name}: Removed {len(removed_problems)} problems: {sorted(removed_problems)} (not included in update)" + f"{tag_name}: Removed {len(removed_problems)} problems: {removed_list} " + f"(not included in update)" ) if not changes_found: diff --git a/.cursor/commands/batch-problem-creation.md b/.cursor/commands/batch-problem-creation.md new file mode 100644 index 0000000..203240d --- /dev/null +++ b/.cursor/commands/batch-problem-creation.md @@ -0,0 +1,220 @@ +# Batch Problem Creation Command + +## Assistant Workflow + +When user requests **batch creation of multiple problems**, the assistant will: + +1. **Get count from user** (default: 5 problems) +2. **Loop through each problem** following the complete workflow +3. **For each problem**: + - Find next problem via `poetry run python .cursor/.dev/next_problem.py` + - Follow all steps from `.cursor/commands/problem-creation.md` + - **MANDATORY**: Read and follow `.cursor/commands/test-quality-assurance.md` for quality verification +4. **Provide batch summary** at the end + +**CRITICAL INSTRUCTION**: You MUST read the test-quality-assurance.md file before executing quality assurance for any problem. Do not rely on memory or assumptions about the workflow. + +## High-Level Process + +### Step 1: Initialize Batch + +- Ask user for count (default: 5) +- Confirm batch creation parameters +- Set up progress tracking + +### Step 2: Problem Creation Loop + +For each problem (1 to count): + +#### 2.1: Find Next Problem + +```bash +poetry run python .cursor/.dev/next_problem.py +``` + +- Extract problem number and name from output +- Log progress: "Problem X/Count: #NUMBER - NAME" +- **Note**: The script automatically excludes unscrapable problems (premium, API issues, etc.) + +#### 2.2: Follow Problem Creation Workflow + +Execute complete workflow from `.cursor/commands/problem-creation.md`: + +1. **Scrape** problem data using `poetry run lcpy scrape` +2. **Transform** data into proper JSON template format +3. **Include images** - Extract image URLs and add to readme_examples +4. **Create** JSON file in `leetcode_py/cli/resources/leetcode/json/problems/{problem_name}.json` +5. **Update** Makefile with `PROBLEM ?= {problem_name}` +6. **Generate** problem structure using `make p-gen` +7. **Verify** with `make p-lint` and fix template issues +8. **Iterate** if needed: re-run `make p-gen PROBLEM={problem_name} FORCE=1` and `make p-lint` + +#### 2.3: Implement Optimal Solution + +**CRITICAL**: Before running quality assurance, implement the optimal solution: + +1. **Implement solution**: Write the optimal algorithm in `solution.py` - implement only 1 solution in the `Solution` class, no need to add more classes +2. **Verify correctness**: Solution must handle all test cases correctly + +#### 2.4: Quality Assurance & Reproducibility Verification + +**MANDATORY**: You MUST read and follow the complete workflow from `.cursor/commands/test-quality-assurance.md` for EVERY problem. + +**REQUIRED ACTION**: Before proceeding with quality assurance, you MUST: + +1. **Read the file**: `read_file /Users/wisl/Desktop/vault/personal-repo/leetcode-py/.cursor/commands/test-quality-assurance.md` +2. **Follow the exact 4-step process** described in that file +3. **Execute each step** as specified in the test-quality-assurance.md workflow + +**CRITICAL**: Do NOT proceed without reading the test-quality-assurance.md file first. The workflow includes specific backup, regenerate, and restore steps that must be followed exactly. + +### Step 3: Batch Summary + +Provide comprehensive summary: + +- Total problems created +- Success rate +- Failed problems (if any) with reasons +- Time taken +- Next steps for any failures + +## Example Assistant Response + +``` +I'll help you create 5 problems in batch. Let me start the process: + +=== Batch Problem Creation Started === +Creating 5 problems... + +=== Problem 1/5 === +Finding next problem... +Running: poetry run python .cursor/.dev/next_problem.py +Next problem: Problem #123 - Word Ladder +Processing: #123 - Word Ladder + +Following problem creation workflow: +1. Scraping problem data... + Running: poetry run lcpy scrape -n 123 + ✓ Scraped successfully + +2. Creating JSON template... + ✓ JSON template created with images included + ✓ Saved to leetcode_py/cli/resources/leetcode/json/problems/word_ladder.json + +3. Updating Makefile... + ✓ Updated PROBLEM ?= word_ladder + +4. Generating problem structure... + Running: make p-gen PROBLEM=word_ladder + ✓ Problem structure generated + +5. Verifying with linting... + Running: make p-lint PROBLEM=word_ladder + ✓ Linting passed + +6. Implementing optimal solution... + ✓ Solution implemented with multiple approaches + ✓ Parametrized testing configured + +7. Running quality assurance... + Running: make p-test PROBLEM=word_ladder + ✓ Tests passed (15 test cases) + ✓ Quality assurance completed + +=== Problem 2/5 === +[Continue with same process...] + +=== Batch Summary === +Total Problems Created: 5/5 +Success Rate: 100% +Failed Problems: None +Time Taken: 12 minutes +Next Steps: All problems created successfully! + +=== Batch Problem Creation Completed === +``` + +## Error Handling + +### If a problem fails: + +1. **Log the failure** with specific reason +2. **Continue with next problem** (don't stop the batch) +3. **Add to retry list** for manual intervention later +4. **Update success rate** accordingly + +### Common failure scenarios: + +- Scraping fails (problem not found, premium problem, API issues) +- JSON template issues (invalid syntax) +- Generation fails (missing dependencies) +- Tests fail (incorrect expected values) +- Linting errors (template problems) +- Unscrapable problems (automatically excluded by next_problem.py) + +### Recovery actions: + +- **Scraping**: Try alternative parameters or manual data entry +- **JSON**: Fix syntax and regenerate +- **Generation**: Check Makefile and dependencies +- **Tests**: Update expected values in JSON template +- **Linting**: Fix template issues and regenerate +- **Unscrapable**: Add to `.cursor/.dev/problem_lists/unscrapable.py` and continue with next problem + +**CRITICAL**: Never edit generated files directly (helpers.py, test_solution.py, README.md, etc.). Always fix issues in the JSON template and regenerate to ensure reproducibility. The ONLY exception is solution.py implementation - you may edit this file directly to implement the optimal solution. + +## Success Criteria + +Each problem must meet: + +- ✅ All files generated (README.md, solution.py, test_solution.py, helpers.py, playground.ipynb, **init**.py) +- ✅ **Optimal solution implemented** with correct algorithm +- ✅ **Multiple solution approaches** (e.g., Solution, SolutionOptimized) +- ✅ **Parametrized testing** for all solution approaches +- ✅ Linting passes without errors +- ✅ All tests pass +- ✅ **test-quality-assurance.md file read and complete workflow executed** +- ✅ Minimum 12 comprehensive test cases (verified via check_test_cases tool) +- ✅ **Test reproducibility verified** (tests pass consistently) +- ✅ Images included in README if available +- ✅ Proper JSON template created and updated +- ✅ Code coverage includes edge cases +- ✅ **Original solution preserved** after quality assurance process + +## Batch Parameters + +- **Count**: Number of problems to create (default: 5) +- **Tags**: Optional filter for specific problem lists +- **Force**: Whether to overwrite existing problems +- **Dry Run**: Preview mode without actual creation + +## Unscrapable Problems Management + +### Adding Unscrapable Problems + +When encountering a problem that cannot be scraped (premium, API issues, etc.): + +1. **Add to unscrapable list**: Update `.cursor/.dev/problem_lists/unscrapable.py` +2. **Format**: `(problem_number, "problem-name")` +3. **Continue batch**: The next_problem.py script will automatically skip unscrapable problems + +### Example unscrapable.py entry: + +```python +UNSCRAPABLE_PROBLEMS = [ + (252, "meeting-rooms"), + (253, "meeting-rooms-ii"), # if also unscrapable + # Add more as discovered +] +``` + +## Notes + +- **Sequential Processing**: Create one problem at a time to avoid conflicts +- **Progress Tracking**: Show clear progress indicators +- **Error Recovery**: Continue batch even if individual problems fail +- **Solution Implementation**: **MUST** implement optimal solution before quality assurance +- **Quality Focus**: Ensure each problem meets all quality standards +- **Reproducibility**: Verify tests pass consistently with implemented solutions +- **Documentation**: Maintain clear logs of the entire process +- **Unscrapable Handling**: Automatically exclude known unscrapable problems diff --git a/.cursor/commands/problem-creation.md b/.cursor/commands/problem-creation.md index 19277ad..cbcf7af 100644 --- a/.cursor/commands/problem-creation.md +++ b/.cursor/commands/problem-creation.md @@ -50,6 +50,12 @@ Required fields for `leetcode_py/cli/resources/leetcode/json/problems/{problem_n - `playground_assertion`: Use single quotes for string literals - Double quotes in JSON + cookiecutter + Jupyter notebook = triple escaping issues +**Test Cases Format:** + +- `test_cases`: Use structured format with `{"list": ["..."]}` instead of string arrays +- Each test case should be a string representation of the tuple/parameters +- Example: `{"list": ["('input1', 'input2', expected)", "('input3', 'input4', expected)"]}` + **IMPORTANT: Create actual JSON files, not JSON5** The template below uses JSON5 format with comments for documentation purposes only. When creating the actual `.json` file, you must: @@ -183,9 +189,30 @@ The template below uses JSON5 format with comments for documentation purposes on parametrize: "s, t, expected", // pytest parametrize parameters // For tree: "root_list, expected_list" // For design: "operations, inputs, expected" - test_cases: "[('anagram', 'nagaram', True), ('rat', 'car', False), ('listen', 'silent', True), ('hello', 'bello', False), ('', '', True), ('a', 'a', True), ('a', 'b', False), ('ab', 'ba', True), ('abc', 'bca', True), ('abc', 'def', False), ('aab', 'abb', False), ('aabbcc', 'abcabc', True), ('abcd', 'abcde', False), ('race', 'care', True), ('elbow', 'below', True), ('study', 'dusty', True), ('night', 'thing', True), ('stressed', 'desserts', True)]", - // For tree: "[([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1]), ([2, 1, 3], [2, 3, 1]), ([], [])]" - // For design: "[(['LRUCache', 'put', 'get'], [[2], [1, 1], [1]], [None, None, 1])]" + test_cases: { + list: [ + "('anagram', 'nagaram', True)", + "('rat', 'car', False)", + "('listen', 'silent', True)", + "('hello', 'bello', False)", + "('', '', True)", + "('a', 'a', True)", + "('a', 'b', False)", + "('ab', 'ba', True)", + "('abc', 'bca', True)", + "('abc', 'def', False)", + "('aab', 'abb', False)", + "('aabbcc', 'abcabc', True)", + "('abcd', 'abcde', False)", + "('race', 'care', True)", + "('elbow', 'below', True)", + "('study', 'dusty', True)", + "('night', 'thing', True)", + "('stressed', 'desserts', True)", + ], + }, + // For tree: {"list": ["([4, 2, 7, 1, 3, 6, 9], [4, 7, 2, 9, 6, 3, 1])", "([2, 1, 3], [2, 3, 1])", "([], [])"]} + // For design: {"list": ["(['LRUCache', 'put', 'get'], [[2], [1, 1], [1]], [None, None, 1])"]} body: " result = run_is_anagram(Solution, s, t)\n assert_is_anagram(result, expected)", // For tree: " result = run_invert_tree(Solution, root_list)\n assert_invert_tree(result, expected_list)" // For design: " result, _ = run_lru_cache(LRUCache, operations, inputs)\n assert_lru_cache(result, expected)" @@ -218,7 +245,7 @@ The template below uses JSON5 format with comments for documentation purposes on // - solution_class_name: "Solution" // - solution_imports: "" // - Simple method signatures: "(self, s: str, t: str) -> bool" - // - Basic test cases: string/number parameters + // - Basic test cases: structured format with {"list": ["..."]} // - Playground: single quotes for strings // // TREE PROBLEMS (invert_binary_tree): @@ -226,7 +253,7 @@ The template below uses JSON5 format with comments for documentation purposes on // - solution_imports: "from leetcode_py import TreeNode" // - Tree method signatures: "(self, root: TreeNode[int] | None) -> TreeNode[int] | None" // - Helper functions use TreeNode.from_list() - // - Test cases: list representations of trees + // - Test cases: structured format with list representations of trees // - Playground: TreeNode imports and list conversions // // LINKED LIST PROBLEMS (merge_two_sorted_lists): @@ -234,13 +261,13 @@ The template below uses JSON5 format with comments for documentation purposes on // - solution_imports: "from leetcode_py import ListNode" // - List method signatures: "(self, list1: ListNode[int] | None, list2: ListNode[int] | None) -> ListNode[int] | None" // - Helper functions use ListNode.from_list() - // - Test cases: list representations of linked lists + // - Test cases: structured format with list representations of linked lists // - Playground: ListNode imports and list conversions // // DESIGN PROBLEMS (lru_cache): // - solution_class_name: "LRUCache" (custom class name) // - Multiple methods including __init__ - // - Operations-based testing: operations, inputs, expected arrays + // - Operations-based testing: structured format with operations, inputs, expected arrays // - Complex test body with operation loops // - Helper functions return (results, instance) for debugging // - Playground: print results, return instance @@ -250,7 +277,7 @@ The template below uses JSON5 format with comments for documentation purposes on // - solution_class_name: "Trie(DictTree[str])" (with inheritance) // - solution_imports: "from leetcode_py.data_structures import DictTree, RecursiveDict" // - Custom class with inheritance from DictTree - // - Operations-based testing like design problems + // - Operations-based testing with structured format like design problems // - Helper functions return (results, instance) for debugging // // MULTIPLE SOLUTIONS (invert_binary_tree, lru_cache): @@ -274,9 +301,17 @@ The template below uses JSON5 format with comments for documentation purposes on - **problem_name**: snake_case (e.g., "two_sum", "valid_palindrome") - **solution_class_name**: Usually "Solution", except for design problems (e.g., "LRUCache") - **test_class_name**: PascalCase (e.g., "TwoSum", "ValidPalindrome") -- **method_name**: snake_case (e.g., "two_sum", "is_palindrome") +- **method_name**: snake_case (e.g., "two_sum", "is_palindrome", "character_replacement") - **parameters**: Use snake_case for all parameter names +**CRITICAL: Method Naming Convention** + +- Always convert LeetCode method names from camelCase to snake_case +- Example: `characterReplacement` → `character_replacement` +- Example: `isSubtree` → `is_subtree` +- Example: `countSubstrings` → `count_substrings` +- This ensures Python convention compliance and consistency across the codebase + ### PascalCase Rules for Properties When creating JSON properties that use PascalCase (solution_class_name, test_class_name): diff --git a/.cursor/commands/test-quality-assurance.md b/.cursor/commands/test-quality-assurance.md index cff96ad..8a2ae0d 100644 --- a/.cursor/commands/test-quality-assurance.md +++ b/.cursor/commands/test-quality-assurance.md @@ -1,43 +1,51 @@ # Test Quality Assurance Rules -## Simple Enhancement Workflow - -When user requests test case enhancement or **test reproducibility verification**: +## CRITICAL: Follow These Steps EXACTLY - No Deviations ### 1. Problem Resolution - Use active file context or user-provided problem name - If unclear, run: `poetry run python -m leetcode_py.tools.check_test_cases --threshold=10 --max=1` -### 2. Enhancement Process +### 2. Test Reproducibility Verification Process + +**MANDATORY 6-Step Process - Execute in Order:** ```bash -# Simple 4-step process: -# 1. Update JSON template with more test cases (12-15 total) -# 2. Backup original -mv leetcode/{problem_name} .cache/leetcode/{problem_name} -# 3. Regenerate with enhanced tests -make p-gen PROBLEM={problem_name} FORCE=1 && make p-lint PROBLEM={problem_name} -# 4. Restore original solution, keep enhanced tests -cp .cache/leetcode/{problem_name}/solution.py leetcode/{problem_name}/solution.py -``` +# Step 1: Backup original files +cp -r leetcode/{problem_name} leetcode/{problem_name}_backup -### 3. Verification +# Step 2: Regenerate from JSON template (use Makefile, NOT poetry run) +make p-gen PROBLEM={problem_name} FORCE=1 -- Run `make p-test PROBLEM={problem_name}` -- Fix any incorrect expected values in test cases -- Update JSON template with corrections +# Step 3: Restore original solution ONLY +cp leetcode/{problem_name}_backup/solution.py leetcode/{problem_name}/solution.py -### 4. Restore Backup +# Step 4: Verify linting pass (CRITICAL for CI) +make p-lint PROBLEM={problem_name} -```bash -# Copy enhanced test_solution.py to backup -cp leetcode/{problem_name}/test_solution.py .cache/leetcode/{problem_name}/ -# Restore all original files (preserves user edits) -rm -rf leetcode/{problem_name} -mv .cache/leetcode/{problem_name} leetcode/{problem_name} +# Step 5: Verify tests pass (expected to fail if solution is incomplete) +make p-test PROBLEM={problem_name} + +# Step 6: Cleanup +rm -rf leetcode/{problem_name}_backup ``` +### 3. What NOT to Do + +- ❌ **NEVER edit cookiecutter templates** (`{{cookiecutter.problem_name}}/` files) +- ❌ **NEVER use `poetry run python -m leetcode_py.cli.main gen`** - use `make p-gen` instead +- ❌ **NEVER modify helpers.py manually** - let regeneration handle it +- ❌ **NEVER skip mypy verification** - this is the main CI issue +- ❌ **NEVER assume tests will pass** - they may fail if solution is incomplete + +### 4. What to Do + +- ✅ **ALWAYS use `make p-gen PROBLEM={problem_name} FORCE=1`** for regeneration +- ✅ **ALWAYS verify mypy passes** before considering task complete +- ✅ **ALWAYS restore original solution** after regeneration +- ✅ **ALWAYS check JSON template** if mypy fails (look for `assert_assert_` bugs) + ## Test Case Standards ### Coverage Requirements @@ -84,24 +92,34 @@ poetry run python -m leetcode_py.tools.check_test_cases --threshold=12 make p-gen PROBLEM={problem_name} FORCE=1 ``` -## Test Reproducibility Verification +## Common Issues & Solutions -Use this same workflow when CI tests fail due to reproducibility issues: +### Issue: `assert_assert_missing_number` Error -**Process Name**: Test Reproducibility Verification +**Cause**: JSON template has `helpers_assert_name: "assert_missing_number"` but template adds `assert_` prefix +**Solution**: Change JSON to `helpers_assert_name: "missing_number"` so template generates `assert_missing_number` -**When to Use**: +### Issue: mypy Import Errors -- CI test failures in reproducibility checks -- Inconsistent test results between environments -- Missing edge cases causing coverage gaps -- Need to ensure 100% code coverage +**Cause**: Regenerated helpers.py doesn't match test imports +**Solution**: Use `make p-gen` (not poetry run) and verify JSON template is correct + +### Issue: Tests Fail After Regeneration + +**Expected**: Tests may fail if solution is incomplete (returns 0 or placeholder) +**Action**: This is normal - focus on mypy passing, not test results ## Success Criteria -- All tests pass with enhanced test cases -- Minimum 12 comprehensive test cases per problem -- Original solution code preserved -- **Enhanced test cases in final test_solution.py** -- JSON template updated for future regeneration -- **100% code coverage including edge cases** +- ✅ **mypy passes** with no errors (CRITICAL for CI) +- ✅ **Test structure matches JSON template** exactly +- ✅ **Original solution preserved** (user's code intact) +- ✅ **helpers.py generated correctly** (no `assert_assert_` bugs) +- ✅ **Reproducibility verified** (can regenerate consistently) + +## When to Use This Workflow + +- GitHub Actions CI failures due to mypy errors +- Test reproducibility verification requests +- Need to ensure test structure matches JSON template +- CI test failures in reproducibility checks diff --git a/.cursor/commands/update-tags.md b/.cursor/commands/update-tags.md new file mode 100644 index 0000000..f6a5257 --- /dev/null +++ b/.cursor/commands/update-tags.md @@ -0,0 +1,53 @@ +# Update Tags Command + +## Description + +Updates `.tags.json5` by running the `update_tags.py` script, sorting the output alphabetically, and cleaning up temporary files. The LLM handles this process directly to preserve comments in the JSON5 file. + +## Usage + +When user requests "update tags" or "update-tags", the assistant will: + +1. **Run update script**: Execute `poetry run python .cursor/.dev/update_tags.py` +2. **Read output**: Read the generated `.cursor/.dev/update_tags.json` file +3. **Read current tags**: Read the existing `.tags.json5` file to preserve comments +4. **Sort and merge**: Sort the new tags alphabetically by name and merge with existing structure +5. **Update file**: Write the updated tags to `.tags.json5` while preserving comments +6. **Cleanup**: Delete the temporary `.cursor/.dev/update_tags.json` file + +## LLM Workflow + +```python +# 1. Run the update script +poetry run python .cursor/.dev/update_tags.py + +# 2. Read the generated JSON file +with open('.cursor/.dev/update_tags.json', 'r') as f: + new_tags = json.load(f) + +# 3. Read existing .tags.json5 to preserve comments +with open('.tags.json5', 'r') as f: + existing_content = f.read() + +# 4. Sort new tags alphabetically by name +sorted_tags = sorted(new_tags, key=lambda x: x.get('name', '')) + +# 5. Update the .tags.json5 file while preserving comments +# (Implementation depends on JSON5 structure and comment preservation needs) + +# 6. Clean up temporary file +os.remove('.cursor/.dev/update_tags.json') +``` + +## Prerequisites + +- `update_tags.py` script must exist in `.cursor/.dev/` +- The script must output a JSON file to `.cursor/.dev/update_tags.json` +- Assistant must handle JSON5 comment preservation + +## Output + +- Updates `.tags.json5` with alphabetically sorted tags +- Preserves existing comments in the JSON5 file +- Removes temporary `.cursor/.dev/update_tags.json` file +- Provides success/error feedback diff --git a/Makefile b/Makefile index 221c972..2e86b61 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ PYTHON_VERSION = 3.13 -PROBLEM ?= number_of_1_bits +PROBLEM ?= reverse_nodes_in_k_group FORCE ?= 0 COMMA := , @@ -24,7 +24,7 @@ define lint_target poetry run black $(1) poetry run isort $(1) $(if $(filter .,$(1)), \ - poetry run nbqa ruff . --nbqa-exclude="leetcode_py/cli/resources/" --ignore=F401$(COMMA)F821, \ + poetry run nbqa ruff . --nbqa-exclude="leetcode_py/cli/resources/" --ignore=F401$(COMMA)F821 --fix, \ poetry run nbqa ruff $(1) --ignore=F401$(COMMA)F821) poetry run ruff check $(1) --exclude="**/*.ipynb" poetry run mypy $(1) \ diff --git a/README.md b/README.md index 82eeb33..900029d 100644 --- a/README.md +++ b/README.md @@ -40,13 +40,13 @@ A Python package to generate professional LeetCode practice environments. Featur **Current Problem Sets**: -- **grind-75** (75 problems) - Essential coding interview questions from [Grind 75](https://www.techinterviewhandbook.org/grind75/) ✅ Complete -- **grind** (100+ problems) - Extended Grind collection including all Grind 75 plus additional problems 🚧 Partial -- **blind-75** (75 problems) - Original [Blind 75](https://leetcode.com/problem-list/xi4ci4ig/) curated list 🚧 Partial -- **neetcode-150** (150+ problems) - Comprehensive [NeetCode 150](https://neetcode.io/practice) problem set 🚧 Partial -- **algo-master-75** (75 problems) - Curated algorithmic mastery problems 🚧 Partial +- **grind-75** - Essential coding interview questions from [Grind 75](https://www.techinterviewhandbook.org/grind75/) ✅ Complete +- **grind** - Extended Grind collection including all Grind 75 plus additional problems 🚧 Partial +- **blind-75** - Original [Blind 75](https://leetcode.com/problem-list/xi4ci4ig/) curated list 🚧 Partial +- **neetcode-150** - Comprehensive [NeetCode 150](https://neetcode.io/practice) problem set 🚧 Partial +- **algo-master-75** - Curated algorithmic mastery problems 🚧 Partial -**Coverage**: 100+ unique problems across all major coding interview topics and difficulty levels. +**Coverage**: 120+ unique problems across all major coding interview topics and difficulty levels. **Note**: Some problem sets are partially covered. We're actively working to complete all collections. [Contributions welcome!](https://github.com/wisarootl/leetcode-py/blob/main/CONTRIBUTING.md) diff --git a/leetcode/add_two_numbers/README.md b/leetcode/add_two_numbers/README.md new file mode 100644 index 0000000..b684a22 --- /dev/null +++ b/leetcode/add_two_numbers/README.md @@ -0,0 +1,45 @@ +# Add Two Numbers + +**Difficulty:** Medium +**Topics:** Linked List, Math, Recursion +**Tags:** algo-master-75 + +**LeetCode:** [Problem 2](https://leetcode.com/problems/add-two-numbers/description/) + +## Problem Description + +You are given two **non-empty** linked lists representing two non-negative integers. The digits are stored in **reverse order**, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list. + +You may assume the two numbers do not contain any leading zero, except the number 0 itself. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2020/10/02/addtwonumber1.jpg) + +``` +Input: l1 = [2,4,3], l2 = [5,6,4] +Output: [7,0,8] +Explanation: 342 + 465 = 807. +``` + +### Example 2: + +``` +Input: l1 = [0], l2 = [0] +Output: [0] +``` + +### Example 3: + +``` +Input: l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9] +Output: [8,9,9,9,0,0,0,1] +``` + +## Constraints + +- The number of nodes in each linked list is in the range [1, 100]. +- 0 <= Node.val <= 9 +- It is guaranteed that the list represents a number that does not have leading zeros. diff --git a/leetcode/add_two_numbers/__init__.py b/leetcode/add_two_numbers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/add_two_numbers/helpers.py b/leetcode/add_two_numbers/helpers.py new file mode 100644 index 0000000..27f0382 --- /dev/null +++ b/leetcode/add_two_numbers/helpers.py @@ -0,0 +1,14 @@ +from leetcode_py import ListNode + + +def run_add_two_numbers(solution_class: type, l1_vals: list[int], l2_vals: list[int]): + l1 = ListNode.from_list(l1_vals) + l2 = ListNode.from_list(l2_vals) + implementation = solution_class() + return implementation.add_two_numbers(l1, l2) + + +def assert_add_two_numbers(result: ListNode[int] | None, expected_vals: list[int]) -> bool: + expected = ListNode.from_list(expected_vals) + assert result == expected + return True diff --git a/leetcode/add_two_numbers/playground.py b/leetcode/add_two_numbers/playground.py new file mode 100644 index 0000000..8b576b5 --- /dev/null +++ b/leetcode/add_two_numbers/playground.py @@ -0,0 +1,30 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_add_two_numbers, run_add_two_numbers +from solution import Solution + +# %% +# Example test case +l1_vals = [2, 4, 3] +l2_vals = [5, 6, 4] +expected_vals = [7, 0, 8] + +# %% +result = run_add_two_numbers(Solution, l1_vals, l2_vals) +result + +# %% +assert_add_two_numbers(result, expected_vals) diff --git a/leetcode/add_two_numbers/solution.py b/leetcode/add_two_numbers/solution.py new file mode 100644 index 0000000..ca07ce5 --- /dev/null +++ b/leetcode/add_two_numbers/solution.py @@ -0,0 +1,27 @@ +from leetcode_py import ListNode + + +class Solution: + + # Time: O(max(m, n)) + # Space: O(max(m, n)) + def add_two_numbers( + self, l1: ListNode[int] | None, l2: ListNode[int] | None + ) -> ListNode[int] | None: + dummy = ListNode(0) + current = dummy + carry = 0 + + while l1 or l2 or carry: + val1 = l1.val if l1 else 0 + val2 = l2.val if l2 else 0 + + total = val1 + val2 + carry + carry = total // 10 + current.next = ListNode(total % 10) + current = current.next + + l1 = l1.next if l1 else None + l2 = l2.next if l2 else None + + return dummy.next diff --git a/leetcode/add_two_numbers/test_solution.py b/leetcode/add_two_numbers/test_solution.py new file mode 100644 index 0000000..927bfd1 --- /dev/null +++ b/leetcode/add_two_numbers/test_solution.py @@ -0,0 +1,174 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_add_two_numbers, run_add_two_numbers +from .solution import Solution + + +class TestAddTwoNumbers: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "l1_vals, l2_vals, expected_vals", + [ + ([2, 4, 3], [5, 6, 4], [7, 0, 8]), + ([0], [0], [0]), + ([9, 9, 9, 9, 9, 9, 9], [9, 9, 9, 9], [8, 9, 9, 9, 0, 0, 0, 1]), + ([1], [2], [3]), + ([5], [5], [0, 1]), + ([1, 8], [0], [1, 8]), + ([0], [1, 8], [1, 8]), + ([9, 9], [1], [0, 0, 1]), + ([1], [9, 9], [0, 0, 1]), + ([2, 4, 3], [5, 6, 4], [7, 0, 8]), + ( + [ + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ], + [5, 6, 4], + [ + 6, + 6, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ], + ), + ( + [5, 6, 4], + [ + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ], + [ + 6, + 6, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ], + ), + ([9], [9], [8, 1]), + ([9, 9], [9, 9], [8, 9, 1]), + ([1, 2, 3], [4, 5, 6], [5, 7, 9]), + ([7, 8, 9], [1, 2, 3], [8, 0, 3, 1]), + ([1, 2, 3, 4, 5], [6, 7, 8], [7, 9, 1, 5, 5]), + ], + ) + def test_add_two_numbers(self, l1_vals: list[int], l2_vals: list[int], expected_vals: list[int]): + result = run_add_two_numbers(Solution, l1_vals, l2_vals) + assert_add_two_numbers(result, expected_vals) diff --git a/leetcode/counting_bits/README.md b/leetcode/counting_bits/README.md new file mode 100644 index 0000000..b57a666 --- /dev/null +++ b/leetcode/counting_bits/README.md @@ -0,0 +1,47 @@ +# Counting Bits + +**Difficulty:** Easy +**Topics:** Dynamic Programming, Bit Manipulation +**Tags:** blind-75 + +**LeetCode:** [Problem 338](https://leetcode.com/problems/counting-bits/description/) + +## Problem Description + +Given an integer `n`, return _an array_ `ans` _of length_ `n + 1` _such that for each_ `i` _(0 <= i <= n),_ `ans[i]` \*is the **number of\*** `1`**\*'s** in the binary representation of\* `i`. + +## Examples + +### Example 1: + +``` +Input: n = 2 +Output: [0,1,1] +Explanation: +0 --> 0 +1 --> 1 +2 --> 10 +``` + +### Example 2: + +``` +Input: n = 5 +Output: [0,1,1,2,1,2] +Explanation: +0 --> 0 +1 --> 1 +2 --> 10 +3 --> 11 +4 --> 100 +5 --> 101 +``` + +## Constraints + +- 0 <= n <= 10^5 + +**Follow up:** + +- It is very easy to come up with a solution with a runtime of `O(n log n)`. Can you do it in linear time `O(n)` and possibly in a single pass? +- Can you do it without using any built-in function (i.e., like `__builtin_popcount` in C++)? diff --git a/leetcode/counting_bits/__init__.py b/leetcode/counting_bits/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/counting_bits/helpers.py b/leetcode/counting_bits/helpers.py new file mode 100644 index 0000000..e170428 --- /dev/null +++ b/leetcode/counting_bits/helpers.py @@ -0,0 +1,8 @@ +def run_count_bits(solution_class: type, n: int): + implementation = solution_class() + return implementation.count_bits(n) + + +def assert_count_bits(result: list[int], expected: list[int]) -> bool: + assert result == expected + return True diff --git a/leetcode/counting_bits/playground.py b/leetcode/counting_bits/playground.py new file mode 100644 index 0000000..65343cf --- /dev/null +++ b/leetcode/counting_bits/playground.py @@ -0,0 +1,29 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_count_bits, run_count_bits +from solution import Solution + +# %% +# Example test case +n = 2 +expected = [0, 1, 1] + +# %% +result = run_count_bits(Solution, n) +result + +# %% +assert_count_bits(result, expected) diff --git a/leetcode/counting_bits/solution.py b/leetcode/counting_bits/solution.py new file mode 100644 index 0000000..871c0c9 --- /dev/null +++ b/leetcode/counting_bits/solution.py @@ -0,0 +1,22 @@ +from typing import List + + +class Solution: + def count_bits(self, n: int) -> List[int]: + """ + Optimized version with better variable naming and comments. + + Time: O(n) + Space: O(1) excluding output array + """ + if n == 0: + return [0] + + bits_count = [0] * (n + 1) + + for num in range(1, n + 1): + # For any number, the count of 1s equals: + # count of 1s in (num >> 1) + whether the last bit is 1 + bits_count[num] = bits_count[num >> 1] + (num & 1) + + return bits_count diff --git a/leetcode/counting_bits/test_solution.py b/leetcode/counting_bits/test_solution.py new file mode 100644 index 0000000..75ddf65 --- /dev/null +++ b/leetcode/counting_bits/test_solution.py @@ -0,0 +1,34 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_count_bits, run_count_bits +from .solution import Solution + + +class TestTestCountingBits: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "solution_class, n, expected", + [ + (Solution, 2, [0, 1, 1]), + (Solution, 5, [0, 1, 1, 2, 1, 2]), + (Solution, 0, [0]), + (Solution, 1, [0, 1]), + (Solution, 3, [0, 1, 1, 2]), + (Solution, 4, [0, 1, 1, 2, 1]), + (Solution, 6, [0, 1, 1, 2, 1, 2, 2]), + (Solution, 7, [0, 1, 1, 2, 1, 2, 2, 3]), + (Solution, 8, [0, 1, 1, 2, 1, 2, 2, 3, 1]), + (Solution, 9, [0, 1, 1, 2, 1, 2, 2, 3, 1, 2]), + (Solution, 10, [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2]), + (Solution, 15, [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4]), + (Solution, 16, [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1]), + ], + ) + def test_count_bits(self, solution_class, n: int, expected: list[int]): + result = run_count_bits(solution_class, n) + assert_count_bits(result, expected) diff --git a/leetcode/house_robber_ii/README.md b/leetcode/house_robber_ii/README.md new file mode 100644 index 0000000..a86b776 --- /dev/null +++ b/leetcode/house_robber_ii/README.md @@ -0,0 +1,46 @@ +# House Robber II + +**Difficulty:** Medium +**Topics:** Array, Dynamic Programming +**Tags:** blind-75 + +**LeetCode:** [Problem 213](https://leetcode.com/problems/house-robber-ii/description/) + +## Problem Description + +You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. All houses at this place are **arranged in a circle.** That means the first house is the neighbor of the last one. Meanwhile, adjacent houses have a security system connected, and **it will automatically contact the police if two adjacent houses were broken into on the same night**. + +Given an integer array `nums` representing the amount of money of each house, return \*the maximum amount of money you can rob tonight **without alerting the police\***. + +## Examples + +### Example 1: + +``` +Input: nums = [2,3,2] +Output: 3 +``` + +**Explanation:** You cannot rob house 1 (money = 2) and then rob house 3 (money = 2), because they are adjacent houses. + +### Example 2: + +``` +Input: nums = [1,2,3,1] +Output: 4 +``` + +**Explanation:** Rob house 1 (money = 1) and then rob house 3 (money = 3). +Total amount you can rob = 1 + 3 = 4. + +### Example 3: + +``` +Input: nums = [1,2,3] +Output: 3 +``` + +## Constraints + +- 1 <= nums.length <= 100 +- 0 <= nums[i] <= 1000 diff --git a/leetcode/house_robber_ii/__init__.py b/leetcode/house_robber_ii/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/house_robber_ii/helpers.py b/leetcode/house_robber_ii/helpers.py new file mode 100644 index 0000000..0107488 --- /dev/null +++ b/leetcode/house_robber_ii/helpers.py @@ -0,0 +1,8 @@ +def run_rob(solution_class: type, nums: list[int]): + implementation = solution_class() + return implementation.rob(nums) + + +def assert_rob(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/house_robber_ii/playground.py b/leetcode/house_robber_ii/playground.py new file mode 100644 index 0000000..ed5d6a2 --- /dev/null +++ b/leetcode/house_robber_ii/playground.py @@ -0,0 +1,29 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_rob, run_rob +from solution import Solution + +# %% +# Example test case +nums = [2, 3, 2] +expected = 3 + +# %% +result = run_rob(Solution, nums) +result + +# %% +assert_rob(result, expected) diff --git a/leetcode/house_robber_ii/solution.py b/leetcode/house_robber_ii/solution.py new file mode 100644 index 0000000..f9fa05d --- /dev/null +++ b/leetcode/house_robber_ii/solution.py @@ -0,0 +1,33 @@ +from typing import List + + +class Solution: + def rob(self, nums: List[int]) -> int: + """ + Optimized version with better variable naming and edge case handling. + + Time: O(n) + Space: O(1) + """ + if not nums: + return 0 + if len(nums) == 1: + return nums[0] + + def rob_range(start: int, end: int) -> int: + """Rob houses from start to end (inclusive).""" + prev_rob = prev_not_rob = 0 + for i in range(start, end + 1): + current_rob = prev_not_rob + nums[i] + current_not_rob = max(prev_rob, prev_not_rob) + prev_rob, prev_not_rob = current_rob, current_not_rob + return max(prev_rob, prev_not_rob) + + n = len(nums) + # Case 1: Rob houses 0 to n-2 (exclude last house) + case1 = rob_range(0, n - 2) + + # Case 2: Rob houses 1 to n-1 (exclude first house) + case2 = rob_range(1, n - 1) + + return max(case1, case2) diff --git a/leetcode/house_robber_ii/test_solution.py b/leetcode/house_robber_ii/test_solution.py new file mode 100644 index 0000000..d81cfcb --- /dev/null +++ b/leetcode/house_robber_ii/test_solution.py @@ -0,0 +1,41 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_rob, run_rob +from .solution import Solution + + +class TestHouseRobberII: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums, expected", + [ + ([2, 3, 2], 3), + ([1, 2, 3, 1], 4), + ([1, 2, 3], 3), + ([1], 1), + ([1, 2], 2), + ([2, 1, 1, 2], 3), + ([1, 2, 3, 4, 5], 8), + ([2, 3, 2, 3, 2], 6), + ([1, 2, 1, 1], 3), + ([2, 1, 1, 1], 3), + ([1, 2, 3, 4, 5, 1, 2, 3, 4, 5], 16), + ([0], 0), + ([0, 0], 0), + ([1, 0, 0, 1], 1), + ([2, 7, 9, 3, 1], 11), + ([1, 2, 3, 1, 2, 3], 6), + ([2, 1, 1, 2, 1, 1], 4), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 30), + ([100, 1, 1, 100], 101), + ([1, 100, 1, 1, 100], 200), + ], + ) + def test_rob(self, nums: list[int], expected: int): + result = run_rob(Solution, nums) + assert_rob(result, expected) diff --git a/leetcode/longest_common_subsequence/README.md b/leetcode/longest_common_subsequence/README.md new file mode 100644 index 0000000..e537d40 --- /dev/null +++ b/leetcode/longest_common_subsequence/README.md @@ -0,0 +1,48 @@ +# Longest Common Subsequence + +**Difficulty:** Medium +**Topics:** String, Dynamic Programming +**Tags:** blind-75 + +**LeetCode:** [Problem 1143](https://leetcode.com/problems/longest-common-subsequence/description/) + +## Problem Description + +Given two strings `text1` and `text2`, return *the length of their longest **common subsequence**. *If there is no **common subsequence**, return `0`. + +A **subsequence** of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters. + +- For example, `"ace"` is a subsequence of `"abcde"`. + +A **common subsequence** of two strings is a subsequence that is common to both strings. + +## Examples + +### Example 1: + +``` +Input: text1 = "abcde", text2 = "ace" +Output: 3 +Explanation: The longest common subsequence is "ace" and its length is 3. +``` + +### Example 2: + +``` +Input: text1 = "abc", text2 = "abc" +Output: 3 +Explanation: The longest common subsequence is "abc" and its length is 3. +``` + +### Example 3: + +``` +Input: text1 = "abc", text2 = "def" +Output: 0 +Explanation: There is no such common subsequence, so the result is 0. +``` + +## Constraints + +- 1 <= text1.length, text2.length <= 1000 +- text1 and text2 consist of only lowercase English characters. diff --git a/leetcode/longest_common_subsequence/__init__.py b/leetcode/longest_common_subsequence/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/longest_common_subsequence/helpers.py b/leetcode/longest_common_subsequence/helpers.py new file mode 100644 index 0000000..50cb366 --- /dev/null +++ b/leetcode/longest_common_subsequence/helpers.py @@ -0,0 +1,8 @@ +def run_longest_common_subsequence(solution_class: type, text1: str, text2: str): + implementation = solution_class() + return implementation.longest_common_subsequence(text1, text2) + + +def assert_longest_common_subsequence(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/longest_common_subsequence/playground.py b/leetcode/longest_common_subsequence/playground.py new file mode 100644 index 0000000..5b46715 --- /dev/null +++ b/leetcode/longest_common_subsequence/playground.py @@ -0,0 +1,30 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_longest_common_subsequence, run_longest_common_subsequence +from solution import Solution + +# %% +# Example test case +text1 = "abcde" +text2 = "ace" +expected = 3 + +# %% +result = run_longest_common_subsequence(Solution, text1, text2) +result + +# %% +assert_longest_common_subsequence(result, expected) diff --git a/leetcode/longest_common_subsequence/solution.py b/leetcode/longest_common_subsequence/solution.py new file mode 100644 index 0000000..f849245 --- /dev/null +++ b/leetcode/longest_common_subsequence/solution.py @@ -0,0 +1,16 @@ +class Solution: + + # Time: O(m * n) + # Space: O(m * n) + def longest_common_subsequence(self, text1: str, text2: str) -> int: + m, n = len(text1), len(text2) + dp = [[0] * (n + 1) for _ in range(m + 1)] + + for i in range(1, m + 1): + for j in range(1, n + 1): + if text1[i - 1] == text2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + 1 + else: + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + + return dp[m][n] diff --git a/leetcode/longest_common_subsequence/test_solution.py b/leetcode/longest_common_subsequence/test_solution.py new file mode 100644 index 0000000..fd44abb --- /dev/null +++ b/leetcode/longest_common_subsequence/test_solution.py @@ -0,0 +1,39 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_longest_common_subsequence, run_longest_common_subsequence +from .solution import Solution + + +class TestLongestCommonSubsequence: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "text1, text2, expected", + [ + ("abcde", "ace", 3), + ("abc", "abc", 3), + ("abc", "def", 0), + ("", "", 0), + ("a", "a", 1), + ("a", "b", 0), + ("ab", "ba", 1), + ("abc", "bca", 2), + ("abcdef", "ace", 3), + ("abcdef", "adf", 3), + ("abcdef", "xyz", 0), + ("abcdef", "abcdef", 6), + ("abcdef", "fedcba", 1), + ("abcdef", "aceg", 3), + ("abcdef", "bdf", 3), + ("abcdef", "bdfh", 3), + ("abcdef", "bdfhj", 3), + ("abcdef", "bdfhjl", 3), + ], + ) + def test_longest_common_subsequence(self, text1: str, text2: str, expected: int): + result = run_longest_common_subsequence(Solution, text1, text2) + assert_longest_common_subsequence(result, expected) diff --git a/leetcode/longest_repeating_character_replacement/README.md b/leetcode/longest_repeating_character_replacement/README.md new file mode 100644 index 0000000..1ff0943 --- /dev/null +++ b/leetcode/longest_repeating_character_replacement/README.md @@ -0,0 +1,39 @@ +# Longest Repeating Character Replacement + +**Difficulty:** Medium +**Topics:** Hash Table, String, Sliding Window +**Tags:** blind-75 + +**LeetCode:** [Problem 424](https://leetcode.com/problems/longest-repeating-character-replacement/description/) + +## Problem Description + +You are given a string s and an integer k. You can choose any character of the string and change it to any other uppercase English character. You can perform this operation at most k times. + +Return the length of the longest substring containing the same letter you can get after performing the above operations. + +## Examples + +### Example 1: + +``` +Input: s = "ABAB", k = 2 +Output: 4 +Explanation: Replace the two 'A's with two 'B's or vice versa. +``` + +### Example 2: + +``` +Input: s = "AABABBA", k = 1 +Output: 4 +Explanation: Replace the one 'A' in the middle with 'B' and form "AABBBBA". +The substring "BBBB" has the longest repeating letters, which is 4. +There may exists other ways to achieve this answer too. +``` + +## Constraints + +1 <= s.length <= 10^5 +s consists of only uppercase English letters. +0 <= k <= s.length diff --git a/leetcode/longest_repeating_character_replacement/__init__.py b/leetcode/longest_repeating_character_replacement/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/longest_repeating_character_replacement/helpers.py b/leetcode/longest_repeating_character_replacement/helpers.py new file mode 100644 index 0000000..0474899 --- /dev/null +++ b/leetcode/longest_repeating_character_replacement/helpers.py @@ -0,0 +1,8 @@ +def run_character_replacement(solution_class: type, s: str, k: int): + implementation = solution_class() + return implementation.character_replacement(s, k) + + +def assert_character_replacement(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/longest_repeating_character_replacement/playground.py b/leetcode/longest_repeating_character_replacement/playground.py new file mode 100644 index 0000000..6d1405a --- /dev/null +++ b/leetcode/longest_repeating_character_replacement/playground.py @@ -0,0 +1,30 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_character_replacement, run_character_replacement +from solution import Solution + +# %% +# Example test case +s = "ABAB" +k = 2 +expected = 4 + +# %% +result = run_character_replacement(Solution, s, k) +result + +# %% +assert_character_replacement(result, expected) diff --git a/leetcode/longest_repeating_character_replacement/solution.py b/leetcode/longest_repeating_character_replacement/solution.py new file mode 100644 index 0000000..1f6d5c2 --- /dev/null +++ b/leetcode/longest_repeating_character_replacement/solution.py @@ -0,0 +1,34 @@ +class Solution: + + # Time: O(n) - single pass through string + # Space: O(1) - at most 26 characters in count dict + def character_replacement(self, s: str, k: int) -> int: + """ + Find the length of the longest substring with same character + after at most k replacements using sliding window approach. + """ + if not s: + return 0 + + count: dict[str, int] = {} + left = 0 + max_freq = 0 + max_length = 0 + + for right in range(len(s)): + # Expand window: add character at right pointer + count[s[right]] = count.get(s[right], 0) + 1 + max_freq = max(max_freq, count[s[right]]) + + # Shrink window if needed: if we need more than k replacements + # Current window size = right - left + 1 + # Characters to replace = window_size - max_freq + # If characters_to_replace > k, we need to shrink + if (right - left + 1) - max_freq > k: + count[s[left]] -= 1 + left += 1 + + # Update max length + max_length = max(max_length, right - left + 1) + + return max_length diff --git a/leetcode/longest_repeating_character_replacement/test_solution.py b/leetcode/longest_repeating_character_replacement/test_solution.py new file mode 100644 index 0000000..2ca5573 --- /dev/null +++ b/leetcode/longest_repeating_character_replacement/test_solution.py @@ -0,0 +1,36 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_character_replacement, run_character_replacement +from .solution import Solution + + +class TestLongestRepeatingCharacterReplacement: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, k, expected", + [ + ("ABAB", 2, 4), + ("AABABBA", 1, 4), + ("AAAA", 0, 4), + ("ABCDE", 0, 1), + ("ABCDE", 4, 5), + ("A", 0, 1), + ("A", 1, 1), + ("AAAB", 0, 3), + ("AAAB", 1, 4), + ("ABABAB", 2, 5), + ("ABABAB", 3, 6), + ("ABCDEF", 0, 1), + ("ABCDEF", 1, 2), + ("ABCDEF", 5, 6), + ("AABABBA", 2, 5), + ], + ) + def test_character_replacement(self, s: str, k: int, expected: int): + result = run_character_replacement(Solution, s, k) + assert_character_replacement(result, expected) diff --git a/leetcode/median_of_two_sorted_arrays/README.md b/leetcode/median_of_two_sorted_arrays/README.md new file mode 100644 index 0000000..2d6a89c --- /dev/null +++ b/leetcode/median_of_two_sorted_arrays/README.md @@ -0,0 +1,40 @@ +# Median of Two Sorted Arrays + +**Difficulty:** Hard +**Topics:** Array, Binary Search, Divide and Conquer +**Tags:** algo-master-75 + +**LeetCode:** [Problem 4](https://leetcode.com/problems/median-of-two-sorted-arrays/description/) + +## Problem Description + +Given two sorted arrays `nums1` and `nums2` of size `m` and `n` respectively, return **the median** of the two sorted arrays. + +The overall run time complexity should be `O(log (m+n))`. + +## Examples + +### Example 1: + +``` +Input: nums1 = [1,3], nums2 = [2] +Output: 2.00000 +Explanation: merged array = [1,2,3] and median is 2. +``` + +### Example 2: + +``` +Input: nums1 = [1,2], nums2 = [3,4] +Output: 2.50000 +Explanation: merged array = [1,2,3,4] and median is (2 + 3) / 2 = 2.5. +``` + +## Constraints + +- nums1.length == m +- nums2.length == n +- 0 <= m <= 1000 +- 0 <= n <= 1000 +- 1 <= m + n <= 2000 +- -10^6 <= nums1[i], nums2[i] <= 10^6 diff --git a/leetcode/median_of_two_sorted_arrays/__init__.py b/leetcode/median_of_two_sorted_arrays/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/median_of_two_sorted_arrays/helpers.py b/leetcode/median_of_two_sorted_arrays/helpers.py new file mode 100644 index 0000000..3258215 --- /dev/null +++ b/leetcode/median_of_two_sorted_arrays/helpers.py @@ -0,0 +1,8 @@ +def run_find_median_sorted_arrays(solution_class: type, nums1: list[int], nums2: list[int]): + implementation = solution_class() + return implementation.find_median_sorted_arrays(nums1, nums2) + + +def assert_find_median_sorted_arrays(result: float, expected: float) -> bool: + assert abs(result - expected) < 1e-5 + return True diff --git a/leetcode/median_of_two_sorted_arrays/playground.py b/leetcode/median_of_two_sorted_arrays/playground.py new file mode 100644 index 0000000..5ec39a9 --- /dev/null +++ b/leetcode/median_of_two_sorted_arrays/playground.py @@ -0,0 +1,30 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_find_median_sorted_arrays, run_find_median_sorted_arrays +from solution import Solution + +# %% +# Example test case +nums1 = [1, 3] +nums2 = [2] +expected = 2.0 + +# %% +result = run_find_median_sorted_arrays(Solution, nums1, nums2) +result + +# %% +assert_find_median_sorted_arrays(result, expected) diff --git a/leetcode/median_of_two_sorted_arrays/solution.py b/leetcode/median_of_two_sorted_arrays/solution.py new file mode 100644 index 0000000..68362b4 --- /dev/null +++ b/leetcode/median_of_two_sorted_arrays/solution.py @@ -0,0 +1,37 @@ +class Solution: + + # Time: O(log(min(m, n))) + # Space: O(1) + def find_median_sorted_arrays(self, nums1: list[int], nums2: list[int]) -> float: + # Ensure nums1 is the smaller array + if len(nums1) > len(nums2): + nums1, nums2 = nums2, nums1 + + m, n = len(nums1), len(nums2) + left, right = 0, m + + while left <= right: + partition_x = (left + right) // 2 + partition_y = (m + n + 1) // 2 - partition_x + + # Handle edge cases + max_left_x = float("-inf") if partition_x == 0 else nums1[partition_x - 1] + min_right_x = float("inf") if partition_x == m else nums1[partition_x] + + max_left_y = float("-inf") if partition_y == 0 else nums2[partition_y - 1] + min_right_y = float("inf") if partition_y == n else nums2[partition_y] + + if max_left_x <= min_right_y and max_left_y <= min_right_x: + # Found the correct partition + if (m + n) % 2 == 0: + return (max(max_left_x, max_left_y) + min(min_right_x, min_right_y)) / 2.0 + else: + return float(max(max_left_x, max_left_y)) + elif max_left_x > min_right_y: + # Too far right in nums1 + right = partition_x - 1 + else: + # Too far left in nums1 + left = partition_x + 1 + + return 0.0 diff --git a/leetcode/median_of_two_sorted_arrays/test_solution.py b/leetcode/median_of_two_sorted_arrays/test_solution.py new file mode 100644 index 0000000..7a229aa --- /dev/null +++ b/leetcode/median_of_two_sorted_arrays/test_solution.py @@ -0,0 +1,39 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_find_median_sorted_arrays, run_find_median_sorted_arrays +from .solution import Solution + + +class TestMedianOfTwoSortedArrays: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "nums1, nums2, expected", + [ + ([1, 3], [2], 2.0), + ([1, 2], [3, 4], 2.5), + ([1], [], 1.0), + ([], [1], 1.0), + ([1, 2, 3], [], 2.0), + ([], [1, 2, 3], 2.0), + ([1, 2, 3, 4], [], 2.5), + ([], [1, 2, 3, 4], 2.5), + ([1, 3, 5], [2, 4, 6], 3.5), + ([1, 2, 3, 4, 5], [6, 7, 8, 9, 10], 5.5), + ([1, 2, 3, 4, 5, 6], [7, 8, 9, 10], 5.5), + ([1, 2, 3, 4, 5], [6, 7, 8, 9, 10, 11], 6.0), + ([1, 2, 3, 4, 5, 6, 7], [8, 9, 10], 5.5), + ([1, 2, 3, 4, 5, 6, 7, 8], [9, 10], 5.5), + ([1, 2, 3, 4, 5, 6, 7, 8, 9], [10], 5.5), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [], 5.5), + ([1, 1, 1, 1], [2, 2, 2, 2], 1.5), + ([1, 1, 1, 1, 1], [2, 2, 2, 2], 1.0), + ], + ) + def test_find_median_sorted_arrays(self, nums1: list[int], nums2: list[int], expected: float): + result = run_find_median_sorted_arrays(Solution, nums1, nums2) + assert_find_median_sorted_arrays(result, expected) diff --git a/leetcode/missing_number/README.md b/leetcode/missing_number/README.md new file mode 100644 index 0000000..159e1d7 --- /dev/null +++ b/leetcode/missing_number/README.md @@ -0,0 +1,55 @@ +# Missing Number + +**Difficulty:** Easy +**Topics:** Array, Hash Table, Math, Binary Search, Bit Manipulation, Sorting +**Tags:** neetcode-150, blind-75 + +**LeetCode:** [Problem 268](https://leetcode.com/problems/missing-number/description/) + +## Problem Description + +Given an array `nums` containing `n` distinct numbers in the range `[0, n]`, return _the only number in the range that is missing from the array._ + +## Examples + +### Example 1: + +``` +Input: nums = [3,0,1] +Output: 2 +``` + +**Explanation:** + +`n = 3` since there are 3 numbers, so all numbers are in the range `[0,3]`. 2 is the missing number in the range since it does not appear in `nums`. + +### Example 2: + +``` +Input: nums = [0,1] +Output: 2 +``` + +**Explanation:** + +`n = 2` since there are 2 numbers, so all numbers are in the range `[0,2]`. 2 is the missing number in the range since it does not appear in `nums`. + +### Example 3: + +``` +Input: nums = [9,6,4,2,3,5,7,0,1] +Output: 8 +``` + +**Explanation:** + +`n = 9` since there are 9 numbers, so all numbers are in the range `[0,9]`. 8 is the missing number in the range since it does not appear in `nums`. + +## Constraints + +- n == nums.length +- 1 <= n <= 10^4 +- 0 <= nums[i] <= n +- All the numbers of nums are **unique**. + +**Follow up:** Could you implement a solution using only `O(1)` extra space complexity and `O(n)` runtime complexity? diff --git a/leetcode/missing_number/__init__.py b/leetcode/missing_number/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/missing_number/helpers.py b/leetcode/missing_number/helpers.py new file mode 100644 index 0000000..77d4633 --- /dev/null +++ b/leetcode/missing_number/helpers.py @@ -0,0 +1,8 @@ +def run_missing_number(solution_class: type, nums: list[int]): + implementation = solution_class() + return implementation.missing_number(nums) + + +def assert_missing_number(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/missing_number/playground.py b/leetcode/missing_number/playground.py new file mode 100644 index 0000000..5d99064 --- /dev/null +++ b/leetcode/missing_number/playground.py @@ -0,0 +1,29 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_missing_number, run_missing_number +from solution import Solution + +# %% +# Example test case +nums = [3, 0, 1] +expected = 2 + +# %% +result = run_missing_number(Solution, nums) +result + +# %% +assert_missing_number(result, expected) diff --git a/leetcode/missing_number/solution.py b/leetcode/missing_number/solution.py new file mode 100644 index 0000000..fc34479 --- /dev/null +++ b/leetcode/missing_number/solution.py @@ -0,0 +1,17 @@ +class Solution: + + # Time: O(n) + # Space: O(1) + def missing_number(self, nums: list[int]) -> int: + """ + Find the missing number in an array containing n distinct numbers + in the range [0, n]. + + Approach: Use the mathematical formula for sum of consecutive integers. + The sum of numbers from 0 to n is n*(n+1)/2. + The missing number = expected_sum - actual_sum. + """ + n = len(nums) + expected_sum = n * (n + 1) // 2 + actual_sum = sum(nums) + return expected_sum - actual_sum diff --git a/leetcode/missing_number/test_solution.py b/leetcode/missing_number/test_solution.py new file mode 100644 index 0000000..685eddf --- /dev/null +++ b/leetcode/missing_number/test_solution.py @@ -0,0 +1,148 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_missing_number, run_missing_number +from .solution import Solution + + +class TestTestMissingNumber: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "solution_class, nums, expected", + [ + (Solution, [3, 0, 1], 2), + (Solution, [0, 1], 2), + (Solution, [9, 6, 4, 2, 3, 5, 7, 0, 1], 8), + (Solution, [0], 1), + (Solution, [1], 0), + (Solution, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 10), + (Solution, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 0), + (Solution, [0, 1, 2, 3, 4, 6, 7, 8, 9, 10], 5), + ( + Solution, + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], + 0, + ), + ( + Solution, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], + 20, + ), + (Solution, [2, 0, 1], 3), + (Solution, [1, 0], 2), + ( + Solution, + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + ], + 101, + ), + ], + ) + def test_missing_number(self, solution_class, nums: list[int], expected: int): + result = run_missing_number(solution_class, nums) + assert_missing_number(result, expected) diff --git a/leetcode/non_overlapping_intervals/README.md b/leetcode/non_overlapping_intervals/README.md new file mode 100644 index 0000000..fed8dea --- /dev/null +++ b/leetcode/non_overlapping_intervals/README.md @@ -0,0 +1,45 @@ +# Non-overlapping Intervals + +**Difficulty:** Medium +**Topics:** Array, Dynamic Programming, Greedy, Sorting +**Tags:** blind-75 + +**LeetCode:** [Problem 435](https://leetcode.com/problems/non-overlapping-intervals/description/) + +## Problem Description + +Given an array of intervals intervals where intervals[i] = [starti, endi], return the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping. + +Note that intervals which only touch at a point are non-overlapping. For example, [1, 2] and [2, 3] are non-overlapping. + +## Examples + +### Example 1: + +``` +Input: intervals = [[1,2],[2,3],[3,4],[1,3]] +Output: 1 +Explanation: [1,3] can be removed and the rest of the intervals are non-overlapping. +``` + +### Example 2: + +``` +Input: intervals = [[1,2],[1,2],[1,2]] +Output: 2 +Explanation: You need to remove two [1,2] to make the rest of the intervals non-overlapping. +``` + +### Example 3: + +``` +Input: intervals = [[1,2],[2,3]] +Output: 0 +Explanation: You don't need to remove any of the intervals since they're already non-overlapping. +``` + +## Constraints + +1 <= intervals.length <= 10^5 +intervals[i].length == 2 +-5 _ 10^4 <= starti < endi <= 5 _ 10^4 diff --git a/leetcode/non_overlapping_intervals/__init__.py b/leetcode/non_overlapping_intervals/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/non_overlapping_intervals/helpers.py b/leetcode/non_overlapping_intervals/helpers.py new file mode 100644 index 0000000..ac5ec6b --- /dev/null +++ b/leetcode/non_overlapping_intervals/helpers.py @@ -0,0 +1,8 @@ +def run_erase_overlap_intervals(solution_class: type, intervals: list[list[int]]): + implementation = solution_class() + return implementation.erase_overlap_intervals(intervals) + + +def assert_erase_overlap_intervals(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/non_overlapping_intervals/playground.py b/leetcode/non_overlapping_intervals/playground.py new file mode 100644 index 0000000..5d91071 --- /dev/null +++ b/leetcode/non_overlapping_intervals/playground.py @@ -0,0 +1,29 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_erase_overlap_intervals, run_erase_overlap_intervals +from solution import Solution + +# %% +# Example test case +intervals = [[1, 2], [2, 3], [3, 4], [1, 3]] +expected = 1 + +# %% +result = run_erase_overlap_intervals(Solution, intervals) +result + +# %% +assert_erase_overlap_intervals(result, expected) diff --git a/leetcode/non_overlapping_intervals/solution.py b/leetcode/non_overlapping_intervals/solution.py new file mode 100644 index 0000000..7c23296 --- /dev/null +++ b/leetcode/non_overlapping_intervals/solution.py @@ -0,0 +1,27 @@ +class Solution: + + # Time: O(n log n) - sorting dominates + # Space: O(1) - no extra space used + def erase_overlap_intervals(self, intervals: list[list[int]]) -> int: + """ + Find minimum number of intervals to remove to make non-overlapping. + Uses greedy approach: sort by end time and keep intervals with earliest end times. + """ + if not intervals: + return 0 + + # Sort intervals by end time + intervals.sort(key=lambda x: x[1]) + + count = 0 + prev_end = intervals[0][1] + + for i in range(1, len(intervals)): + # If current interval starts before previous ends, it overlaps + if intervals[i][0] < prev_end: + count += 1 # Remove this interval + else: + # No overlap, update previous end time + prev_end = intervals[i][1] + + return count diff --git a/leetcode/non_overlapping_intervals/test_solution.py b/leetcode/non_overlapping_intervals/test_solution.py new file mode 100644 index 0000000..0b444ca --- /dev/null +++ b/leetcode/non_overlapping_intervals/test_solution.py @@ -0,0 +1,36 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_erase_overlap_intervals, run_erase_overlap_intervals +from .solution import Solution + + +class TestNonOverlappingIntervals: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "intervals, expected", + [ + ([[1, 2], [2, 3], [3, 4], [1, 3]], 1), + ([[1, 2], [1, 2], [1, 2]], 2), + ([[1, 2], [2, 3]], 0), + ([[1, 2]], 0), + ([[1, 2], [1, 3], [2, 3], [3, 4]], 1), + ([[1, 2], [2, 3], [3, 4], [1, 3], [2, 4]], 2), + ([[1, 2], [3, 4], [5, 6]], 0), + ([[1, 2], [1, 3], [1, 4]], 2), + ([[1, 2], [2, 3], [3, 4], [4, 5]], 0), + ([[1, 2], [1, 2], [1, 2], [1, 2]], 3), + ([[1, 3], [2, 4], [3, 5], [4, 6]], 2), + ([[1, 2], [3, 4], [5, 6], [7, 8]], 0), + ([[1, 4], [2, 3], [3, 4]], 1), + ([[1, 2], [1, 2], [1, 2], [2, 3]], 2), + ([[1, 2], [2, 3], [1, 3], [3, 4]], 1), + ], + ) + def test_erase_overlap_intervals(self, intervals: list[list[int]], expected: int): + result = run_erase_overlap_intervals(Solution, intervals) + assert_erase_overlap_intervals(result, expected) diff --git a/leetcode/palindromic_substrings/README.md b/leetcode/palindromic_substrings/README.md new file mode 100644 index 0000000..4cba2ef --- /dev/null +++ b/leetcode/palindromic_substrings/README.md @@ -0,0 +1,38 @@ +# Palindromic Substrings + +**Difficulty:** Medium +**Topics:** Two Pointers, String, Dynamic Programming +**Tags:** blind-75 + +**LeetCode:** [Problem 647](https://leetcode.com/problems/palindromic-substrings/description/) + +## Problem Description + +Given a string s, return the number of palindromic substrings in it. + +A string is a palindrome when it reads the same backward as forward. + +A substring is a contiguous sequence of characters within the string. + +## Examples + +### Example 1: + +``` +Input: s = "abc" +Output: 3 +Explanation: Three palindromic strings: "a", "b", "c". +``` + +### Example 2: + +``` +Input: s = "aaa" +Output: 6 +Explanation: Six palindromic strings: "a", "a", "a", "aa", "aa", "aaa". +``` + +## Constraints + +1 <= s.length <= 1000 +s consists of lowercase English letters. diff --git a/leetcode/palindromic_substrings/__init__.py b/leetcode/palindromic_substrings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/palindromic_substrings/helpers.py b/leetcode/palindromic_substrings/helpers.py new file mode 100644 index 0000000..56c968e --- /dev/null +++ b/leetcode/palindromic_substrings/helpers.py @@ -0,0 +1,8 @@ +def run_count_substrings(solution_class: type, s: str): + implementation = solution_class() + return implementation.count_substrings(s) + + +def assert_count_substrings(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/palindromic_substrings/playground.py b/leetcode/palindromic_substrings/playground.py new file mode 100644 index 0000000..6919b48 --- /dev/null +++ b/leetcode/palindromic_substrings/playground.py @@ -0,0 +1,29 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_count_substrings, run_count_substrings +from solution import Solution + +# %% +# Example test case +s = "abc" +expected = 3 + +# %% +result = run_count_substrings(Solution, s) +result + +# %% +assert_count_substrings(result, expected) diff --git a/leetcode/palindromic_substrings/solution.py b/leetcode/palindromic_substrings/solution.py new file mode 100644 index 0000000..2a82f3b --- /dev/null +++ b/leetcode/palindromic_substrings/solution.py @@ -0,0 +1,33 @@ +class Solution: + + # Time: O(n^2) - expand around centers approach + # Space: O(1) - no extra space used + def count_substrings(self, s: str) -> int: + """ + Count palindromic substrings using expand around centers approach. + For each possible center (single char or between two chars), expand outward + and count palindromes. + """ + if not s: + return 0 + + count = 0 + n = len(s) + + for i in range(n): + # Odd length palindromes (center at i) + count += self._expand_around_center(s, i, i) + + # Even length palindromes (center between i and i+1) + count += self._expand_around_center(s, i, i + 1) + + return count + + def _expand_around_center(self, s: str, left: int, right: int) -> int: + """Expand around center and count palindromes.""" + count = 0 + while left >= 0 and right < len(s) and s[left] == s[right]: + count += 1 + left -= 1 + right += 1 + return count diff --git a/leetcode/palindromic_substrings/test_solution.py b/leetcode/palindromic_substrings/test_solution.py new file mode 100644 index 0000000..b079066 --- /dev/null +++ b/leetcode/palindromic_substrings/test_solution.py @@ -0,0 +1,36 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_count_substrings, run_count_substrings +from .solution import Solution + + +class TestPalindromicSubstrings: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "s, expected", + [ + ("abc", 3), + ("aaa", 6), + ("a", 1), + ("aa", 3), + ("aba", 4), + ("abccba", 9), + ("racecar", 10), + ("abcdef", 6), + ("aab", 4), + ("ababa", 9), + ("aaaaa", 15), + ("ab", 2), + ("abcba", 7), + ("abacaba", 12), + ("xyz", 3), + ], + ) + def test_count_substrings(self, s: str, expected: int): + result = run_count_substrings(Solution, s) + assert_count_substrings(result, expected) diff --git a/leetcode/reverse_integer/README.md b/leetcode/reverse_integer/README.md new file mode 100644 index 0000000..2118131 --- /dev/null +++ b/leetcode/reverse_integer/README.md @@ -0,0 +1,40 @@ +# Reverse Integer + +**Difficulty:** Medium +**Topics:** Math +**Tags:** algo-master-75 + +**LeetCode:** [Problem 7](https://leetcode.com/problems/reverse-integer/description/) + +## Problem Description + +Given a signed 32-bit integer `x`, return `x` _with its digits reversed_. If reversing `x` causes the value to go outside the signed 32-bit integer range `[-2^31, 2^31 - 1]`, then return `0`. + +**Assume the environment does not allow you to store 64-bit integers (signed or unsigned).** + +## Examples + +### Example 1: + +``` +Input: x = 123 +Output: 321 +``` + +### Example 2: + +``` +Input: x = -123 +Output: -321 +``` + +### Example 3: + +``` +Input: x = 120 +Output: 21 +``` + +## Constraints + +- -2^31 <= x <= 2^31 - 1 diff --git a/leetcode/reverse_integer/__init__.py b/leetcode/reverse_integer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/reverse_integer/helpers.py b/leetcode/reverse_integer/helpers.py new file mode 100644 index 0000000..e487245 --- /dev/null +++ b/leetcode/reverse_integer/helpers.py @@ -0,0 +1,8 @@ +def run_reverse(solution_class: type, x: int): + implementation = solution_class() + return implementation.reverse(x) + + +def assert_reverse(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/reverse_integer/playground.py b/leetcode/reverse_integer/playground.py new file mode 100644 index 0000000..ae8f240 --- /dev/null +++ b/leetcode/reverse_integer/playground.py @@ -0,0 +1,29 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_reverse, run_reverse +from solution import Solution + +# %% +# Example test case +x = 123 +expected = 321 + +# %% +result = run_reverse(Solution, x) +result + +# %% +assert_reverse(result, expected) diff --git a/leetcode/reverse_integer/solution.py b/leetcode/reverse_integer/solution.py new file mode 100644 index 0000000..7fd15f2 --- /dev/null +++ b/leetcode/reverse_integer/solution.py @@ -0,0 +1,22 @@ +class Solution: + + # Time: O(log(x)) + # Space: O(1) + def reverse(self, x: int) -> int: + INT_MAX = 2**31 - 1 + + result = 0 + sign = 1 if x >= 0 else -1 + x = abs(x) + + while x != 0: + digit = x % 10 + x //= 10 + + # Check for overflow before adding the digit + if result > (INT_MAX - digit) // 10: + return 0 + + result = result * 10 + digit + + return sign * result diff --git a/leetcode/reverse_integer/test_solution.py b/leetcode/reverse_integer/test_solution.py new file mode 100644 index 0000000..51c6fee --- /dev/null +++ b/leetcode/reverse_integer/test_solution.py @@ -0,0 +1,41 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_reverse, run_reverse +from .solution import Solution + + +class TestReverseInteger: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "x, expected", + [ + (123, 321), + (-123, -321), + (120, 21), + (0, 0), + (1, 1), + (-1, -1), + (10, 1), + (-10, -1), + (100, 1), + (-100, -1), + (1534236469, 0), + (-1534236469, 0), + (2147483647, 0), + (-2147483648, 0), + (1463847412, 2147483641), + (-1463847412, -2147483641), + (123456789, 987654321), + (-123456789, -987654321), + (1000000003, 0), + (-1000000003, 0), + ], + ) + def test_reverse(self, x: int, expected: int): + result = run_reverse(Solution, x) + assert_reverse(result, expected) diff --git a/leetcode/reverse_nodes_in_k_group/README.md b/leetcode/reverse_nodes_in_k_group/README.md new file mode 100644 index 0000000..97b9065 --- /dev/null +++ b/leetcode/reverse_nodes_in_k_group/README.md @@ -0,0 +1,43 @@ +# Reverse Nodes in k-Group + +**Difficulty:** Hard +**Topics:** Linked List, Recursion +**Tags:** algo-master-75 + +**LeetCode:** [Problem 25](https://leetcode.com/problems/reverse-nodes-in-k-group/description/) + +## Problem Description + +Given the `head` of a linked list, reverse the nodes of the list `k` at a time, and return _the modified list_. + +`k` is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of `k` then left-out nodes, in the end, should remain as it is. + +You may not alter the values in the list's nodes, only nodes themselves may be changed. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2020/10/03/reverse_ex1.jpg) + +``` +Input: head = [1,2,3,4,5], k = 2 +Output: [2,1,4,3,5] +``` + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2020/10/03/reverse_ex2.jpg) + +``` +Input: head = [1,2,3,4,5], k = 3 +Output: [3,2,1,4,5] +``` + +## Constraints + +- The number of nodes in the list is n. +- 1 <= k <= n <= 5000 +- 0 <= Node.val <= 1000 + +**Follow-up:** Can you solve the problem in `O(1)` extra memory space? diff --git a/leetcode/reverse_nodes_in_k_group/__init__.py b/leetcode/reverse_nodes_in_k_group/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/reverse_nodes_in_k_group/helpers.py b/leetcode/reverse_nodes_in_k_group/helpers.py new file mode 100644 index 0000000..c6e8283 --- /dev/null +++ b/leetcode/reverse_nodes_in_k_group/helpers.py @@ -0,0 +1,13 @@ +from leetcode_py import ListNode + + +def run_reverse_k_group(solution_class: type, head_vals: list[int], k: int): + head = ListNode.from_list(head_vals) + implementation = solution_class() + return implementation.reverse_k_group(head, k) + + +def assert_reverse_k_group(result: ListNode[int] | None, expected_vals: list[int]) -> bool: + expected = ListNode.from_list(expected_vals) + assert result == expected + return True diff --git a/leetcode/reverse_nodes_in_k_group/playground.py b/leetcode/reverse_nodes_in_k_group/playground.py new file mode 100644 index 0000000..48163d0 --- /dev/null +++ b/leetcode/reverse_nodes_in_k_group/playground.py @@ -0,0 +1,30 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_reverse_k_group, run_reverse_k_group +from solution import Solution + +# %% +# Example test case +head_vals = [1, 2, 3, 4, 5] +k = 2 +expected_vals = [2, 1, 4, 3, 5] + +# %% +result = run_reverse_k_group(Solution, head_vals, k) +result + +# %% +assert_reverse_k_group(result, expected_vals) diff --git a/leetcode/reverse_nodes_in_k_group/solution.py b/leetcode/reverse_nodes_in_k_group/solution.py new file mode 100644 index 0000000..06e1fe3 --- /dev/null +++ b/leetcode/reverse_nodes_in_k_group/solution.py @@ -0,0 +1,33 @@ +from leetcode_py import ListNode + + +class Solution: + + # Time: O(n) + # Space: O(1) + def reverse_k_group(self, head: ListNode[int] | None, k: int) -> ListNode[int] | None: + if not head or k == 1: + return head + + # Check if we have at least k nodes + curr: ListNode[int] | None = head + count = 0 + while curr and count < k: + curr = curr.next + count += 1 + + if count == k: + # Reverse the first k nodes + prev = self.reverse_k_group(curr, k) # Recursively reverse remaining groups + curr = head + + while count > 0 and curr is not None: + next_temp = curr.next + curr.next = prev + prev = curr + curr = next_temp + count -= 1 + + head = prev + + return head diff --git a/leetcode/reverse_nodes_in_k_group/test_solution.py b/leetcode/reverse_nodes_in_k_group/test_solution.py new file mode 100644 index 0000000..84a6bd7 --- /dev/null +++ b/leetcode/reverse_nodes_in_k_group/test_solution.py @@ -0,0 +1,41 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_reverse_k_group, run_reverse_k_group +from .solution import Solution + + +class TestReverseNodesInKGroup: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "head_vals, k, expected_vals", + [ + ([1, 2, 3, 4, 5], 2, [2, 1, 4, 3, 5]), + ([1, 2, 3, 4, 5], 3, [3, 2, 1, 4, 5]), + ([1, 2, 3, 4, 5], 1, [1, 2, 3, 4, 5]), + ([1, 2, 3, 4, 5], 5, [5, 4, 3, 2, 1]), + ([1], 1, [1]), + ([1, 2], 1, [1, 2]), + ([1, 2], 2, [2, 1]), + ([1, 2, 3], 1, [1, 2, 3]), + ([1, 2, 3], 2, [2, 1, 3]), + ([1, 2, 3], 3, [3, 2, 1]), + ([1, 2, 3, 4], 2, [2, 1, 4, 3]), + ([1, 2, 3, 4], 3, [3, 2, 1, 4]), + ([1, 2, 3, 4], 4, [4, 3, 2, 1]), + ([1, 2, 3, 4, 5, 6], 2, [2, 1, 4, 3, 6, 5]), + ([1, 2, 3, 4, 5, 6], 3, [3, 2, 1, 6, 5, 4]), + ([1, 2, 3, 4, 5, 6], 4, [4, 3, 2, 1, 5, 6]), + ([1, 2, 3, 4, 5, 6, 7], 2, [2, 1, 4, 3, 6, 5, 7]), + ([1, 2, 3, 4, 5, 6, 7], 3, [3, 2, 1, 6, 5, 4, 7]), + ([1, 2, 3, 4, 5, 6, 7, 8], 2, [2, 1, 4, 3, 6, 5, 8, 7]), + ([1, 2, 3, 4, 5, 6, 7, 8], 3, [3, 2, 1, 6, 5, 4, 7, 8]), + ], + ) + def test_reverse_k_group(self, head_vals: list[int], k: int, expected_vals: list[int]): + result = run_reverse_k_group(Solution, head_vals, k) + assert_reverse_k_group(result, expected_vals) diff --git a/leetcode/subtree_of_another_tree/README.md b/leetcode/subtree_of_another_tree/README.md new file mode 100644 index 0000000..5e5848f --- /dev/null +++ b/leetcode/subtree_of_another_tree/README.md @@ -0,0 +1,40 @@ +# Subtree of Another Tree + +**Difficulty:** Easy +**Topics:** Tree, Depth-First Search, String Matching, Binary Tree, Hash Function +**Tags:** blind-75 + +**LeetCode:** [Problem 572](https://leetcode.com/problems/subtree-of-another-tree/description/) + +## Problem Description + +Given the roots of two binary trees root and subRoot, return true if there is a subtree of root with the same structure and node values of subRoot and false otherwise. + +A subtree of a binary tree tree is a tree that consists of a node in tree and all of this node's descendants. The tree tree could also be considered as a subtree of itself. + +## Examples + +### Example 1: + +![Example 1](https://assets.leetcode.com/uploads/2021/04/28/subtree1-tree.jpg) + +``` +Input: root = [3,4,5,1,2], subRoot = [4,1,2] +Output: true +``` + +### Example 2: + +![Example 2](https://assets.leetcode.com/uploads/2021/04/28/subtree2-tree.jpg) + +``` +Input: root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2] +Output: false +``` + +## Constraints + +The number of nodes in the root tree is in the range [1, 2000]. +The number of nodes in the subRoot tree is in the range [1, 1000]. +-10^4 <= root.val <= 10^4 +-10^4 <= subRoot.val <= 10^4 diff --git a/leetcode/subtree_of_another_tree/__init__.py b/leetcode/subtree_of_another_tree/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/subtree_of_another_tree/helpers.py b/leetcode/subtree_of_another_tree/helpers.py new file mode 100644 index 0000000..edd24b6 --- /dev/null +++ b/leetcode/subtree_of_another_tree/helpers.py @@ -0,0 +1,13 @@ +from leetcode_py import TreeNode + + +def run_is_subtree(solution_class: type, root_list: list[int | None], sub_root_list: list[int | None]): + root = TreeNode[int].from_list(root_list) + sub_root = TreeNode[int].from_list(sub_root_list) + implementation = solution_class() + return implementation.is_subtree(root, sub_root) + + +def assert_is_subtree(result: bool, expected: bool) -> bool: + assert result == expected + return True diff --git a/leetcode/subtree_of_another_tree/playground.py b/leetcode/subtree_of_another_tree/playground.py new file mode 100644 index 0000000..222018d --- /dev/null +++ b/leetcode/subtree_of_another_tree/playground.py @@ -0,0 +1,30 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_is_subtree, run_is_subtree +from solution import Solution + +# %% +# Example test case +root_list: list[int | None] = [3, 4, 5, 1, 2] +sub_root_list: list[int | None] = [4, 1, 2] +expected = True + +# %% +result = run_is_subtree(Solution, root_list, sub_root_list) +result + +# %% +assert_is_subtree(result, expected) diff --git a/leetcode/subtree_of_another_tree/solution.py b/leetcode/subtree_of_another_tree/solution.py new file mode 100644 index 0000000..9e04633 --- /dev/null +++ b/leetcode/subtree_of_another_tree/solution.py @@ -0,0 +1,34 @@ +from leetcode_py import TreeNode + + +class Solution: + + # Time: O(m * n) - where m is nodes in root, n is nodes in sub_root + # Space: O(h) - where h is height of root tree (recursion stack) + def is_subtree(self, root: TreeNode[int] | None, sub_root: TreeNode[int] | None) -> bool: + """ + Check if sub_root is a subtree of root. + Uses DFS to check every node in root as potential subtree root. + """ + if not sub_root: + return True + if not root: + return False + + # Check if current root matches sub_root + if self._is_same_tree(root, sub_root): + return True + + # Recursively check left and right subtrees + return self.is_subtree(root.left, sub_root) or self.is_subtree(root.right, sub_root) + + def _is_same_tree(self, p: TreeNode[int] | None, q: TreeNode[int] | None) -> bool: + """Helper method to check if two trees are identical.""" + if not p and not q: + return True + if not p or not q: + return False + if p.val != q.val: + return False + + return self._is_same_tree(p.left, q.left) and self._is_same_tree(p.right, q.right) diff --git a/leetcode/subtree_of_another_tree/test_solution.py b/leetcode/subtree_of_another_tree/test_solution.py new file mode 100644 index 0000000..e77bd9f --- /dev/null +++ b/leetcode/subtree_of_another_tree/test_solution.py @@ -0,0 +1,40 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_is_subtree, run_is_subtree +from .solution import Solution + + +class TestSubtreeOfAnotherTree: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "root_list, sub_root_list, expected", + [ + ([3, 4, 5, 1, 2], [4, 1, 2], True), + ([3, 4, 5, 1, 2, None, None, None, None, 0], [4, 1, 2], False), + ([1], [1], True), + ([1], [2], False), + ([1, 2, 3], [2], True), + ([1, 2, 3], [2, 4], False), + ([1, 2, 3, 4, 5], [2, 4, 5], True), + ([1, 2, 3, 4, 5], [2, 4, 6], False), + ([1, 2, 3, 4, 5, 6, 7], [2, 4, 5], True), + ([1, 2, 3, 4, 5, 6, 7], [3, 6, 7], True), + ([1, 2, 3, 4, 5, 6, 7], [2, 4, 6], False), + ([1, 2, 3, 4, 5, 6, 7], [1, 2, 4], False), + ([1, 2, 3, 4, 5, 6, 7], [4, 5, 6, 7], False), + ([1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7], True), + ], + ) + def test_is_subtree( + self, + root_list: list[int | None], + sub_root_list: list[int | None], + expected: bool, + ): + result = run_is_subtree(Solution, root_list, sub_root_list) + assert_is_subtree(result, expected) diff --git a/leetcode/sum_of_two_integers/README.md b/leetcode/sum_of_two_integers/README.md new file mode 100644 index 0000000..d6359d2 --- /dev/null +++ b/leetcode/sum_of_two_integers/README.md @@ -0,0 +1,31 @@ +# Sum of Two Integers + +**Difficulty:** Medium +**Topics:** Math, Bit Manipulation +**Tags:** blind-75 + +**LeetCode:** [Problem 371](https://leetcode.com/problems/sum-of-two-integers/description/) + +## Problem Description + +Given two integers a and b, return the sum of the two integers without using the operators + and -. + +## Examples + +### Example 1: + +``` +Input: a = 1, b = 2 +Output: 3 +``` + +### Example 2: + +``` +Input: a = 2, b = 3 +Output: 5 +``` + +## Constraints + +-1000 <= a, b <= 1000 diff --git a/leetcode/sum_of_two_integers/__init__.py b/leetcode/sum_of_two_integers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/sum_of_two_integers/helpers.py b/leetcode/sum_of_two_integers/helpers.py new file mode 100644 index 0000000..951766e --- /dev/null +++ b/leetcode/sum_of_two_integers/helpers.py @@ -0,0 +1,8 @@ +def run_get_sum(solution_class: type, a: int, b: int): + implementation = solution_class() + return implementation.get_sum(a, b) + + +def assert_get_sum(result: int, expected: int) -> bool: + assert result == expected + return True diff --git a/leetcode/sum_of_two_integers/playground.py b/leetcode/sum_of_two_integers/playground.py new file mode 100644 index 0000000..51043f5 --- /dev/null +++ b/leetcode/sum_of_two_integers/playground.py @@ -0,0 +1,30 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_get_sum, run_get_sum +from solution import Solution + +# %% +# Example test case +a = 1 +b = 2 +expected = 3 + +# %% +result = run_get_sum(Solution, a, b) +result + +# %% +assert_get_sum(result, expected) diff --git a/leetcode/sum_of_two_integers/solution.py b/leetcode/sum_of_two_integers/solution.py new file mode 100644 index 0000000..557e1cc --- /dev/null +++ b/leetcode/sum_of_two_integers/solution.py @@ -0,0 +1,28 @@ +class Solution: + + # Time: O(1) - constant time bit operations + # Space: O(1) - no extra space used + def get_sum(self, a: int, b: int) -> int: + """ + Add two integers without using + or - operators. + Uses bit manipulation approach: + 1. XOR gives us the sum without carry + 2. AND + left shift gives us the carry + 3. Repeat until no carry remains + """ + # Handle 32-bit signed integer overflow + mask = 0xFFFFFFFF + + while b != 0: + # Calculate sum without carry + sum_without_carry = (a ^ b) & mask + # Calculate carry + carry = ((a & b) << 1) & mask + a = sum_without_carry + b = carry + + # Handle negative result for 32-bit signed integer + if a > 0x7FFFFFFF: + a = ~(a ^ mask) + + return a diff --git a/leetcode/sum_of_two_integers/test_solution.py b/leetcode/sum_of_two_integers/test_solution.py new file mode 100644 index 0000000..fca9e23 --- /dev/null +++ b/leetcode/sum_of_two_integers/test_solution.py @@ -0,0 +1,36 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_get_sum, run_get_sum +from .solution import Solution + + +class TestSumOfTwoIntegers: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "a, b, expected", + [ + (1, 2, 3), + (2, 3, 5), + (-1, 1, 0), + (0, 0, 0), + (100, 200, 300), + (-100, -200, -300), + (1, -1, 0), + (999, 1, 1000), + (-999, -1, -1000), + (0, 1, 1), + (1, 0, 1), + (5, -3, 2), + (-5, 3, -2), + (1000, 0, 1000), + (0, 1000, 1000), + ], + ) + def test_get_sum(self, a: int, b: int, expected: int): + result = run_get_sum(Solution, a, b) + assert_get_sum(result, expected) diff --git a/leetcode/top_k_frequent_elements/README.md b/leetcode/top_k_frequent_elements/README.md new file mode 100644 index 0000000..f9d4b7f --- /dev/null +++ b/leetcode/top_k_frequent_elements/README.md @@ -0,0 +1,43 @@ +# Top K Frequent Elements + +**Difficulty:** Medium +**Topics:** Array, Hash Table, Divide and Conquer, Sorting, Heap (Priority Queue), Bucket Sort, Counting, Quickselect +**Tags:** blind-75 + +**LeetCode:** [Problem 347](https://leetcode.com/problems/top-k-frequent-elements/description/) + +## Problem Description + +Given an integer array `nums` and an integer `k`, return _the_ `k` _most frequent elements_. You may return the answer in **any order**. + +## Examples + +### Example 1: + +``` +Input: nums = [1,1,1,2,2,3], k = 2 +Output: [1,2] +``` + +### Example 2: + +``` +Input: nums = [1], k = 1 +Output: [1] +``` + +### Example 3: + +``` +Input: nums = [1,2,1,2,1,2,3,1,3,2], k = 2 +Output: [1,2] +``` + +## Constraints + +- 1 <= nums.length <= 10^5 +- -10^4 <= nums[i] <= 10^4 +- k is in the range [1, the number of unique elements in the array]. +- It is **guaranteed** that the answer is **unique**. + +**Follow up:** Your algorithm's time complexity must be better than `O(n log n)`, where n is the array's size. diff --git a/leetcode/top_k_frequent_elements/__init__.py b/leetcode/top_k_frequent_elements/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/top_k_frequent_elements/helpers.py b/leetcode/top_k_frequent_elements/helpers.py new file mode 100644 index 0000000..26ea1de --- /dev/null +++ b/leetcode/top_k_frequent_elements/helpers.py @@ -0,0 +1,8 @@ +def run_top_k_frequent(solution_class: type, nums: list[int], k: int): + implementation = solution_class() + return implementation.top_k_frequent(nums, k) + + +def assert_top_k_frequent(result: list[int], expected: list[int]) -> bool: + assert set(result) == set(expected) + return True diff --git a/leetcode/top_k_frequent_elements/playground.py b/leetcode/top_k_frequent_elements/playground.py new file mode 100644 index 0000000..574a843 --- /dev/null +++ b/leetcode/top_k_frequent_elements/playground.py @@ -0,0 +1,30 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_top_k_frequent, run_top_k_frequent +from solution import Solution + +# %% +# Example test case +nums = [1, 1, 1, 2, 2, 3] +k = 2 +expected = [1, 2] + +# %% +result = run_top_k_frequent(Solution, nums, k) +result + +# %% +assert_top_k_frequent(result, expected) diff --git a/leetcode/top_k_frequent_elements/solution.py b/leetcode/top_k_frequent_elements/solution.py new file mode 100644 index 0000000..f289609 --- /dev/null +++ b/leetcode/top_k_frequent_elements/solution.py @@ -0,0 +1,25 @@ +import heapq +from collections import Counter +from typing import List + + +class Solution: + def top_k_frequent(self, nums: List[int], k: int) -> List[int]: + """ + Optimized version using heap for O(n log k) time complexity. + + Time: O(n log k) - heap operations + Space: O(n) - for counter and heap + """ + counter = Counter(nums) + + # Use min heap of size k - keep the k most frequent elements + heap: list[tuple[int, int]] = [] + for num, count in counter.items(): + if len(heap) < k: + heapq.heappush(heap, (count, num)) + elif count > heap[0][0]: + heapq.heapreplace(heap, (count, num)) + + # Extract numbers from heap (order doesn't matter for this problem) + return [num for _, num in heap] diff --git a/leetcode/top_k_frequent_elements/test_solution.py b/leetcode/top_k_frequent_elements/test_solution.py new file mode 100644 index 0000000..0161d02 --- /dev/null +++ b/leetcode/top_k_frequent_elements/test_solution.py @@ -0,0 +1,39 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_top_k_frequent, run_top_k_frequent +from .solution import Solution + + +class TestTestTopKFrequentElements: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "solution_class, nums, k, expected", + [ + (Solution, [1, 1, 1, 2, 2, 3], 2, [1, 2]), + (Solution, [1], 1, [1]), + (Solution, [1, 2, 1, 2, 1, 2, 3, 1, 3, 2], 2, [1, 2]), + (Solution, [1, 2, 3, 4, 5], 1, [1]), + (Solution, [1, 1, 2, 2, 3, 3], 3, [1, 2, 3]), + (Solution, [1, 1, 1, 1, 1], 1, [1]), + (Solution, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5, [1, 2, 3, 4, 5]), + (Solution, [1, 1, 2, 2, 3, 3, 4, 4, 5, 5], 2, [1, 2]), + (Solution, [1, 2, 3, 1, 2, 3, 1, 2, 3], 3, [1, 2, 3]), + (Solution, [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4], 4, [1, 2, 3, 4]), + (Solution, [1, 1, 1, 1, 2, 2, 2, 3, 3, 4], 3, [1, 2, 3]), + ( + Solution, + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5], + 5, + [1, 2, 3, 4, 5], + ), + (Solution, [1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5], 3, [1, 2, 3]), + ], + ) + def test_top_k_frequent(self, solution_class, nums: list[int], k: int, expected: list[int]): + result = run_top_k_frequent(solution_class, nums, k) + assert_top_k_frequent(result, expected) diff --git a/leetcode/word_search_ii/README.md b/leetcode/word_search_ii/README.md new file mode 100644 index 0000000..87bf60d --- /dev/null +++ b/leetcode/word_search_ii/README.md @@ -0,0 +1,44 @@ +# Word Search II + +**Difficulty:** Hard +**Topics:** Array, String, Backtracking, Trie, Matrix +**Tags:** blind-75 + +**LeetCode:** [Problem 212](https://leetcode.com/problems/word-search-ii/description/) + +## Problem Description + +Given an m x n board of characters and a list of strings words, return all words on the board. + +Each word must be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once in a word. + +## Examples + +### Example 1: + +``` +Input: board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"] +Output: ["eat","oath"] +``` + +**Explanation:** The words "eat" and "oath" can be found on the board. + +### Example 2: + +``` +Input: board = [["a","b"],["c","d"]], words = ["abcb"] +Output: [] +``` + +**Explanation:** The word "abcb" cannot be found on the board. + +## Constraints + +- m == board.length +- n == board[i].length +- 1 <= m, n <= 12 +- board[i][j] is a lowercase English letter. +- 1 <= words.length <= 3 \* 10^4 +- 1 <= words[i].length <= 10 +- words[i] consists of lowercase English letters. +- All the strings of words are unique. diff --git a/leetcode/word_search_ii/__init__.py b/leetcode/word_search_ii/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/leetcode/word_search_ii/helpers.py b/leetcode/word_search_ii/helpers.py new file mode 100644 index 0000000..cb88fd2 --- /dev/null +++ b/leetcode/word_search_ii/helpers.py @@ -0,0 +1,8 @@ +def run_find_words(solution_class: type, board: list[list[str]], words: list[str]): + implementation = solution_class() + return implementation.find_words(board, words) + + +def assert_find_words(result: list[str], expected: list[str]) -> bool: + assert set(result) == set(expected) + return True diff --git a/leetcode/word_search_ii/playground.py b/leetcode/word_search_ii/playground.py new file mode 100644 index 0000000..e3a7af1 --- /dev/null +++ b/leetcode/word_search_ii/playground.py @@ -0,0 +1,30 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.17.3 +# kernelspec: +# display_name: leetcode-py-py3.13 +# language: python +# name: python3 +# --- + +# %% +from helpers import assert_find_words, run_find_words +from solution import Solution + +# %% +# Example test case +board = [["o", "a", "a", "n"], ["e", "t", "a", "e"], ["i", "h", "k", "r"], ["i", "f", "l", "v"]] +words = ["oath", "pea", "eat", "rain"] +expected = ["eat", "oath"] + +# %% +result = run_find_words(Solution, board, words) +result + +# %% +assert_find_words(result, expected) diff --git a/leetcode/word_search_ii/solution.py b/leetcode/word_search_ii/solution.py new file mode 100644 index 0000000..85b6476 --- /dev/null +++ b/leetcode/word_search_ii/solution.py @@ -0,0 +1,63 @@ +from typing import List + + +class TrieNode: + def __init__(self): + self.children = {} + self.word = None + + +class Solution: + def find_words(self, board: List[List[str]], words: List[str]) -> List[str]: + """ + Optimized version with early termination and word removal. + + Time: O(m*n*4^L) where m*n is board size, L is max word length + Space: O(W*L) where W is number of words, L is max word length + """ + if not board or not board[0] or not words: + return [] + + # Build trie + root = TrieNode() + for word in words: + node = root + for char in word: + if char not in node.children: + node.children[char] = TrieNode() + node = node.children[char] + node.word = word + + m, n = len(board), len(board[0]) + result = set() + + def dfs(i: int, j: int, node: TrieNode) -> None: + if i < 0 or i >= m or j < 0 or j >= n: + return + + char = board[i][j] + if char not in node.children: + return + + node = node.children[char] + if node.word: + result.add(node.word) + # Remove word from trie to avoid duplicates + node.word = None + + # Mark as visited + board[i][j] = "#" + + # Explore all 4 directions + for di, dj in [(0, 1), (1, 0), (0, -1), (-1, 0)]: + dfs(i + di, j + dj, node) + + # Restore + board[i][j] = char + + # Try starting from each cell + for i in range(m): + for j in range(n): + dfs(i, j, root) + + return list(result) diff --git a/leetcode/word_search_ii/test_solution.py b/leetcode/word_search_ii/test_solution.py new file mode 100644 index 0000000..2464714 --- /dev/null +++ b/leetcode/word_search_ii/test_solution.py @@ -0,0 +1,98 @@ +import pytest + +from leetcode_py import logged_test + +from .helpers import assert_find_words, run_find_words +from .solution import Solution + + +class TestWordSearchII: + def setup_method(self): + self.solution = Solution() + + @logged_test + @pytest.mark.parametrize( + "board, words, expected", + [ + ( + [ + ["o", "a", "a", "n"], + ["e", "t", "a", "e"], + ["i", "h", "k", "r"], + ["i", "f", "l", "v"], + ], + ["oath", "pea", "eat", "rain"], + ["eat", "oath"], + ), + ([["a", "b"], ["c", "d"]], ["abcb"], []), + ([["a"]], ["a"], ["a"]), + ([["a"]], ["b"], []), + ([["a", "a"], ["a", "a"]], ["aaaa"], ["aaaa"]), + ([["a", "a"], ["a", "a"]], ["aa"], ["aa"]), + ( + [["a", "b"], ["c", "d"]], + ["ab", "cd", "ac", "bd"], + ["ab", "cd", "ac", "bd"], + ), + ([["a", "b"], ["c", "d"]], ["abcd"], []), + ( + [ + ["o", "a", "a", "n"], + ["e", "t", "a", "e"], + ["i", "h", "k", "r"], + ["i", "f", "l", "v"], + ], + ["oath", "pea", "eat", "rain", "oathf"], + ["oath", "oathf", "eat"], + ), + ( + [["a", "b", "c"], ["a", "e", "d"], ["a", "f", "g"]], + ["abcdefg", "gfedcbaaa", "eaabcdgfa", "befa", "dgc", "ade"], + ["abcdefg", "befa", "eaabcdgfa", "gfedcbaaa"], + ), + ([["a", "b"], ["c", "d"]], ["abdc"], ["abdc"]), + ( + [ + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a"], + ], + [ + "a", + "aa", + "aaa", + "aaaa", + "aaaaa", + "aaaaaa", + "aaaaaaa", + "aaaaaaaa", + "aaaaaaaaa", + "aaaaaaaaaa", + ], + [ + "a", + "aa", + "aaa", + "aaaa", + "aaaaa", + "aaaaaa", + "aaaaaaa", + "aaaaaaaa", + "aaaaaaaaa", + "aaaaaaaaaa", + ], + ), + ], + ) + def test_find_words(self, board: list[list[str]], words: list[str], expected: list[str]): + result = run_find_words(Solution, board, words) + assert_find_words(result, expected) diff --git a/leetcode_py/cli/resources/leetcode/json/problems/add_two_numbers.json b/leetcode_py/cli/resources/leetcode/json/problems/add_two_numbers.json new file mode 100644 index 0000000..947c676 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/add_two_numbers.json @@ -0,0 +1,85 @@ +{ + "problem_name": "add_two_numbers", + "solution_class_name": "Solution", + "problem_number": "2", + "problem_title": "Add Two Numbers", + "difficulty": "Medium", + "topics": "Linked List, Math, Recursion", + "_tags": { "list": ["algo-master-75"] }, + "readme_description": "You are given two **non-empty** linked lists representing two non-negative integers. The digits are stored in **reverse order**, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.\n\nYou may assume the two numbers do not contain any leading zero, except the number 0 itself.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2020/10/02/addtwonumber1.jpg)\n\n```\nInput: l1 = [2,4,3], l2 = [5,6,4]\nOutput: [7,0,8]\nExplanation: 342 + 465 = 807.\n```" + }, + { "content": "```\nInput: l1 = [0], l2 = [0]\nOutput: [0]\n```" }, + { + "content": "```\nInput: l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]\nOutput: [8,9,9,9,0,0,0,1]\n```" + } + ] + }, + "readme_constraints": "- The number of nodes in each linked list is in the range [1, 100].\n- 0 <= Node.val <= 9\n- It is guaranteed that the list represents a number that does not have leading zeros.", + "readme_additional": "", + "helpers_imports": "from leetcode_py import ListNode", + "helpers_content": "", + "helpers_run_name": "add_two_numbers", + "helpers_run_signature": "(solution_class: type, l1_vals: list[int], l2_vals: list[int])", + "helpers_run_body": " l1 = ListNode.from_list(l1_vals)\n l2 = ListNode.from_list(l2_vals)\n implementation = solution_class()\n return implementation.add_two_numbers(l1, l2)", + "helpers_assert_name": "add_two_numbers", + "helpers_assert_signature": "(result: ListNode[int] | None, expected_vals: list[int]) -> bool", + "helpers_assert_body": " expected = ListNode.from_list(expected_vals)\n assert result == expected\n return True", + "solution_imports": "from leetcode_py import ListNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_add_two_numbers, run_add_two_numbers\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "AddTwoNumbers", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "add_two_numbers", + "signature": "(self, l1: ListNode[int] | None, l2: ListNode[int] | None) -> ListNode[int] | None", + "body": " # TODO: Implement add_two_numbers\n return None" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_add_two_numbers", + "signature": "(self, l1_vals: list[int], l2_vals: list[int], expected_vals: list[int])", + "parametrize": "l1_vals, l2_vals, expected_vals", + "test_cases": { + "list": [ + "([2, 4, 3], [5, 6, 4], [7, 0, 8])", + "([0], [0], [0])", + "([9, 9, 9, 9, 9, 9, 9], [9, 9, 9, 9], [8, 9, 9, 9, 0, 0, 0, 1])", + "([1], [2], [3])", + "([5], [5], [0, 1])", + "([1, 8], [0], [1, 8])", + "([0], [1, 8], [1, 8])", + "([9, 9], [1], [0, 0, 1])", + "([1], [9, 9], [0, 0, 1])", + "([2, 4, 3], [5, 6, 4], [7, 0, 8])", + "([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [5, 6, 4], [6, 6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])", + "([5, 6, 4], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [6, 6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])", + "([9], [9], [8, 1])", + "([9, 9], [9, 9], [8, 9, 1])", + "([1, 2, 3], [4, 5, 6], [5, 7, 9])", + "([7, 8, 9], [1, 2, 3], [8, 0, 3, 1])", + "([1, 2, 3, 4, 5], [6, 7, 8], [7, 9, 1, 5, 5])" + ] + }, + "body": " result = run_add_two_numbers(Solution, l1_vals, l2_vals)\n assert_add_two_numbers(result, expected_vals)" + } + ] + }, + "playground_imports": "from helpers import run_add_two_numbers, assert_add_two_numbers\nfrom solution import Solution\nfrom leetcode_py import ListNode", + "playground_setup": "# Example test case\nl1_vals = [2, 4, 3]\nl2_vals = [5, 6, 4]\nexpected_vals = [7, 0, 8]", + "playground_run": "result = run_add_two_numbers(Solution, l1_vals, l2_vals)\nresult", + "playground_assert": "assert_add_two_numbers(result, expected_vals)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/counting_bits.json b/leetcode_py/cli/resources/leetcode/json/problems/counting_bits.json new file mode 100644 index 0000000..0a569bd --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/counting_bits.json @@ -0,0 +1,91 @@ +{ + "problem_name": "counting_bits", + "solution_class_name": "Solution", + "problem_number": "338", + "problem_title": "Counting Bits", + "difficulty": "Easy", + "topics": "Dynamic Programming, Bit Manipulation", + "_tags": { + "list": ["blind-75"] + }, + "readme_description": "Given an integer `n`, return *an array* `ans` *of length* `n + 1` *such that for each* `i` *(0 <= i <= n),* `ans[i]` *is the **number of*** `1`***'s** in the binary representation of* `i`.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: n = 2\nOutput: [0,1,1]\nExplanation:\n0 --> 0\n1 --> 1\n2 --> 10\n```" + }, + { + "content": "```\nInput: n = 5\nOutput: [0,1,1,2,1,2]\nExplanation:\n0 --> 0\n1 --> 1\n2 --> 10\n3 --> 11\n4 --> 100\n5 --> 101\n```" + } + ] + }, + "readme_constraints": "- 0 <= n <= 10^5", + "readme_additional": "**Follow up:**\n\n- It is very easy to come up with a solution with a runtime of `O(n log n)`. Can you do it in linear time `O(n)` and possibly in a single pass?\n- Can you do it without using any built-in function (i.e., like `__builtin_popcount` in C++)?", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "count_bits", + "helpers_run_signature": "(solution_class: type, n: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.count_bits(n)", + "helpers_assert_name": "count_bits", + "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_count_bits, run_count_bits\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "TestCountingBits", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "count_bits", + "signature": "(self, n: int) -> list[int]", + "body": " # TODO: Implement count_bits\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [ + { + "name": "setup_method", + "parameters": "", + "body": "self.solution = Solution()" + } + ] + }, + "playground_imports": "from helpers import run_count_bits, assert_count_bits\nfrom solution import Solution", + "playground_setup": "# Example test case\nn = 2\nexpected = [0, 1, 1]", + "playground_run": "result = run_count_bits(Solution, n)\nresult", + "playground_assert": "assert_count_bits(result, expected)", + "_test_methods": { + "list": [ + { + "name": "test_count_bits", + "signature": "(self, solution_class, n: int, expected: list[int])", + "parametrize": "solution_class, n, expected", + "test_cases": { + "list": [ + "(Solution, 2, [0, 1, 1])", + "(Solution, 5, [0, 1, 1, 2, 1, 2])", + "(Solution, 0, [0])", + "(Solution, 1, [0, 1])", + "(Solution, 3, [0, 1, 1, 2])", + "(Solution, 4, [0, 1, 1, 2, 1])", + "(Solution, 6, [0, 1, 1, 2, 1, 2, 2])", + "(Solution, 7, [0, 1, 1, 2, 1, 2, 2, 3])", + "(Solution, 8, [0, 1, 1, 2, 1, 2, 2, 3, 1])", + "(Solution, 9, [0, 1, 1, 2, 1, 2, 2, 3, 1, 2])", + "(Solution, 10, [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2])", + "(Solution, 15, [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4])", + "(Solution, 16, [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1])" + ] + }, + "body": " result = run_count_bits(solution_class, n)\n assert_count_bits(result, expected)" + } + ] + }, + "playground_test_case": "n = 2", + "playground_execution": "result = run_count_bits(Solution, n)", + "playground_assertion": "assert_count_bits(result, [0, 1, 1])" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/house_robber_ii.json b/leetcode_py/cli/resources/leetcode/json/problems/house_robber_ii.json new file mode 100644 index 0000000..d531d34 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/house_robber_ii.json @@ -0,0 +1,98 @@ +{ + "problem_name": "house_robber_ii", + "solution_class_name": "Solution", + "problem_number": "213", + "problem_title": "House Robber II", + "difficulty": "Medium", + "topics": "Array, Dynamic Programming", + "_tags": { + "list": ["blind-75"] + }, + "readme_description": "You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. All houses at this place are **arranged in a circle.** That means the first house is the neighbor of the last one. Meanwhile, adjacent houses have a security system connected, and **it will automatically contact the police if two adjacent houses were broken into on the same night**.\n\nGiven an integer array `nums` representing the amount of money of each house, return *the maximum amount of money you can rob tonight **without alerting the police***.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums = [2,3,2]\nOutput: 3\n```\n**Explanation:** You cannot rob house 1 (money = 2) and then rob house 3 (money = 2), because they are adjacent houses." + }, + { + "content": "```\nInput: nums = [1,2,3,1]\nOutput: 4\n```\n**Explanation:** Rob house 1 (money = 1) and then rob house 3 (money = 3).\nTotal amount you can rob = 1 + 3 = 4." + }, + { + "content": "```\nInput: nums = [1,2,3]\nOutput: 3\n```" + } + ] + }, + "readme_constraints": "- 1 <= nums.length <= 100\n- 0 <= nums[i] <= 1000", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "rob", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.rob(nums)", + "helpers_assert_name": "rob", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_rob, run_rob\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "HouseRobberII", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "rob", + "signature": "(self, nums: list[int]) -> int", + "body": " # TODO: Implement rob\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [ + { + "name": "setup_method", + "parameters": "", + "body": "self.solution = Solution()" + } + ] + }, + "_test_methods": { + "list": [ + { + "name": "test_rob", + "signature": "(self, nums: list[int], expected: int)", + "parametrize": "nums, expected", + "test_cases": { + "list": [ + "([2, 3, 2], 3)", + "([1, 2, 3, 1], 4)", + "([1, 2, 3], 3)", + "([1], 1)", + "([1, 2], 2)", + "([2, 1, 1, 2], 3)", + "([1, 2, 3, 4, 5], 8)", + "([2, 3, 2, 3, 2], 6)", + "([1, 2, 1, 1], 3)", + "([2, 1, 1, 1], 3)", + "([1, 2, 3, 4, 5, 1, 2, 3, 4, 5], 16)", + "([0], 0)", + "([0, 0], 0)", + "([1, 0, 0, 1], 1)", + "([2, 7, 9, 3, 1], 11)", + "([1, 2, 3, 1, 2, 3], 6)", + "([2, 1, 1, 2, 1, 1], 4)", + "([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 30)", + "([100, 1, 1, 100], 101)", + "([1, 100, 1, 1, 100], 200)" + ] + }, + "body": " result = run_rob(Solution, nums)\n assert_rob(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_rob, assert_rob\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [2, 3, 2]\nexpected = 3", + "playground_run": "result = run_rob(Solution, nums)\nresult", + "playground_assert": "assert_rob(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/longest_common_subsequence.json b/leetcode_py/cli/resources/leetcode/json/problems/longest_common_subsequence.json new file mode 100644 index 0000000..4689031 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/longest_common_subsequence.json @@ -0,0 +1,88 @@ +{ + "problem_name": "longest_common_subsequence", + "solution_class_name": "Solution", + "problem_number": "1143", + "problem_title": "Longest Common Subsequence", + "difficulty": "Medium", + "topics": "String, Dynamic Programming", + "_tags": { "list": ["blind-75"] }, + "readme_description": "Given two strings `text1` and `text2`, return *the length of their longest **common subsequence**. *If there is no **common subsequence**, return `0`.\n\nA **subsequence** of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.\n\n- For example, `\"ace\"` is a subsequence of `\"abcde\"`.\n\nA **common subsequence** of two strings is a subsequence that is common to both strings.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: text1 = \"abcde\", text2 = \"ace\" \nOutput: 3 \nExplanation: The longest common subsequence is \"ace\" and its length is 3.\n```" + }, + { + "content": "```\nInput: text1 = \"abc\", text2 = \"abc\"\nOutput: 3\nExplanation: The longest common subsequence is \"abc\" and its length is 3.\n```" + }, + { + "content": "```\nInput: text1 = \"abc\", text2 = \"def\"\nOutput: 0\nExplanation: There is no such common subsequence, so the result is 0.\n```" + } + ] + }, + "readme_constraints": "- 1 <= text1.length, text2.length <= 1000\n- text1 and text2 consist of only lowercase English characters.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "longest_common_subsequence", + "helpers_run_signature": "(solution_class: type, text1: str, text2: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.longest_common_subsequence(text1, text2)", + "helpers_assert_name": "longest_common_subsequence", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_longest_common_subsequence, run_longest_common_subsequence\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "LongestCommonSubsequence", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "longest_common_subsequence", + "signature": "(self, text1: str, text2: str) -> int", + "body": " # TODO: Implement longest_common_subsequence\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_longest_common_subsequence", + "signature": "(self, text1: str, text2: str, expected: int)", + "parametrize": "text1, text2, expected", + "test_cases": { + "list": [ + "('abcde', 'ace', 3)", + "('abc', 'abc', 3)", + "('abc', 'def', 0)", + "('', '', 0)", + "('a', 'a', 1)", + "('a', 'b', 0)", + "('ab', 'ba', 1)", + "('abc', 'bca', 2)", + "('abcdef', 'ace', 3)", + "('abcdef', 'adf', 3)", + "('abcdef', 'xyz', 0)", + "('abcdef', 'abcdef', 6)", + "('abcdef', 'fedcba', 1)", + "('abcdef', 'aceg', 3)", + "('abcdef', 'bdf', 3)", + "('abcdef', 'bdfh', 3)", + "('abcdef', 'bdfhj', 3)", + "('abcdef', 'bdfhjl', 3)" + ] + }, + "body": " result = run_longest_common_subsequence(Solution, text1, text2)\n assert_longest_common_subsequence(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_longest_common_subsequence, assert_longest_common_subsequence\nfrom solution import Solution", + "playground_setup": "# Example test case\ntext1 = 'abcde'\ntext2 = 'ace'\nexpected = 3", + "playground_run": "result = run_longest_common_subsequence(Solution, text1, text2)\nresult", + "playground_assert": "assert_longest_common_subsequence(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/longest_repeating_character_replacement.json b/leetcode_py/cli/resources/leetcode/json/problems/longest_repeating_character_replacement.json new file mode 100644 index 0000000..d401ebe --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/longest_repeating_character_replacement.json @@ -0,0 +1,93 @@ +{ + "problem_name": "longest_repeating_character_replacement", + "solution_class_name": "Solution", + "problem_number": "424", + "problem_title": "Longest Repeating Character Replacement", + "difficulty": "Medium", + "topics": "Hash Table, String, Sliding Window", + "_tags": { + "list": ["blind-75"] + }, + "readme_description": "You are given a string s and an integer k. You can choose any character of the string and change it to any other uppercase English character. You can perform this operation at most k times.\n\nReturn the length of the longest substring containing the same letter you can get after performing the above operations.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: s = \"ABAB\", k = 2\nOutput: 4\nExplanation: Replace the two 'A's with two 'B's or vice versa.\n```" + }, + { + "content": "```\nInput: s = \"AABABBA\", k = 1\nOutput: 4\nExplanation: Replace the one 'A' in the middle with 'B' and form \"AABBBBA\".\nThe substring \"BBBB\" has the longest repeating letters, which is 4.\nThere may exists other ways to achieve this answer too.\n```" + } + ] + }, + "readme_constraints": "1 <= s.length <= 10^5\ns consists of only uppercase English letters.\n0 <= k <= s.length", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "character_replacement", + "helpers_run_signature": "(solution_class: type, s: str, k: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.character_replacement(s, k)", + "helpers_assert_name": "character_replacement", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_character_replacement, run_character_replacement\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "LongestRepeatingCharacterReplacement", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "character_replacement", + "signature": "(self, s: str, k: int) -> int", + "body": " # TODO: Implement character_replacement\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [ + { + "name": "setup_method", + "parameters": "", + "body": "self.solution = Solution()" + } + ] + }, + "_test_methods": { + "list": [ + { + "name": "test_character_replacement", + "signature": "(self, s: str, k: int, expected: int)", + "parametrize": "s, k, expected", + "test_cases": { + "list": [ + "('ABAB', 2, 4)", + "('AABABBA', 1, 4)", + "('AAAA', 0, 4)", + "('ABCDE', 0, 1)", + "('ABCDE', 4, 5)", + "('A', 0, 1)", + "('A', 1, 1)", + "('AAAB', 0, 3)", + "('AAAB', 1, 4)", + "('ABABAB', 2, 5)", + "('ABABAB', 3, 6)", + "('ABCDEF', 0, 1)", + "('ABCDEF', 1, 2)", + "('ABCDEF', 5, 6)", + "('AABABBA', 2, 5)" + ] + }, + "body": " result = run_character_replacement(Solution, s, k)\n assert_character_replacement(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_character_replacement, assert_character_replacement\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = 'ABAB'\nk = 2\nexpected = 4", + "playground_run": "result = run_character_replacement(Solution, s, k)\nresult", + "playground_assert": "assert_character_replacement(result, expected)", + "playground_test_case": "s = 'ABAB', k = 2", + "playground_execution": "result = run_character_replacement(Solution, s, k)", + "playground_assertion": "assert_character_replacement(result, 4)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/median_of_two_sorted_arrays.json b/leetcode_py/cli/resources/leetcode/json/problems/median_of_two_sorted_arrays.json new file mode 100644 index 0000000..e979e7d --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/median_of_two_sorted_arrays.json @@ -0,0 +1,85 @@ +{ + "problem_name": "median_of_two_sorted_arrays", + "solution_class_name": "Solution", + "problem_number": "4", + "problem_title": "Median of Two Sorted Arrays", + "difficulty": "Hard", + "topics": "Array, Binary Search, Divide and Conquer", + "_tags": { "list": ["algo-master-75"] }, + "readme_description": "Given two sorted arrays `nums1` and `nums2` of size `m` and `n` respectively, return **the median** of the two sorted arrays.\n\nThe overall run time complexity should be `O(log (m+n))`.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums1 = [1,3], nums2 = [2]\nOutput: 2.00000\nExplanation: merged array = [1,2,3] and median is 2.\n```" + }, + { + "content": "```\nInput: nums1 = [1,2], nums2 = [3,4]\nOutput: 2.50000\nExplanation: merged array = [1,2,3,4] and median is (2 + 3) / 2 = 2.5.\n```" + } + ] + }, + "readme_constraints": "- nums1.length == m\n- nums2.length == n\n- 0 <= m <= 1000\n- 0 <= n <= 1000\n- 1 <= m + n <= 2000\n- -10^6 <= nums1[i], nums2[i] <= 10^6", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "find_median_sorted_arrays", + "helpers_run_signature": "(solution_class: type, nums1: list[int], nums2: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.find_median_sorted_arrays(nums1, nums2)", + "helpers_assert_name": "find_median_sorted_arrays", + "helpers_assert_signature": "(result: float, expected: float) -> bool", + "helpers_assert_body": " assert abs(result - expected) < 1e-5\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_find_median_sorted_arrays, run_find_median_sorted_arrays\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "MedianOfTwoSortedArrays", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "find_median_sorted_arrays", + "signature": "(self, nums1: list[int], nums2: list[int]) -> float", + "body": " # TODO: Implement find_median_sorted_arrays\n return 0.0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_find_median_sorted_arrays", + "signature": "(self, nums1: list[int], nums2: list[int], expected: float)", + "parametrize": "nums1, nums2, expected", + "test_cases": { + "list": [ + "([1, 3], [2], 2.0)", + "([1, 2], [3, 4], 2.5)", + "([1], [], 1.0)", + "([], [1], 1.0)", + "([1, 2, 3], [], 2.0)", + "([], [1, 2, 3], 2.0)", + "([1, 2, 3, 4], [], 2.5)", + "([], [1, 2, 3, 4], 2.5)", + "([1, 3, 5], [2, 4, 6], 3.5)", + "([1, 2, 3, 4, 5], [6, 7, 8, 9, 10], 5.5)", + "([1, 2, 3, 4, 5, 6], [7, 8, 9, 10], 5.5)", + "([1, 2, 3, 4, 5], [6, 7, 8, 9, 10, 11], 6.0)", + "([1, 2, 3, 4, 5, 6, 7], [8, 9, 10], 5.5)", + "([1, 2, 3, 4, 5, 6, 7, 8], [9, 10], 5.5)", + "([1, 2, 3, 4, 5, 6, 7, 8, 9], [10], 5.5)", + "([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [], 5.5)", + "([1, 1, 1, 1], [2, 2, 2, 2], 1.5)", + "([1, 1, 1, 1, 1], [2, 2, 2, 2], 1.0)" + ] + }, + "body": " result = run_find_median_sorted_arrays(Solution, nums1, nums2)\n assert_find_median_sorted_arrays(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_find_median_sorted_arrays, assert_find_median_sorted_arrays\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums1 = [1, 3]\nnums2 = [2]\nexpected = 2.0", + "playground_run": "result = run_find_median_sorted_arrays(Solution, nums1, nums2)\nresult", + "playground_assert": "assert_find_median_sorted_arrays(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/missing_number.json b/leetcode_py/cli/resources/leetcode/json/problems/missing_number.json new file mode 100644 index 0000000..cbeec97 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/missing_number.json @@ -0,0 +1,94 @@ +{ + "problem_name": "missing_number", + "solution_class_name": "Solution", + "problem_number": "268", + "problem_title": "Missing Number", + "difficulty": "Easy", + "topics": "Array, Hash Table, Math, Binary Search, Bit Manipulation, Sorting", + "_tags": { + "list": ["blind-75"] + }, + "readme_description": "Given an array `nums` containing `n` distinct numbers in the range `[0, n]`, return *the only number in the range that is missing from the array.*", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums = [3,0,1]\nOutput: 2\n```\n**Explanation:**\n\n`n = 3` since there are 3 numbers, so all numbers are in the range `[0,3]`. 2 is the missing number in the range since it does not appear in `nums`." + }, + { + "content": "```\nInput: nums = [0,1]\nOutput: 2\n```\n**Explanation:**\n\n`n = 2` since there are 2 numbers, so all numbers are in the range `[0,2]`. 2 is the missing number in the range since it does not appear in `nums`." + }, + { + "content": "```\nInput: nums = [9,6,4,2,3,5,7,0,1]\nOutput: 8\n```\n**Explanation:**\n\n`n = 9` since there are 9 numbers, so all numbers are in the range `[0,9]`. 8 is the missing number in the range since it does not appear in `nums`." + } + ] + }, + "readme_constraints": "- n == nums.length\n- 1 <= n <= 10^4\n- 0 <= nums[i] <= n\n- All the numbers of nums are **unique**.", + "readme_additional": "**Follow up:** Could you implement a solution using only `O(1)` extra space complexity and `O(n)` runtime complexity?", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "missing_number", + "helpers_run_signature": "(solution_class: type, nums: list[int])", + "helpers_run_body": " implementation = solution_class()\n return implementation.missing_number(nums)", + "helpers_assert_name": "missing_number", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_missing_number, run_missing_number\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "TestMissingNumber", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "missing_number", + "signature": "(self, nums: list[int]) -> int", + "body": " # TODO: Implement missing_number\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [ + { + "name": "setup_method", + "parameters": "", + "body": "self.solution = Solution()" + } + ] + }, + "playground_imports": "from helpers import run_missing_number, assert_missing_number\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [3, 0, 1]\nexpected = 2", + "playground_run": "result = run_missing_number(Solution, nums)\nresult", + "playground_assert": "assert_missing_number(result, expected)", + "_test_methods": { + "list": [ + { + "name": "test_missing_number", + "signature": "(self, solution_class, nums: list[int], expected: int)", + "parametrize": "solution_class, nums, expected", + "test_cases": { + "list": [ + "(Solution, [3, 0, 1], 2)", + "(Solution, [0, 1], 2)", + "(Solution, [9, 6, 4, 2, 3, 5, 7, 0, 1], 8)", + "(Solution, [0], 1)", + "(Solution, [1], 0)", + "(Solution, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 10)", + "(Solution, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 0)", + "(Solution, [0, 1, 2, 3, 4, 6, 7, 8, 9, 10], 5)", + "(Solution, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], 0)", + "(Solution, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], 20)", + "(Solution, [2, 0, 1], 3)", + "(Solution, [1, 0], 2)", + "(Solution, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100], 101)" + ] + }, + "body": " result = run_missing_number(solution_class, nums)\n assert_missing_number(result, expected)" + } + ] + }, + "playground_test_case": "nums = [3, 0, 1]", + "playground_execution": "result = run_missing_number(Solution, nums)", + "playground_assertion": "assert_missing_number(result, 2)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/non_overlapping_intervals.json b/leetcode_py/cli/resources/leetcode/json/problems/non_overlapping_intervals.json new file mode 100644 index 0000000..d85c9bb --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/non_overlapping_intervals.json @@ -0,0 +1,96 @@ +{ + "problem_name": "non_overlapping_intervals", + "solution_class_name": "Solution", + "problem_number": "435", + "problem_title": "Non-overlapping Intervals", + "difficulty": "Medium", + "topics": "Array, Dynamic Programming, Greedy, Sorting", + "_tags": { + "list": ["blind-75"] + }, + "readme_description": "Given an array of intervals intervals where intervals[i] = [starti, endi], return the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping.\n\nNote that intervals which only touch at a point are non-overlapping. For example, [1, 2] and [2, 3] are non-overlapping.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: intervals = [[1,2],[2,3],[3,4],[1,3]]\nOutput: 1\nExplanation: [1,3] can be removed and the rest of the intervals are non-overlapping.\n```" + }, + { + "content": "```\nInput: intervals = [[1,2],[1,2],[1,2]]\nOutput: 2\nExplanation: You need to remove two [1,2] to make the rest of the intervals non-overlapping.\n```" + }, + { + "content": "```\nInput: intervals = [[1,2],[2,3]]\nOutput: 0\nExplanation: You don't need to remove any of the intervals since they're already non-overlapping.\n```" + } + ] + }, + "readme_constraints": "1 <= intervals.length <= 10^5\nintervals[i].length == 2\n-5 * 10^4 <= starti < endi <= 5 * 10^4", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "erase_overlap_intervals", + "helpers_run_signature": "(solution_class: type, intervals: list[list[int]])", + "helpers_run_body": " implementation = solution_class()\n return implementation.erase_overlap_intervals(intervals)", + "helpers_assert_name": "erase_overlap_intervals", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_erase_overlap_intervals, run_erase_overlap_intervals\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "NonOverlappingIntervals", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "erase_overlap_intervals", + "signature": "(self, intervals: list[list[int]]) -> int", + "body": " # TODO: Implement erase_overlap_intervals\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [ + { + "name": "setup_method", + "parameters": "", + "body": "self.solution = Solution()" + } + ] + }, + "_test_methods": { + "list": [ + { + "name": "test_erase_overlap_intervals", + "signature": "(self, intervals: list[list[int]], expected: int)", + "parametrize": "intervals, expected", + "test_cases": { + "list": [ + "([[1,2],[2,3],[3,4],[1,3]], 1)", + "([[1,2],[1,2],[1,2]], 2)", + "([[1,2],[2,3]], 0)", + "([[1,2]], 0)", + "([[1,2],[1,3],[2,3],[3,4]], 1)", + "([[1,2],[2,3],[3,4],[1,3],[2,4]], 2)", + "([[1,2],[3,4],[5,6]], 0)", + "([[1,2],[1,3],[1,4]], 2)", + "([[1,2],[2,3],[3,4],[4,5]], 0)", + "([[1,2],[1,2],[1,2],[1,2]], 3)", + "([[1,3],[2,4],[3,5],[4,6]], 2)", + "([[1,2],[3,4],[5,6],[7,8]], 0)", + "([[1,4],[2,3],[3,4]], 1)", + "([[1,2],[1,2],[1,2],[2,3]], 2)", + "([[1,2],[2,3],[1,3],[3,4]], 1)" + ] + }, + "body": " result = run_erase_overlap_intervals(Solution, intervals)\n assert_erase_overlap_intervals(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_erase_overlap_intervals, assert_erase_overlap_intervals\nfrom solution import Solution", + "playground_setup": "# Example test case\nintervals = [[1,2],[2,3],[3,4],[1,3]]\nexpected = 1", + "playground_run": "result = run_erase_overlap_intervals(Solution, intervals)\nresult", + "playground_assert": "assert_erase_overlap_intervals(result, expected)", + "playground_test_case": "intervals = [[1,2],[2,3],[3,4],[1,3]]", + "playground_execution": "result = run_erase_overlap_intervals(Solution, intervals)", + "playground_assertion": "assert_erase_overlap_intervals(result, 1)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/palindromic_substrings.json b/leetcode_py/cli/resources/leetcode/json/problems/palindromic_substrings.json new file mode 100644 index 0000000..8eb7a20 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/palindromic_substrings.json @@ -0,0 +1,93 @@ +{ + "problem_name": "palindromic_substrings", + "solution_class_name": "Solution", + "problem_number": "647", + "problem_title": "Palindromic Substrings", + "difficulty": "Medium", + "topics": "Two Pointers, String, Dynamic Programming", + "_tags": { + "list": ["blind-75"] + }, + "readme_description": "Given a string s, return the number of palindromic substrings in it.\n\nA string is a palindrome when it reads the same backward as forward.\n\nA substring is a contiguous sequence of characters within the string.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: s = \"abc\"\nOutput: 3\nExplanation: Three palindromic strings: \"a\", \"b\", \"c\".\n```" + }, + { + "content": "```\nInput: s = \"aaa\"\nOutput: 6\nExplanation: Six palindromic strings: \"a\", \"a\", \"a\", \"aa\", \"aa\", \"aaa\".\n```" + } + ] + }, + "readme_constraints": "1 <= s.length <= 1000\ns consists of lowercase English letters.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "count_substrings", + "helpers_run_signature": "(solution_class: type, s: str)", + "helpers_run_body": " implementation = solution_class()\n return implementation.count_substrings(s)", + "helpers_assert_name": "count_substrings", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_count_substrings, run_count_substrings\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "PalindromicSubstrings", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "count_substrings", + "signature": "(self, s: str) -> int", + "body": " # TODO: Implement count_substrings\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [ + { + "name": "setup_method", + "parameters": "", + "body": "self.solution = Solution()" + } + ] + }, + "_test_methods": { + "list": [ + { + "name": "test_count_substrings", + "signature": "(self, s: str, expected: int)", + "parametrize": "s, expected", + "test_cases": { + "list": [ + "('abc', 3)", + "('aaa', 6)", + "('a', 1)", + "('aa', 3)", + "('aba', 4)", + "('abccba', 9)", + "('racecar', 10)", + "('abcdef', 6)", + "('aab', 4)", + "('ababa', 9)", + "('aaaaa', 15)", + "('ab', 2)", + "('abcba', 7)", + "('abacaba', 12)", + "('xyz', 3)" + ] + }, + "body": " result = run_count_substrings(Solution, s)\n assert_count_substrings(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_count_substrings, assert_count_substrings\nfrom solution import Solution", + "playground_setup": "# Example test case\ns = 'abc'\nexpected = 3", + "playground_run": "result = run_count_substrings(Solution, s)\nresult", + "playground_assert": "assert_count_substrings(result, expected)", + "playground_test_case": "s = 'abc'", + "playground_execution": "result = run_count_substrings(Solution, s)", + "playground_assertion": "assert_count_substrings(result, 3)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/reverse_integer.json b/leetcode_py/cli/resources/leetcode/json/problems/reverse_integer.json new file mode 100644 index 0000000..da80c8e --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/reverse_integer.json @@ -0,0 +1,84 @@ +{ + "problem_name": "reverse_integer", + "solution_class_name": "Solution", + "problem_number": "7", + "problem_title": "Reverse Integer", + "difficulty": "Medium", + "topics": "Math", + "_tags": { "list": ["algo-master-75"] }, + "readme_description": "Given a signed 32-bit integer `x`, return `x` *with its digits reversed*. If reversing `x` causes the value to go outside the signed 32-bit integer range `[-2^31, 2^31 - 1]`, then return `0`.\n\n**Assume the environment does not allow you to store 64-bit integers (signed or unsigned).**", + "_readme_examples": { + "list": [ + { "content": "```\nInput: x = 123\nOutput: 321\n```" }, + { "content": "```\nInput: x = -123\nOutput: -321\n```" }, + { "content": "```\nInput: x = 120\nOutput: 21\n```" } + ] + }, + "readme_constraints": "- -2^31 <= x <= 2^31 - 1", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "reverse", + "helpers_run_signature": "(solution_class: type, x: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.reverse(x)", + "helpers_assert_name": "reverse", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_reverse, run_reverse\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ReverseInteger", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "reverse", + "signature": "(self, x: int) -> int", + "body": " # TODO: Implement reverse\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_reverse", + "signature": "(self, x: int, expected: int)", + "parametrize": "x, expected", + "test_cases": { + "list": [ + "(123, 321)", + "(-123, -321)", + "(120, 21)", + "(0, 0)", + "(1, 1)", + "(-1, -1)", + "(10, 1)", + "(-10, -1)", + "(100, 1)", + "(-100, -1)", + "(1534236469, 0)", + "(-1534236469, 0)", + "(2147483647, 0)", + "(-2147483648, 0)", + "(1463847412, 2147483641)", + "(-1463847412, -2147483641)", + "(123456789, 987654321)", + "(-123456789, -987654321)", + "(1000000003, 0)", + "(-1000000003, 0)" + ] + }, + "body": " result = run_reverse(Solution, x)\n assert_reverse(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_reverse, assert_reverse\nfrom solution import Solution", + "playground_setup": "# Example test case\nx = 123\nexpected = 321", + "playground_run": "result = run_reverse(Solution, x)\nresult", + "playground_assert": "assert_reverse(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/reverse_nodes_in_k_group.json b/leetcode_py/cli/resources/leetcode/json/problems/reverse_nodes_in_k_group.json new file mode 100644 index 0000000..4790134 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/reverse_nodes_in_k_group.json @@ -0,0 +1,87 @@ +{ + "problem_name": "reverse_nodes_in_k_group", + "solution_class_name": "Solution", + "problem_number": "25", + "problem_title": "Reverse Nodes in k-Group", + "difficulty": "Hard", + "topics": "Linked List, Recursion", + "_tags": { "list": ["algo-master-75"] }, + "readme_description": "Given the `head` of a linked list, reverse the nodes of the list `k` at a time, and return *the modified list*.\n\n`k` is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of `k` then left-out nodes, in the end, should remain as it is.\n\nYou may not alter the values in the list's nodes, only nodes themselves may be changed.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2020/10/03/reverse_ex1.jpg)\n\n```\nInput: head = [1,2,3,4,5], k = 2\nOutput: [2,1,4,3,5]\n```" + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2020/10/03/reverse_ex2.jpg)\n\n```\nInput: head = [1,2,3,4,5], k = 3\nOutput: [3,2,1,4,5]\n```" + } + ] + }, + "readme_constraints": "- The number of nodes in the list is n.\n- 1 <= k <= n <= 5000\n- 0 <= Node.val <= 1000", + "readme_additional": "**Follow-up:** Can you solve the problem in `O(1)` extra memory space?", + "helpers_imports": "from leetcode_py import ListNode", + "helpers_content": "", + "helpers_run_name": "reverse_k_group", + "helpers_run_signature": "(solution_class: type, head_vals: list[int], k: int)", + "helpers_run_body": " head = ListNode.from_list(head_vals)\n implementation = solution_class()\n return implementation.reverse_k_group(head, k)", + "helpers_assert_name": "reverse_k_group", + "helpers_assert_signature": "(result: ListNode[int] | None, expected_vals: list[int]) -> bool", + "helpers_assert_body": " expected = ListNode.from_list(expected_vals)\n assert result == expected\n return True", + "solution_imports": "from leetcode_py import ListNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_reverse_k_group, run_reverse_k_group\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "ReverseNodesInKGroup", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "reverse_k_group", + "signature": "(self, head: ListNode[int] | None, k: int) -> ListNode[int] | None", + "body": " # TODO: Implement reverse_k_group\n return None" + } + ] + }, + "_test_helper_methods": { + "list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }] + }, + "_test_methods": { + "list": [ + { + "name": "test_reverse_k_group", + "signature": "(self, head_vals: list[int], k: int, expected_vals: list[int])", + "parametrize": "head_vals, k, expected_vals", + "test_cases": { + "list": [ + "([1, 2, 3, 4, 5], 2, [2, 1, 4, 3, 5])", + "([1, 2, 3, 4, 5], 3, [3, 2, 1, 4, 5])", + "([1, 2, 3, 4, 5], 1, [1, 2, 3, 4, 5])", + "([1, 2, 3, 4, 5], 5, [5, 4, 3, 2, 1])", + "([1], 1, [1])", + "([1, 2], 1, [1, 2])", + "([1, 2], 2, [2, 1])", + "([1, 2, 3], 1, [1, 2, 3])", + "([1, 2, 3], 2, [2, 1, 3])", + "([1, 2, 3], 3, [3, 2, 1])", + "([1, 2, 3, 4], 2, [2, 1, 4, 3])", + "([1, 2, 3, 4], 3, [3, 2, 1, 4])", + "([1, 2, 3, 4], 4, [4, 3, 2, 1])", + "([1, 2, 3, 4, 5, 6], 2, [2, 1, 4, 3, 6, 5])", + "([1, 2, 3, 4, 5, 6], 3, [3, 2, 1, 6, 5, 4])", + "([1, 2, 3, 4, 5, 6], 4, [4, 3, 2, 1, 5, 6])", + "([1, 2, 3, 4, 5, 6, 7], 2, [2, 1, 4, 3, 6, 5, 7])", + "([1, 2, 3, 4, 5, 6, 7], 3, [3, 2, 1, 6, 5, 4, 7])", + "([1, 2, 3, 4, 5, 6, 7, 8], 2, [2, 1, 4, 3, 6, 5, 8, 7])", + "([1, 2, 3, 4, 5, 6, 7, 8], 3, [3, 2, 1, 6, 5, 4, 7, 8])" + ] + }, + "body": " result = run_reverse_k_group(Solution, head_vals, k)\n assert_reverse_k_group(result, expected_vals)" + } + ] + }, + "playground_imports": "from helpers import run_reverse_k_group, assert_reverse_k_group\nfrom solution import Solution\nfrom leetcode_py import ListNode", + "playground_setup": "# Example test case\nhead_vals = [1, 2, 3, 4, 5]\nk = 2\nexpected_vals = [2, 1, 4, 3, 5]", + "playground_run": "result = run_reverse_k_group(Solution, head_vals, k)\nresult", + "playground_assert": "assert_reverse_k_group(result, expected_vals)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/subtree_of_another_tree.json b/leetcode_py/cli/resources/leetcode/json/problems/subtree_of_another_tree.json new file mode 100644 index 0000000..73fe9d7 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/subtree_of_another_tree.json @@ -0,0 +1,92 @@ +{ + "problem_name": "subtree_of_another_tree", + "solution_class_name": "Solution", + "problem_number": "572", + "problem_title": "Subtree of Another Tree", + "difficulty": "Easy", + "topics": "Tree, Depth-First Search, String Matching, Binary Tree, Hash Function", + "_tags": { + "list": ["blind-75"] + }, + "readme_description": "Given the roots of two binary trees root and subRoot, return true if there is a subtree of root with the same structure and node values of subRoot and false otherwise.\n\nA subtree of a binary tree tree is a tree that consists of a node in tree and all of this node's descendants. The tree tree could also be considered as a subtree of itself.", + "_readme_examples": { + "list": [ + { + "content": "![Example 1](https://assets.leetcode.com/uploads/2021/04/28/subtree1-tree.jpg)\n\n```\nInput: root = [3,4,5,1,2], subRoot = [4,1,2]\nOutput: true\n```" + }, + { + "content": "![Example 2](https://assets.leetcode.com/uploads/2021/04/28/subtree2-tree.jpg)\n\n```\nInput: root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]\nOutput: false\n```" + } + ] + }, + "readme_constraints": "The number of nodes in the root tree is in the range [1, 2000].\nThe number of nodes in the subRoot tree is in the range [1, 1000].\n-10^4 <= root.val <= 10^4\n-10^4 <= subRoot.val <= 10^4", + "readme_additional": "", + "helpers_imports": "from leetcode_py import TreeNode", + "helpers_content": "", + "helpers_run_name": "is_subtree", + "helpers_run_signature": "(solution_class: type, root_list: list[int | None], sub_root_list: list[int | None])", + "helpers_run_body": " root = TreeNode[int].from_list(root_list)\n sub_root = TreeNode[int].from_list(sub_root_list)\n implementation = solution_class()\n return implementation.is_subtree(root, sub_root)", + "helpers_assert_name": "is_subtree", + "helpers_assert_signature": "(result: bool, expected: bool) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "from leetcode_py import TreeNode", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_is_subtree, run_is_subtree\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "SubtreeOfAnotherTree", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "is_subtree", + "signature": "(self, root: TreeNode[int] | None, sub_root: TreeNode[int] | None) -> bool", + "body": " # TODO: Implement is_subtree\n return False" + } + ] + }, + "_test_helper_methods": { + "list": [ + { + "name": "setup_method", + "parameters": "", + "body": "self.solution = Solution()" + } + ] + }, + "_test_methods": { + "list": [ + { + "name": "test_is_subtree", + "signature": "(self, root_list: list[int | None], sub_root_list: list[int | None], expected: bool)", + "parametrize": "root_list, sub_root_list, expected", + "test_cases": { + "list": [ + "([3,4,5,1,2], [4,1,2], True)", + "([3,4,5,1,2,None,None,None,None,0], [4,1,2], False)", + "([1], [1], True)", + "([1], [2], False)", + "([1,2,3], [2], True)", + "([1,2,3], [2,4], False)", + "([1,2,3,4,5], [2,4,5], True)", + "([1,2,3,4,5], [2,4,6], False)", + "([1,2,3,4,5,6,7], [2,4,5], True)", + "([1,2,3,4,5,6,7], [3,6,7], True)", + "([1,2,3,4,5,6,7], [2,4,6], False)", + "([1,2,3,4,5,6,7], [1,2,4], False)", + "([1,2,3,4,5,6,7], [4,5,6,7], False)", + "([1,2,3,4,5,6,7], [1,2,3,4,5,6,7], True)" + ] + }, + "body": " result = run_is_subtree(Solution, root_list, sub_root_list)\n assert_is_subtree(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_is_subtree, assert_is_subtree\nfrom solution import Solution\nfrom leetcode_py import TreeNode", + "playground_setup": "# Example test case\nroot_list: list[int | None] = [3,4,5,1,2]\nsub_root_list: list[int | None] = [4,1,2]\nexpected = True", + "playground_run": "result = run_is_subtree(Solution, root_list, sub_root_list)\nresult", + "playground_assert": "assert_is_subtree(result, expected)", + "playground_test_case": "root_list = [3,4,5,1,2], sub_root_list = [4,1,2]", + "playground_execution": "result = run_is_subtree(Solution, root_list, sub_root_list)", + "playground_assertion": "assert_is_subtree(result, True)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/sum_of_two_integers.json b/leetcode_py/cli/resources/leetcode/json/problems/sum_of_two_integers.json new file mode 100644 index 0000000..d2a3c31 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/sum_of_two_integers.json @@ -0,0 +1,93 @@ +{ + "problem_name": "sum_of_two_integers", + "solution_class_name": "Solution", + "problem_number": "371", + "problem_title": "Sum of Two Integers", + "difficulty": "Medium", + "topics": "Math, Bit Manipulation", + "_tags": { + "list": ["blind-75"] + }, + "readme_description": "Given two integers a and b, return the sum of the two integers without using the operators + and -.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: a = 1, b = 2\nOutput: 3\n```" + }, + { + "content": "```\nInput: a = 2, b = 3\nOutput: 5\n```" + } + ] + }, + "readme_constraints": "-1000 <= a, b <= 1000", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "get_sum", + "helpers_run_signature": "(solution_class: type, a: int, b: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.get_sum(a, b)", + "helpers_assert_name": "get_sum", + "helpers_assert_signature": "(result: int, expected: int) -> bool", + "helpers_assert_body": " assert result == expected\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_get_sum, run_get_sum\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "SumOfTwoIntegers", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "get_sum", + "signature": "(self, a: int, b: int) -> int", + "body": " # TODO: Implement get_sum\n return 0" + } + ] + }, + "_test_helper_methods": { + "list": [ + { + "name": "setup_method", + "parameters": "", + "body": "self.solution = Solution()" + } + ] + }, + "_test_methods": { + "list": [ + { + "name": "test_get_sum", + "signature": "(self, a: int, b: int, expected: int)", + "parametrize": "a, b, expected", + "test_cases": { + "list": [ + "(1, 2, 3)", + "(2, 3, 5)", + "(-1, 1, 0)", + "(0, 0, 0)", + "(100, 200, 300)", + "(-100, -200, -300)", + "(1, -1, 0)", + "(999, 1, 1000)", + "(-999, -1, -1000)", + "(0, 1, 1)", + "(1, 0, 1)", + "(5, -3, 2)", + "(-5, 3, -2)", + "(1000, 0, 1000)", + "(0, 1000, 1000)" + ] + }, + "body": " result = run_get_sum(Solution, a, b)\n assert_get_sum(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_get_sum, assert_get_sum\nfrom solution import Solution", + "playground_setup": "# Example test case\na = 1\nb = 2\nexpected = 3", + "playground_run": "result = run_get_sum(Solution, a, b)\nresult", + "playground_assert": "assert_get_sum(result, expected)", + "playground_test_case": "a = 1, b = 2", + "playground_execution": "result = run_get_sum(Solution, a, b)", + "playground_assertion": "assert_get_sum(result, 3)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/top_k_frequent_elements.json b/leetcode_py/cli/resources/leetcode/json/problems/top_k_frequent_elements.json new file mode 100644 index 0000000..944433e --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/top_k_frequent_elements.json @@ -0,0 +1,94 @@ +{ + "problem_name": "top_k_frequent_elements", + "solution_class_name": "Solution", + "problem_number": "347", + "problem_title": "Top K Frequent Elements", + "difficulty": "Medium", + "topics": "Array, Hash Table, Divide and Conquer, Sorting, Heap (Priority Queue), Bucket Sort, Counting, Quickselect", + "_tags": { + "list": ["blind-75"] + }, + "readme_description": "Given an integer array `nums` and an integer `k`, return *the* `k` *most frequent elements*. You may return the answer in **any order**.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: nums = [1,1,1,2,2,3], k = 2\nOutput: [1,2]\n```" + }, + { + "content": "```\nInput: nums = [1], k = 1\nOutput: [1]\n```" + }, + { + "content": "```\nInput: nums = [1,2,1,2,1,2,3,1,3,2], k = 2\nOutput: [1,2]\n```" + } + ] + }, + "readme_constraints": "- 1 <= nums.length <= 10^5\n- -10^4 <= nums[i] <= 10^4\n- k is in the range [1, the number of unique elements in the array].\n- It is **guaranteed** that the answer is **unique**.", + "readme_additional": "**Follow up:** Your algorithm's time complexity must be better than `O(n log n)`, where n is the array's size.", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "top_k_frequent", + "helpers_run_signature": "(solution_class: type, nums: list[int], k: int)", + "helpers_run_body": " implementation = solution_class()\n return implementation.top_k_frequent(nums, k)", + "helpers_assert_name": "top_k_frequent", + "helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool", + "helpers_assert_body": " assert set(result) == set(expected)\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_top_k_frequent, run_top_k_frequent\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "TestTopKFrequentElements", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "top_k_frequent", + "signature": "(self, nums: list[int], k: int) -> list[int]", + "body": " # TODO: Implement top_k_frequent\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [ + { + "name": "setup_method", + "parameters": "", + "body": "self.solution = Solution()" + } + ] + }, + "playground_imports": "from helpers import run_top_k_frequent, assert_top_k_frequent\nfrom solution import Solution", + "playground_setup": "# Example test case\nnums = [1, 1, 1, 2, 2, 3]\nk = 2\nexpected = [1, 2]", + "playground_run": "result = run_top_k_frequent(Solution, nums, k)\nresult", + "playground_assert": "assert_top_k_frequent(result, expected)", + "_test_methods": { + "list": [ + { + "name": "test_top_k_frequent", + "signature": "(self, solution_class, nums: list[int], k: int, expected: list[int])", + "parametrize": "solution_class, nums, k, expected", + "test_cases": { + "list": [ + "(Solution, [1, 1, 1, 2, 2, 3], 2, [1, 2])", + "(Solution, [1], 1, [1])", + "(Solution, [1, 2, 1, 2, 1, 2, 3, 1, 3, 2], 2, [1, 2])", + "(Solution, [1, 2, 3, 4, 5], 1, [1])", + "(Solution, [1, 1, 2, 2, 3, 3], 3, [1, 2, 3])", + "(Solution, [1, 1, 1, 1, 1], 1, [1])", + "(Solution, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5, [1, 2, 3, 4, 5])", + "(Solution, [1, 1, 2, 2, 3, 3, 4, 4, 5, 5], 2, [1, 2])", + "(Solution, [1, 2, 3, 1, 2, 3, 1, 2, 3], 3, [1, 2, 3])", + "(Solution, [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4], 4, [1, 2, 3, 4])", + "(Solution, [1, 1, 1, 1, 2, 2, 2, 3, 3, 4], 3, [1, 2, 3])", + "(Solution, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5], 5, [1, 2, 3, 4, 5])", + "(Solution, [1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5], 3, [1, 2, 3])" + ] + }, + "body": " result = run_top_k_frequent(solution_class, nums, k)\n assert_top_k_frequent(result, expected)" + } + ] + }, + "playground_test_case": "nums = [1, 1, 1, 2, 2, 3], k = 2", + "playground_execution": "result = run_top_k_frequent(Solution, nums, k)", + "playground_assertion": "assert_top_k_frequent(result, [1, 2])" +} diff --git a/leetcode_py/cli/resources/leetcode/json/problems/word_search_ii.json b/leetcode_py/cli/resources/leetcode/json/problems/word_search_ii.json new file mode 100644 index 0000000..a84fa56 --- /dev/null +++ b/leetcode_py/cli/resources/leetcode/json/problems/word_search_ii.json @@ -0,0 +1,87 @@ +{ + "problem_name": "word_search_ii", + "solution_class_name": "Solution", + "problem_number": "212", + "problem_title": "Word Search II", + "difficulty": "Hard", + "topics": "Array, String, Backtracking, Trie, Matrix", + "_tags": { + "list": ["blind-75"] + }, + "readme_description": "Given an m x n board of characters and a list of strings words, return all words on the board.\n\nEach word must be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.", + "_readme_examples": { + "list": [ + { + "content": "```\nInput: board = [[\"o\",\"a\",\"a\",\"n\"],[\"e\",\"t\",\"a\",\"e\"],[\"i\",\"h\",\"k\",\"r\"],[\"i\",\"f\",\"l\",\"v\"]], words = [\"oath\",\"pea\",\"eat\",\"rain\"]\nOutput: [\"eat\",\"oath\"]\n```\n**Explanation:** The words \"eat\" and \"oath\" can be found on the board." + }, + { + "content": "```\nInput: board = [[\"a\",\"b\"],[\"c\",\"d\"]], words = [\"abcb\"]\nOutput: []\n```\n**Explanation:** The word \"abcb\" cannot be found on the board." + } + ] + }, + "readme_constraints": "- m == board.length\n- n == board[i].length\n- 1 <= m, n <= 12\n- board[i][j] is a lowercase English letter.\n- 1 <= words.length <= 3 * 10^4\n- 1 <= words[i].length <= 10\n- words[i] consists of lowercase English letters.\n- All the strings of words are unique.", + "readme_additional": "", + "helpers_imports": "", + "helpers_content": "", + "helpers_run_name": "find_words", + "helpers_run_signature": "(solution_class: type, board: list[list[str]], words: list[str])", + "helpers_run_body": " implementation = solution_class()\n return implementation.find_words(board, words)", + "helpers_assert_name": "find_words", + "helpers_assert_signature": "(result: list[str], expected: list[str]) -> bool", + "helpers_assert_body": " assert set(result) == set(expected)\n return True", + "solution_imports": "", + "solution_contents": "", + "solution_class_content": "", + "test_imports": "import pytest\nfrom leetcode_py import logged_test\nfrom .helpers import assert_find_words, run_find_words\nfrom .solution import Solution", + "test_content": "", + "test_class_name": "WordSearchII", + "test_class_content": " def setup_method(self):\n self.solution = Solution()", + "_solution_methods": { + "list": [ + { + "name": "find_words", + "signature": "(self, board: list[list[str]], words: list[str]) -> list[str]", + "body": " # TODO: Implement find_words\n return []" + } + ] + }, + "_test_helper_methods": { + "list": [ + { + "name": "setup_method", + "parameters": "", + "body": "self.solution = Solution()" + } + ] + }, + "_test_methods": { + "list": [ + { + "name": "test_find_words", + "signature": "(self, board: list[list[str]], words: list[str], expected: list[str])", + "parametrize": "board, words, expected", + "test_cases": { + "list": [ + "([[\"o\",\"a\",\"a\",\"n\"],[\"e\",\"t\",\"a\",\"e\"],[\"i\",\"h\",\"k\",\"r\"],[\"i\",\"f\",\"l\",\"v\"]], [\"oath\",\"pea\",\"eat\",\"rain\"], [\"eat\",\"oath\"])", + "([[\"a\",\"b\"],[\"c\",\"d\"]], [\"abcb\"], [])", + "([[\"a\"]], [\"a\"], [\"a\"])", + "([[\"a\"]], [\"b\"], [])", + "([[\"a\",\"a\"],[\"a\",\"a\"]], [\"aaaa\"], [\"aaaa\"])", + "([[\"a\",\"a\"],[\"a\",\"a\"]], [\"aa\"], [\"aa\"])", + "([[\"a\",\"b\"],[\"c\",\"d\"]], [\"ab\",\"cd\",\"ac\",\"bd\"], [\"ab\",\"cd\",\"ac\",\"bd\"])", + "([[\"a\",\"b\"],[\"c\",\"d\"]], [\"abcd\"], [])", + "([[\"o\",\"a\",\"a\",\"n\"],[\"e\",\"t\",\"a\",\"e\"],[\"i\",\"h\",\"k\",\"r\"],[\"i\",\"f\",\"l\",\"v\"]], [\"oath\",\"pea\",\"eat\",\"rain\",\"oathf\"], [\"oath\",\"oathf\",\"eat\"])", + "([[\"a\",\"b\",\"c\"],[\"a\",\"e\",\"d\"],[\"a\",\"f\",\"g\"]], [\"abcdefg\",\"gfedcbaaa\",\"eaabcdgfa\",\"befa\",\"dgc\",\"ade\"], [\"abcdefg\",\"befa\",\"eaabcdgfa\",\"gfedcbaaa\"])", + "([[\"a\",\"b\"],[\"c\",\"d\"]], [\"abdc\"], [\"abdc\"])", + "([[\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\"],[\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\"],[\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\"],[\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\"],[\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\"],[\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\"],[\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\"],[\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\"],[\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\"],[\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\"],[\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\"],[\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\",\"a\"]], [\"a\",\"aa\",\"aaa\",\"aaaa\",\"aaaaa\",\"aaaaaa\",\"aaaaaaa\",\"aaaaaaaa\",\"aaaaaaaaa\",\"aaaaaaaaaa\"], [\"a\",\"aa\",\"aaa\",\"aaaa\",\"aaaaa\",\"aaaaaa\",\"aaaaaaa\",\"aaaaaaaa\",\"aaaaaaaaa\",\"aaaaaaaaaa\"])" + ] + }, + "body": " result = run_find_words(Solution, board, words)\n assert_find_words(result, expected)" + } + ] + }, + "playground_imports": "from helpers import run_find_words, assert_find_words\nfrom solution import Solution", + "playground_setup": "# Example test case\nboard = [[\"o\",\"a\",\"a\",\"n\"],[\"e\",\"t\",\"a\",\"e\"],[\"i\",\"h\",\"k\",\"r\"],[\"i\",\"f\",\"l\",\"v\"]]\nwords = [\"oath\",\"pea\",\"eat\",\"rain\"]\nexpected = [\"eat\",\"oath\"]", + "playground_run": "result = run_find_words(Solution, board, words)\nresult", + "playground_assert": "assert_find_words(result, expected)" +} diff --git a/leetcode_py/cli/resources/leetcode/json/tags.json5 b/leetcode_py/cli/resources/leetcode/json/tags.json5 index 6acbb49..13fcebf 100644 --- a/leetcode_py/cli/resources/leetcode/json/tags.json5 +++ b/leetcode_py/cli/resources/leetcode/json/tags.json5 @@ -121,6 +121,7 @@ "construct_binary_tree_from_preorder_and_inorder_traversal", "container_with_most_water", "contains_duplicate", + "counting_bits", "course_schedule", "decode_ways", "design_add_and_search_words_data_structure", @@ -128,15 +129,18 @@ "find_minimum_in_rotated_sorted_array", "group_anagrams", "house_robber", + "house_robber_ii", "implement_trie_prefix_tree", "insert_interval", "invert_binary_tree", "jump_game", "kth_smallest_element_in_a_bst", "linked_list_cycle", + "longest_common_subsequence", "longest_consecutive_sequence", "longest_increasing_subsequence", "longest_palindromic_substring", + "longest_repeating_character_replacement", "longest_substring_without_repeating_characters", "lowest_common_ancestor_of_a_binary_search_tree", "maximum_depth_of_binary_tree", @@ -146,9 +150,12 @@ "merge_k_sorted_lists", "merge_two_sorted_lists", "minimum_window_substring", + "missing_number", + "non_overlapping_intervals", "number_of_1_bits", "number_of_islands", "pacific_atlantic_water_flow", + "palindromic_substrings", "product_of_array_except_self", "remove_nth_node_from_end_of_list", "reorder_list", @@ -160,7 +167,10 @@ "serialize_and_deserialize_binary_tree", "set_matrix_zeroes", "spiral_matrix", + "subtree_of_another_tree", + "sum_of_two_integers", "three_sum", + "top_k_frequent_elements", "two_sum", "unique_paths", "valid_anagram", @@ -169,10 +179,12 @@ "validate_binary_search_tree", "word_break", "word_search", + "word_search_ii", ], // NeetCode 150 - (on-going) "neetcode-150": [ + "add_two_numbers", "alien_dictionary", "balanced_binary_tree", "best_time_to_buy_and_sell_stock", @@ -187,6 +199,7 @@ "construct_binary_tree_from_preorder_and_inorder_traversal", "container_with_most_water", "contains_duplicate", + "counting_bits", "course_schedule", "course_schedule_ii", "daily_temperatures", @@ -200,6 +213,7 @@ "gas_station", "group_anagrams", "house_robber", + "house_robber_ii", "implement_trie_prefix_tree", "insert_interval", "invert_binary_tree", @@ -209,30 +223,38 @@ "largest_rectangle_in_histogram", "letter_combinations_of_a_phone_number", "linked_list_cycle", + "longest_common_subsequence", "longest_consecutive_sequence", "longest_increasing_subsequence", "longest_palindromic_substring", + "longest_repeating_character_replacement", "longest_substring_without_repeating_characters", "lowest_common_ancestor_of_a_binary_search_tree", "lru_cache", "maximum_depth_of_binary_tree", "maximum_product_subarray", "maximum_subarray", + "median_of_two_sorted_arrays", "merge_intervals", "merge_k_sorted_lists", "merge_two_sorted_lists", "min_stack", "minimum_window_substring", + "missing_number", + "non_overlapping_intervals", "number_of_1_bits", "number_of_islands", "pacific_atlantic_water_flow", + "palindromic_substrings", "partition_equal_subset_sum", "permutations", "product_of_array_except_self", "remove_nth_node_from_end_of_list", "reorder_list", "reverse_bits", + "reverse_integer", "reverse_linked_list", + "reverse_nodes_in_k_group", "rotate_image", "rotting_oranges", "same_tree", @@ -241,9 +263,12 @@ "set_matrix_zeroes", "spiral_matrix", "subsets", + "subtree_of_another_tree", + "sum_of_two_integers", "task_scheduler", "three_sum", "time_based_key_value_store", + "top_k_frequent_elements", "trapping_rain_water", "two_sum", "unique_paths", @@ -255,23 +280,28 @@ "word_break", "word_ladder", "word_search", + "word_search_ii", ], // Algo Master 75 - (on-going) "algo-master-75": [ + "add_two_numbers", "binary_tree_level_order_traversal", "binary_tree_maximum_path_sum", "binary_tree_right_side_view", "clone_graph", "coin_change", "container_with_most_water", + "counting_bits", "course_schedule_ii", "find_all_anagrams_in_a_string", "find_median_from_data_stream", "group_anagrams", + "house_robber_ii", "implement_trie_prefix_tree", "kth_smallest_element_in_a_bst", "largest_rectangle_in_histogram", + "longest_common_subsequence", "longest_consecutive_sequence", "longest_increasing_subsequence", "longest_substring_without_repeating_characters", @@ -279,15 +309,19 @@ "lru_cache", "majority_element", "maximum_subarray", + "median_of_two_sorted_arrays", "merge_intervals", "merge_k_sorted_lists", "min_stack", "minimum_window_substring", + "non_overlapping_intervals", "number_of_islands", "partition_equal_subset_sum", "permutations", "product_of_array_except_self", "remove_nth_node_from_end_of_list", + "reverse_integer", + "reverse_nodes_in_k_group", "rotate_image", "rotting_oranges", "search_in_rotated_sorted_array", @@ -297,10 +331,12 @@ "subsets", "swap_nodes_in_pairs", "three_sum", + "top_k_frequent_elements", "trapping_rain_water", "valid_parentheses", "validate_binary_search_tree", "word_break", "word_ladder", + "word_search_ii", ], } diff --git a/pyproject.toml b/pyproject.toml index 74cd83b..9e7150b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,6 +82,17 @@ line-length = 105 target-version = 'py310' extend-exclude = ["leetcode_py/cli/resources"] +[tool.ruff.lint] +# Core rules - essential for any Python project +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "N", # pep8-naming +] +ignore = ["N806"] + [tool.ruff.lint.pydocstyle] convention = "numpy"