Skip to content

Commit 4079793

Browse files
authored
feat: add two_sum (#36)
1 parent f6667a8 commit 4079793

File tree

10 files changed

+315
-2
lines changed

10 files changed

+315
-2
lines changed

.github/workflows/ci-test-reproducibility.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,40 @@ jobs:
3535
- name: Install dependencies
3636
run: poetry install --no-interaction --no-ansi
3737

38+
- name: Cache Graphviz installation
39+
id: cache-graphviz
40+
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
41+
with:
42+
path: ~/graphviz-cache
43+
key: graphviz-installed-${{ runner.os }}
44+
45+
- name: Install Graphviz
46+
run: |
47+
if [ "${{ steps.cache-graphviz.outputs.cache-hit }}" = "true" ]; then
48+
sudo cp ~/graphviz-cache/bin/* /usr/bin/ 2>/dev/null || true
49+
sudo cp ~/graphviz-cache/lib/* /usr/lib/x86_64-linux-gnu/ 2>/dev/null || true
50+
sudo cp -r ~/graphviz-cache/share/graphviz /usr/share/ 2>/dev/null || true
51+
sudo cp -r ~/graphviz-cache/lib/graphviz /usr/lib/x86_64-linux-gnu/ 2>/dev/null || true
52+
sudo ldconfig
53+
sudo dot -c
54+
else
55+
sudo apt-get update
56+
sudo apt-get install -y graphviz
57+
mkdir -p ~/graphviz-cache/{bin,lib,share}
58+
cp /usr/bin/{dot,neato,twopi,circo,fdp,sfdp,patchwork,osage} ~/graphviz-cache/bin/ 2>/dev/null || true
59+
cp /usr/lib/x86_64-linux-gnu/lib{gvc,cgraph,cdt,pathplan,gvpr,lab-gamut,ann,gts}* ~/graphviz-cache/lib/ 2>/dev/null || true
60+
cp -r /usr/lib/x86_64-linux-gnu/graphviz ~/graphviz-cache/lib/ 2>/dev/null || true
61+
cp -r /usr/share/graphviz ~/graphviz-cache/share/ 2>/dev/null || true
62+
fi
63+
64+
- name: Check test case count
65+
run: poetry run python .templates/check_test_cases.py --threshold=10 --max=100
66+
67+
- name: Backup existing problems
68+
run: |
69+
mkdir -p .cache
70+
cp -r leetcode .cache/
71+
3872
- name: Delete existing problems
3973
run: rm -rf leetcode/*/
4074

@@ -46,3 +80,15 @@ jobs:
4680

4781
- name: Run linting to verify reproducibility
4882
run: make lint
83+
84+
- name: Copy solution files from backup
85+
run: |
86+
for problem in .cache/leetcode/*/; do
87+
problem_name=$(basename "$problem")
88+
if [ -f "$problem/solution.py" ] && [ -d "leetcode/$problem_name" ]; then
89+
cp "$problem/solution.py" "leetcode/$problem_name/solution.py"
90+
fi
91+
done
92+
93+
- name: Run tests
94+
run: make test

.templates/check_test_cases.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,14 @@ def main(
8282
typer.echo(f"Invalid max_results value: {max_results}", err=True)
8383
raise typer.Exit(1)
8484

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

89+
# Exit with non-zero code if any files found
90+
if filtered_files:
91+
raise typer.Exit(1)
92+
8993

9094
if __name__ == "__main__":
9195
typer.run(main)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"problem_name": "two_sum",
3+
"solution_class_name": "Solution",
4+
"problem_number": "1",
5+
"problem_title": "Two Sum",
6+
"difficulty": "Easy",
7+
"topics": "Array, Hash Table",
8+
"_tags": { "list": ["grind-75"] },
9+
"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.",
10+
"_readme_examples": {
11+
"list": [
12+
{
13+
"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]."
14+
},
15+
{ "content": "```\nInput: nums = [3,2,4], target = 6\nOutput: [1,2]\n```" },
16+
{ "content": "```\nInput: nums = [3,3], target = 6\nOutput: [0,1]\n```" }
17+
]
18+
},
19+
"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.",
20+
"readme_additional": "**Follow-up:** Can you come up with an algorithm that is less than O(n^2) time complexity?",
21+
"helpers_imports": "",
22+
"helpers_content": "",
23+
"helpers_run_name": "two_sum",
24+
"helpers_run_signature": "(solution_class: type, nums: list[int], target: int)",
25+
"helpers_run_body": " implementation = solution_class()\n return implementation.two_sum(nums, target)",
26+
"helpers_assert_name": "two_sum",
27+
"helpers_assert_signature": "(result: list[int], expected: list[int]) -> bool",
28+
"helpers_assert_body": " assert result == expected\n return True",
29+
"solution_imports": "",
30+
"solution_contents": "",
31+
"solution_class_content": "",
32+
"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",
33+
"test_content": "",
34+
"test_class_name": "TwoSum",
35+
"test_class_content": " def setup_method(self):\n self.solution = Solution()",
36+
"_solution_methods": {
37+
"list": [
38+
{
39+
"name": "two_sum",
40+
"signature": "(self, nums: list[int], target: int) -> list[int]",
41+
"body": " # TODO: Implement two_sum\n return []"
42+
}
43+
]
44+
},
45+
"_test_helper_methods": {
46+
"list": [{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }]
47+
},
48+
"_test_methods": {
49+
"list": [
50+
{
51+
"name": "test_two_sum",
52+
"signature": "(self, nums: list[int], target: int, expected: list[int])",
53+
"parametrize": "nums, target, expected",
54+
"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, [])]",
55+
"body": " result = run_two_sum(Solution, nums, target)\n assert_two_sum(result, expected)"
56+
}
57+
]
58+
},
59+
"playground_imports": "from helpers import run_two_sum, assert_two_sum\nfrom solution import Solution",
60+
"playground_setup": "# Example test case\nnums = [2, 7, 11, 15]\ntarget = 9\nexpected = [0, 1]",
61+
"playground_run": "result = run_two_sum(Solution, nums, target)\nresult",
62+
"playground_assert": "assert_two_sum(result, expected)"
63+
}

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PYTHON_VERSION = 3.13
2-
PROBLEM ?= min_stack
2+
PROBLEM ?= two_sum
33
FORCE ?= 0
44
COMMA := ,
55

leetcode/two_sum/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Two Sum
2+
3+
**Difficulty:** Easy
4+
**Topics:** Array, Hash Table
5+
**Tags:** grind-75
6+
7+
**LeetCode:** [Problem 1](https://leetcode.com/problems/two-sum/description/)
8+
9+
## Problem Description
10+
11+
Given an array of integers `nums` and an integer `target`, return indices of the two numbers such that they add up to `target`.
12+
13+
You may assume that each input would have exactly one solution, and you may not use the same element twice.
14+
15+
You can return the answer in any order.
16+
17+
## Examples
18+
19+
### Example 1:
20+
21+
```
22+
Input: nums = [2,7,11,15], target = 9
23+
Output: [0,1]
24+
```
25+
26+
**Explanation:** Because nums[0] + nums[1] == 9, we return [0, 1].
27+
28+
### Example 2:
29+
30+
```
31+
Input: nums = [3,2,4], target = 6
32+
Output: [1,2]
33+
```
34+
35+
### Example 3:
36+
37+
```
38+
Input: nums = [3,3], target = 6
39+
Output: [0,1]
40+
```
41+
42+
## Constraints
43+
44+
- 2 <= nums.length <= 10^4
45+
- -10^9 <= nums[i] <= 10^9
46+
- -10^9 <= target <= 10^9
47+
- Only one valid answer exists.
48+
49+
**Follow-up:** Can you come up with an algorithm that is less than O(n^2) time complexity?

leetcode/two_sum/__init__.py

Whitespace-only changes.

leetcode/two_sum/helpers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
def run_two_sum(solution_class: type, nums: list[int], target: int):
2+
implementation = solution_class()
3+
return implementation.two_sum(nums, target)
4+
5+
6+
def assert_two_sum(result: list[int], expected: list[int]) -> bool:
7+
assert result == expected
8+
return True

leetcode/two_sum/playground.ipynb

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"id": "imports",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"from helpers import assert_two_sum, run_two_sum\n",
11+
"from solution import Solution"
12+
]
13+
},
14+
{
15+
"cell_type": "code",
16+
"execution_count": 2,
17+
"id": "setup",
18+
"metadata": {},
19+
"outputs": [],
20+
"source": [
21+
"# Example test case\n",
22+
"nums = [2, 7, 11, 15]\n",
23+
"target = 9\n",
24+
"expected = [0, 1]"
25+
]
26+
},
27+
{
28+
"cell_type": "code",
29+
"execution_count": 3,
30+
"id": "run",
31+
"metadata": {},
32+
"outputs": [
33+
{
34+
"data": {
35+
"text/plain": [
36+
"[0, 1]"
37+
]
38+
},
39+
"execution_count": 3,
40+
"metadata": {},
41+
"output_type": "execute_result"
42+
}
43+
],
44+
"source": [
45+
"result = run_two_sum(Solution, nums, target)\n",
46+
"result"
47+
]
48+
},
49+
{
50+
"cell_type": "code",
51+
"execution_count": 4,
52+
"id": "assert",
53+
"metadata": {},
54+
"outputs": [
55+
{
56+
"data": {
57+
"text/plain": [
58+
"True"
59+
]
60+
},
61+
"execution_count": 4,
62+
"metadata": {},
63+
"output_type": "execute_result"
64+
}
65+
],
66+
"source": [
67+
"assert_two_sum(result, expected)"
68+
]
69+
}
70+
],
71+
"metadata": {
72+
"kernelspec": {
73+
"display_name": "leetcode-py-py3.13",
74+
"language": "python",
75+
"name": "python3"
76+
},
77+
"language_info": {
78+
"codemirror_mode": {
79+
"name": "ipython",
80+
"version": 3
81+
},
82+
"file_extension": ".py",
83+
"mimetype": "text/x-python",
84+
"name": "python",
85+
"nbconvert_exporter": "python",
86+
"pygments_lexer": "ipython3",
87+
"version": "3.13.7"
88+
}
89+
},
90+
"nbformat": 4,
91+
"nbformat_minor": 5
92+
}

leetcode/two_sum/solution.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class Solution:
2+
3+
# Time: O(n)
4+
# Space: O(n)
5+
def two_sum(self, nums: list[int], target: int) -> list[int]:
6+
seen: dict[int, int] = {}
7+
for i, num in enumerate(nums):
8+
complement = target - num
9+
if complement in seen:
10+
return [seen[complement], i]
11+
seen[num] = i
12+
return []

leetcode/two_sum/test_solution.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import pytest
2+
3+
from leetcode_py.test_utils import logged_test
4+
5+
from .helpers import assert_two_sum, run_two_sum
6+
from .solution import Solution
7+
8+
9+
class TestTwoSum:
10+
def setup_method(self):
11+
self.solution = Solution()
12+
13+
@logged_test
14+
@pytest.mark.parametrize(
15+
"nums, target, expected",
16+
[
17+
([2, 7, 11, 15], 9, [0, 1]),
18+
([3, 2, 4], 6, [1, 2]),
19+
([3, 3], 6, [0, 1]),
20+
([2, 5, 5, 11], 10, [1, 2]),
21+
([1, 2, 3, 4, 5], 8, [2, 4]),
22+
([0, 4, 3, 0], 0, [0, 3]),
23+
([-1, -2, -3, -4, -5], -8, [2, 4]),
24+
([1, 3, 4, 2], 6, [2, 3]),
25+
([5, 75, 25], 100, [1, 2]),
26+
([-3, 4, 3, 90], 0, [0, 2]),
27+
([1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 2], 6, [5, 11]),
28+
([2, 1, 9, 4, 4, 56, 90, 3], 8, [3, 4]),
29+
([89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99], 185, [3, 4]),
30+
([-1000000000, 1000000000], 0, [0, 1]),
31+
([0, 1], 1, [0, 1]),
32+
([1, 2], 5, []),
33+
([3, 5, 7], 1, []),
34+
([10, 20, 30], 15, []),
35+
],
36+
)
37+
def test_two_sum(self, nums: list[int], target: int, expected: list[int]):
38+
result = run_two_sum(Solution, nums, target)
39+
assert_two_sum(result, expected)

0 commit comments

Comments
 (0)