Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/ci-test-reproducibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,40 @@ jobs:
- name: Install dependencies
run: poetry install --no-interaction --no-ansi

- name: Cache Graphviz installation
id: cache-graphviz
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ~/graphviz-cache
key: graphviz-installed-${{ runner.os }}

- name: Install Graphviz
run: |
if [ "${{ steps.cache-graphviz.outputs.cache-hit }}" = "true" ]; then
sudo cp ~/graphviz-cache/bin/* /usr/bin/ 2>/dev/null || true
sudo cp ~/graphviz-cache/lib/* /usr/lib/x86_64-linux-gnu/ 2>/dev/null || true
sudo cp -r ~/graphviz-cache/share/graphviz /usr/share/ 2>/dev/null || true
sudo cp -r ~/graphviz-cache/lib/graphviz /usr/lib/x86_64-linux-gnu/ 2>/dev/null || true
sudo ldconfig
sudo dot -c
else
sudo apt-get update
sudo apt-get install -y graphviz
mkdir -p ~/graphviz-cache/{bin,lib,share}
cp /usr/bin/{dot,neato,twopi,circo,fdp,sfdp,patchwork,osage} ~/graphviz-cache/bin/ 2>/dev/null || true
cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut,ann,gts}* ~/graphviz-cache/lib/ 2>/dev/null || true
cp -r /usr/lib/x86_64-linux-gnu/graphviz ~/graphviz-cache/lib/ 2>/dev/null || true
cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null || true
fi

- name: Check test case count
run: poetry run python .templates/check_test_cases.py --threshold=10 --max=100

- name: Backup existing problems
run: |
mkdir -p .cache
cp -r leetcode .cache/

- name: Delete existing problems
run: rm -rf leetcode/*/

Expand All @@ -46,3 +80,15 @@ jobs:

- name: Run linting to verify reproducibility
run: make lint

