Skip to content

Commit e810c96

Browse files
committed
feat: add Longest Palindrome
1 parent ef58b73 commit e810c96

File tree

9 files changed

+242
-31
lines changed

9 files changed

+242
-31
lines changed

.amazonq/rules/problem-creation.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ When user requests a problem by **number** or **name/slug**, the assistant will:
1515
4. **Create** JSON file in `.templates/leetcode/json/{problem_name}.json`
1616
5. **Update** Makefile with `PROBLEM ?= {problem_name}`
1717
6. **Generate** problem structure using `make p-gen`
18-
7. **Verify** with `make lint` - fix template issues in JSON if possible, or manually fix generated files if template limitations
19-
8. **Iterate** if JSON fixes: re-run `make p-gen PROBLEM={problem_name} FORCE=1` and `make lint` until passes to ensure reproducibility
18+
7. **Verify** with `make p-lint` - fix template issues in JSON if possible, or manually fix generated files if template limitations
19+
8. **Iterate** if JSON fixes: re-run `make p-gen PROBLEM={problem_name} FORCE=1` and `make p-lint` until passes to ensure reproducibility
2020

2121
## Scraping Commands
2222

@@ -148,7 +148,10 @@ make p-gen PROBLEM={problem_name} FORCE=1
148148
# Test specific problem
149149
make p-test PROBLEM={problem_name}
150150

151-
# Lint check
151+
# Lint problem only (faster)
152+
make p-lint PROBLEM={problem_name}
153+
154+
# Lint entire project
152155
make lint
153156
```
154157

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"problem_name": "longest_palindrome",
3+
"solution_class_name": "Solution",
4+
"problem_number": "409",
5+
"problem_title": "Longest Palindrome",
6+
"difficulty": "Easy",
7+
"topics": "Hash Table, String, Greedy",
8+
"tags": ["grind-75"],
9+
"readme_description": "Given a string `s` which consists of lowercase or uppercase letters, return the length of the longest palindrome that can be built with those letters.\n\nLetters are case sensitive, for example, \"Aa\" is not considered a palindrome.",
10+
"readme_examples": [
11+
{
12+
"content": "```\nInput: s = \"abccccdd\"\nOutput: 7\n```\n**Explanation:** One longest palindrome that can be built is \"dccaccd\", whose length is 7."
13+
},
14+
{
15+
"content": "```\nInput: s = \"a\"\nOutput: 1\n```\n**Explanation:** The longest palindrome that can be built is \"a\", whose length is 1."
16+
}
17+
],
18+
"readme_constraints": "- `1 <= s.length <= 2000`\n- `s` consists of lowercase and/or uppercase English letters only.",
19+
"readme_additional": "",
20+
"solution_imports": "",
21+
"solution_methods": [
22+
{
23+
"name": "longest_palindrome",
24+
"parameters": "s: str",
25+
"return_type": "int",
26+
"dummy_return": "0"
27+
}
28+
],
29+
"test_imports": "import pytest\nfrom leetcode_py.test_utils import logged_test\nfrom .solution import Solution",
30+
"test_class_name": "LongestPalindrome",
31+
"test_helper_methods": [
32+
{ "name": "setup_method", "parameters": "", "body": "self.solution = Solution()" }
33+
],
34+
"test_methods": [
35+
{
36+
"name": "test_longest_palindrome",
37+
"parametrize": "s, expected",
38+
"parametrize_typed": "s: str, expected: int",
39+
"test_cases": "[('abccccdd', 7), ('a', 1), ('Aa', 1), ('aabbcc', 6)]",
40+
"body": "result = self.solution.longest_palindrome(s)\nassert result == expected"
41+
}
42+
],
43+
"playground_imports": "from solution import Solution",
44+
"playground_test_case": "# Example test case\ns = 'abccccdd'\nexpected = 7",
45+
"playground_execution": "result = Solution().longest_palindrome(s)\nresult",
46+
"playground_assertion": "assert result == expected"
47+
}

Makefile

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
PYTHON_VERSION = 3.13
2-
PROBLEM ?= longest_palindromic_substring
2+
PROBLEM ?= longest_palindrome
33
FORCE ?= 0
4+
COMMA := ,
45

56
sync_submodules:
67
git submodule update --init --recursive --remote
@@ -19,23 +20,31 @@ assert_setup_dev:
1920
chmod +x scripts/shared/python/poetry/assert_setup_dev.sh
2021
./scripts/shared/python/poetry/assert_setup_dev.sh
2122

22-
lint:
23-
poetry sort
24-
npx prettier --write "**/*.{ts,tsx,css,json,yaml,yml,md}"
25-
poetry run black .
26-
poetry run isort .
27-
poetry run nbqa ruff . --nbqa-exclude=".templates" --ignore=F401,F821
28-
poetry run ruff check . --exclude="**/*.ipynb"
29-
poetry run mypy \
23+
define lint_target
24+
poetry run black $(1)
25+
poetry run isort $(1)
26+
$(if $(filter .,$(1)), \
27+
poetry run nbqa ruff . --nbqa-exclude=".templates" --ignore=F401$(COMMA)F821, \
28+
poetry run nbqa ruff $(1) --ignore=F401$(COMMA)F821)
29+
poetry run ruff check $(1) --exclude="**/*.ipynb"
30+
poetry run mypy $(1) \
3031
--explicit-package-bases \
3132
--install-types \
3233
--non-interactive \
33-
--check-untyped-defs .
34-
poetry run nbqa isort . --nbqa-exclude=".templates"
35-
poetry run nbqa mypy . \
36-
--nbqa-exclude=".templates" \
37-
--ignore-missing-imports \
38-
--disable-error-code=name-defined
34+
--check-untyped-defs
35+
$(if $(filter .,$(1)), \
36+
poetry run nbqa isort . --nbqa-exclude=".templates", \
37+
poetry run nbqa isort $(1))
38+
$(if $(filter .,$(1)), \
39+
poetry run nbqa mypy . --nbqa-exclude=".templates" \
40+
--ignore-missing-imports --disable-error-code=name-defined, \
41+
poetry run nbqa mypy $(1) --ignore-missing-imports --disable-error-code=name-defined)
42+
endef
43+
44+
lint:
45+
poetry sort
46+
npx prettier --write "**/*.{ts,tsx,css,json,yaml,yml,md}"
47+
$(call lint_target,.)
3948

4049

4150
test:
@@ -46,7 +55,6 @@ test:
4655
--ignore=.templates \
4756
--ignore=leetcode/__pycache__
4857

49-
# Test Problems
5058
p-test:
5159
@echo "Testing problem: $(PROBLEM)"
5260
@if [ ! -d "leetcode/$(PROBLEM)" ]; then \
@@ -55,11 +63,21 @@ p-test:
5563
fi
5664
poetry run pytest leetcode/$(PROBLEM)/tests.py -v -s
5765

58-
# Generate Problem
66+
p-lint:
67+
@echo "Linting problem: $(PROBLEM)"
68+
@if [ ! -d "leetcode/$(PROBLEM)" ]; then \
69+
echo "Error: Problem '$(PROBLEM)' not found in leetcode/ directory"; \
70+
exit 1; \
71+
fi
72+
$(call lint_target,leetcode/$(PROBLEM))
73+
5974
p-gen:
6075
@echo "Generating problem: $(PROBLEM)"
6176
poetry run python .templates/leetcode/gen.py .templates/leetcode/json/$(PROBLEM).json $(if $(filter 1,$(FORCE)),--force)
6277