- name: Copy solution files from backup
run: |
for problem in .cache/leetcode/*/; do
problem_name=$(basename "$problem")
if [ -f "$problem/solution.py" ] && [ -d "leetcode/$problem_name" ]; then
cp "$problem/solution.py" "leetcode/$problem_name/solution.py"
fi
done

- name: Run tests
run: make test
6 changes: 5 additions & 1 deletion .templates/check_test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,14 @@ def main(
typer.echo(f"Invalid max_results value: {max_results}", err=True)
raise typer.Exit(1)

typer.echo(f"Files with ≤{threshold} test cases ({len(filtered_files)} total):")
typer.echo(f"Problems with ≤{threshold} test cases ({len(filtered_files)} total):")
for filename, count in filtered_files:
typer.echo(f"{filename}: {count} test cases")

# Exit with non-zero code if any files found
if filtered_files:
raise typer.Exit(1)


if __name__ == "__main__":
typer.run(main)
63 changes: 63 additions & 0 deletions .templates/leetcode/json/two_sum.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"problem_name": "two_sum",
"solution_class_name": "Solution",
"problem_number": "1",
"problem_title": "Two Sum",
"difficulty": "Easy",
"topics": "Array, Hash Table",
"_tags": { "list": ["grind-75"] },
"readme_description": "Given an array of integers `nums` and an integer `target`, return indices of the two numbers such that they add up to `target`.\n\nYou may assume that each input would have exactly one solution, and you may not use the same element twice.\n\nYou can return the answer in any order.",
"_readme_examples": {
"list": [
{
"content": "```\nInput: nums = [2,7,11,15], target = 9\nOutput: [0,1]\n```\n**Explanation:** Because nums[0] + nums[1] == 9, we return [0, 1]."
},
{ "content": "```\nInput: nums = [3,2,4], target = 6\nOutput: [1,2]\n```" },
{ "content": "```\nInput: nums = [3,3], target = 6\nOutput: [0,1]\n```" }
]
},
"readme_constraints": "- 2 <= nums.length <= 10^4\n- -10^9 <= nums[i] <= 10^9\n- -10^9 <= target <= 10^9\n- Only one valid answer exists.",
"readme_additional": "**Follow-up:** Can you come up with an algorithm that is less than O(n^2) time complexity?",
"helpers_imports": "",
"helpers_content": "",
"helpers_run_name": "two_sum",
"helpers_run_signature": "(solution_class: type, nums: list[int], target: int)",
"helpers_run_body": " implementation = solution_class()\n return implementation.two_sum(nums, target)",
"helpers_assert_name": "two_sum",
"helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool",
"helpers_assert_body": " assert result == expected\n return True",
"solution_imports": "",
"solution_contents": "",
"solution_class_content": "",
"test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .helpers import assert_two_sum, run_two_sum\nfrom .solution import Solution",
"test_content": "",
"test_class_name": "TwoSum",
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
"_solution_methods": {
"list": [
{
"name": "two_sum",
"signature": "(self, nums: list[int], target: int) -> list[int]",
"body": " # TODO: Implement two_sum\n return []"
}
]
},
"_test_helper_methods": {
"list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
},
"_test_methods": {
"list": [
{
"name": "test_two_sum",
"signature": "(self, nums: list[int], target: int, expected: list[int])",
"parametrize": "nums, target, expected",
"test_cases": "[([2, 7, 11, 15], 9, [0, 1]), ([3, 2, 4], 6, [1, 2]), ([3, 3], 6, [0, 1]), ([2, 5, 5, 11], 10, [1, 2]), ([1, 2, 3, 4, 5], 8, [2, 4]), ([0, 4, 3, 0], 0, [0, 3]), ([-1, -2, -3, -4, -5], -8, [2, 4]), ([1, 3, 4, 2], 6, [2, 3]), ([5, 75, 25], 100, [1, 2]), ([-3, 4, 3, 90], 0, [0, 2]), ([1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 2], 6, [5, 11]), ([2, 1, 9, 4, 4, 56, 90, 3], 8, [3, 4]), ([89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99], 185, [3, 4]), ([-1000000000, 1000000000], 0, [0, 1]), ([0, 1], 1, [0, 1]), ([1, 2], 5, []), ([3, 5, 7], 1, []), ([10, 20, 30], 15, [])]",
"body": " result = run_two_sum(Solution, nums, target)\n assert_two_sum(result, expected)"
}
]
},
"playground_imports": "from helpers import run_two_sum, assert_two_sum\nfrom solution import Solution",
"playground_setup": "# Example test case\nnums = [2, 7, 11, 15]\ntarget = 9\nexpected = [0, 1]",
"playground_run": "result = run_two_sum(Solution, nums, target)\nresult",
"playground_assert": "assert_two_sum(result, expected)"
}
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PYTHON_VERSION = 3.13
PROBLEM ?= min_stack
PROBLEM ?= two_sum
FORCE ?= 0
COMMA := ,

Expand Down
49 changes: 49 additions & 0 deletions leetcode/two_sum/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Two Sum

**Difficulty:** Easy
**Topics:** Array, Hash Table
**Tags:** grind-75

**LeetCode:** [Problem 1](https://leetcode.com/problems/two-sum/description/)

## Problem Description

Given an array of integers `nums` and an integer `target`, return indices of the two numbers such that they add up to `target`.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

You can return the answer in any order.

## Examples

### Example 1:

```
Input: nums = [2,7,11,15], target = 9
Output: [0,1]
```

**Explanation:** Because nums[0] + nums[1] == 9, we return [0, 1].

### Example 2:

```
Input: nums = [3,2,4], target = 6
Output: [1,2]
```

### Example 3:

```
Input: nums = [3,3], target = 6
Output: [0,1]
```

## Constraints

- 2 <= nums.length <= 10^4
- -10^9 <= nums[i] <= 10^9
- -10^9 <= target <= 10^9
- Only one valid answer exists.

**Follow-up:** Can you come up with an algorithm that is less than O(n^2) time complexity?
Empty file added leetcode/two_sum/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions leetcode/two_sum/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
def run_two_sum(solution_class: type, nums: list[int], target: int):
implementation = solution_class()
return implementation.two_sum(nums, target)


def assert_two_sum(result: list[int], expected: list[int]) -> bool:
assert result == expected
return True
92 changes: 92 additions & 0 deletions leetcode/two_sum/playground.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
"from helpers import assert_two_sum, run_two_sum\n",
"from solution import Solution"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "setup",
"metadata": {},
"outputs": [],
"source": [
"# Example test case\n",
"nums = [2, 7, 11, 15]\n",
"target = 9\n",
"expected = [0, 1]"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "run",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[0, 1]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"result = run_two_sum(Solution, nums, target)\n",
"result"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "assert",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"assert_two_sum(result, expected)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "leetcode-py-py3.13",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
12 changes: 12 additions & 0 deletions leetcode/two_sum/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Solution:

# Time: O(n)
# Space: O(n)
def two_sum(self, nums: list[int], target: int) -> list[int]:
seen: dict[int, int] = {}
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [seen[complement], i]
seen[num] = i
return []
39 changes: 39 additions & 0 deletions leetcode/two_sum/test_solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import pytest

from leetcode_py.test_utils import logged_test

from .helpers import assert_two_sum, run_two_sum
from .solution import Solution


class TestTwoSum:
def setup_method(self):
self.solution = Solution()

@logged_test
@pytest.mark.parametrize(
"nums, target, expected",
[
([2, 7, 11, 15], 9, [0, 1]),
([3, 2, 4], 6, [1, 2]),
([3, 3], 6, [0, 1]),
([2, 5, 5, 11], 10, [1, 2]),
([1, 2, 3, 4, 5], 8, [2, 4]),
([0, 4, 3, 0], 0, [0, 3]),
([-1, -2, -3, -4, -5], -8, [2, 4]),
([1, 3, 4, 2], 6, [2, 3]),
([5, 75, 25], 100, [1, 2]),
([-3, 4, 3, 90], 0, [0, 2]),
([1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 2], 6, [5, 11]),
([2, 1, 9, 4, 4, 56, 90, 3], 8, [3, 4]),
([89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99], 185, [3, 4]),
([-1000000000, 1000000000], 0, [0, 1]),
([0, 1], 1, [0, 1]),
([1, 2], 5, []),
([3, 5, 7], 1, []),
([10, 20, 30], 15, []),
],
)
def test_two_sum(self, nums: list[int], target: int, expected: list[int]):
result = run_two_sum(Solution, nums, target)
assert_two_sum(result, expected)