78+
p-del:
79+
rm -rf leetcode/$(PROBLEM)
80+
6381
# Generate All Problems - useful for people who fork this repo
6482
gen-all-problems:
6583
@echo "This will DELETE all existing problems and regenerate from JSON templates."
@@ -72,13 +90,3 @@ gen-all-problems:
7290
echo "Generating: $$problem"; \
7391
poetry run python .templates/leetcode/gen.py "$$json_file" $(if $(filter 1,$(FORCE)),--force); \
7492
done
75-
76-
# Validate Problem - INTERNAL USE ONLY: For cookiecutter template creation/validation
77-
# Do not use during normal problem solving - only for template development
78-
p-validate:
79-
@echo "Validating problem: $(PROBLEM)"
80-
@if [ ! -d "leetcode/$(PROBLEM)" ]; then \
81-
echo "Error: Generated problem '$(PROBLEM)' not found. Run: make p-gen PROBLEM=$(PROBLEM)"; \
82-
exit 1; \
83-
fi
84-
poetry run python .amazonq/plan/compare_template_files.py generated --problem=$(PROBLEM)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Longest Palindrome
2+
3+
**Difficulty:** Easy
4+
**Topics:** Hash Table, String, Greedy
5+
**Tags:** grind-75
6+
7+
**LeetCode:** [Problem 409](https://leetcode.com/problems/longest-palindrome/description/)
8+
9+
## Problem Description
10+
11+
Given a string `s` which consists of lowercase or uppercase letters, return the length of the longest palindrome that can be built with those letters.
12+
13+
Letters are case sensitive, for example, "Aa" is not considered a palindrome.
14+
15+
## Examples
16+
17+
### Example 1:
18+
19+
```
20+
Input: s = "abccccdd"
21+
Output: 7
22+
```
23+
24+
**Explanation:** One longest palindrome that can be built is "dccaccd", whose length is 7.
25+
26+
### Example 2:
27+
28+
```
29+
Input: s = "a"
30+
Output: 1
31+
```
32+
33+
**Explanation:** The longest palindrome that can be built is "a", whose length is 1.
34+
35+
## Constraints
36+
37+
- `1 <= s.length <= 2000`
38+
- `s` consists of lowercase and/or uppercase English letters only.

leetcode/longest_palindrome/__init__.py

Whitespace-only changes.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"id": "imports",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"from solution import Solution"
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"id": "setup",
17+
"metadata": {},
18+
"outputs": [],
19+
"source": [
20+
"# Example test case\n",
21+
"s = \"abccccdd\"\n",
22+
"expected = 7"
23+
]
24+
},
25+
{
26+
"cell_type": "code",
27+
"execution_count": null,
28+
"id": "execute",
29+
"metadata": {},
30+
"outputs": [],
31+
"source": [
32+
"result = Solution().longest_palindrome(s)\nresult"
33+
]
34+
},
35+
{
36+
"cell_type": "code",
37+
"execution_count": null,
38+
"id": "test",
39+
"metadata": {},
40+
"outputs": [],
41+
"source": [
42+
"assert result == expected"
43+
]
44+
}
45+
],
46+
"metadata": {
47+
"kernelspec": {
48+
"display_name": "leetcode-py-py3.13",
49+
"language": "python",
50+
"name": "python3"
51+
},
52+
"language_info": {
53+
"codemirror_mode": {
54+
"name": "ipython",
55+
"version": 3
56+
},
57+
"file_extension": ".py",
58+
"mimetype": "text/x-python",
59+
"name": "python",
60+
"nbconvert_exporter": "python3",
61+
"pygments_lexer": "ipython3",
62+
"version": "3.13.7"
63+
}
64+
},
65+
"nbformat": 4,
66+
"nbformat_minor": 5
67+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class Solution:
2+
# Time: O(n)
3+
# Space: O(1)
4+
def longest_palindrome(self, s: str) -> int:
5+
char_count: dict[str, int] = {}
6+
for char in s:
7+
char_count[char] = char_count.get(char, 0) + 1
8+
9+
length = 0
10+
has_odd = False
11+
12+
for count in char_count.values():
13+
length += count // 2 * 2
14+
if count % 2 == 1:
15+
has_odd = True
16+
17+
return length + (1 if has_odd else 0)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import pytest
2+
3+
from leetcode_py.test_utils import logged_test
4+
5+
from .solution import Solution
6+
7+
8+
class TestLongestPalindrome:
9+
def setup_method(self):
10+
self.solution = Solution()
11+
12+
@pytest.mark.parametrize(
13+
"s, expected",
14+
[
15+
("abccccdd", 7),
16+
("a", 1),
17+
("Aa", 1),
18+
("aabbcc", 6),
19+
("abc", 1),
20+
("abcdef", 1),
21+
("aab", 3),
22+
("aaaa", 4),
23+
("AaBbCc", 1),
24+
("civilwartestingwhetherthatnaptionoranynartionsoconceivedandsodedicatedcanlongendure", 73),
25+
("bananas", 5),
26+
],
27+
)
28+
@logged_test
29+
def test_longest_palindrome(self, s: str, expected: int):
30+
result = self.solution.longest_palindrome(s)
31+
assert result == expected

scripts/shared

0 commit comments

Comments
 (0)