Solution Contract Specification¶
Status: Canonical Reference
Scope: All solution files insolutions/
Last Updated: January 9, 2026 10:49:40
Created: December 12, 2025 14:55:40
This document defines the contract for solution files in this repository. All solution files MUST conform to this specification. The test runner, generators, and tooling depend on this contract.
Table of Contents¶
- File Structure
- SOLUTIONS Metadata
- Validation (JUDGE_FUNC / COMPARE_MODE)
- Test Files
- Metadata Layers
- Quick Reference
File Structure¶
Naming Convention¶
| Component | Format | Example |
|---|---|---|
problem_id | 4-digit zero-padded LeetCode ID | 0001, 0023, 0994 |
slug | snake_case problem name | two_sum, merge_k_sorted_lists |
Examples: - solutions/0001_two_sum.py - solutions/0023_merge_k_sorted_lists.py - solutions/0051_n_queens.py
Required Elements¶
Every solution file MUST contain:
| Element | Required | Description |
|---|---|---|
SOLUTIONS dict | β | Metadata for all solution variants |
| Solution class(es) | β | One or more classes implementing the solution |
solve() function | β | Entry point for stdin/stdout execution |
_runner import | β | For polymorphic dispatch |
Minimal Solution File¶
# solutions/0001_two_sum.py
from typing import List
from _runner import get_solver
SOLUTIONS = {
"default": {
"class": "Solution",
"method": "twoSum",
"complexity": "O(n) time, O(n) space",
"description": "Single pass with hash map",
},
}
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
seen = {}
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [seen[complement], i]
seen[num] = i
return []
def solve():
import sys
import json
lines = sys.stdin.read().strip().split('\n')
nums = json.loads(lines[0])
target = int(lines[1])
solver = get_solver(SOLUTIONS)
result = solver.twoSum(nums, target)
print(json.dumps(result, separators=(',', ':')))
if __name__ == "__main__":
solve()
Multi-Solution File (Polymorphic Pattern)¶
When a problem has multiple solution approaches, use separate classes with the same method name:
from typing import List
from _runner import get_solver
SOLUTIONS = {
"default": {
"class": "SolutionHeap",
"method": "mergeKLists",
"complexity": "O(N log k)",
"description": "Min-heap approach",
},
"divide": {
"class": "SolutionDivideConquer",
"method": "mergeKLists",
"complexity": "O(N log k)",
"description": "Divide and conquer",
},
"greedy": {
"class": "SolutionGreedy",
"method": "mergeKLists",
"complexity": "O(N * k)",
"description": "Sequential merge",
},
}
class SolutionHeap:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
...
class SolutionDivideConquer:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
...
class SolutionGreedy:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
...
def solve():
# ... parse input ...
solver = get_solver(SOLUTIONS)
result = solver.mergeKLists(lists)
print(result)
Key Polymorphism Rule: All solution classes MUST implement the same method name (the LeetCode original method name). The runner selects the class via SOLUTION_METHOD environment variable.
Runner Method Selection¶
The test runner selects solutions via the SOLUTION_METHOD environment variable:
# Run default solution
python runner/test_runner.py 0023
# Run specific solution
python runner/test_runner.py 0023 --method divide
# Run all solutions
python runner/test_runner.py 0023 --all
π See Test Runner Specification for full CLI reference.
The solve() function uses get_solver() to dispatch:
from _runner import get_solver
def solve():
solver = get_solver(SOLUTIONS) # Returns correct class instance
result = solver.methodName(args) # Natural LeetCode-style call
Deprecated Patterns¶
The following patterns are DEPRECATED and should not be used in new code:
| Pattern | Status | Migration |
|---|---|---|
No SOLUTIONS dictionary | β DEPRECATED | Add SOLUTIONS with class + method |
| Wrapper functions | β DEPRECATED | Use polymorphic classes directly |
| Single class with multiple methods | β DEPRECATED | Split into separate classes |
SOLUTIONS without class field | β DEPRECATED | Add class field to each entry |
invoke_solution() helper | β DEPRECATED | Use get_solver() instead |
globals()[method_name] dispatch | β DEPRECATED | Use get_solver() instead |
Deprecated wrapper pattern (DO NOT USE):
# β DEPRECATED
def solve_a(nums, val):
return SolutionA().removeElement(nums, val)
SOLUTIONS = {
"default": {"method": "solve_a", ...}, # Missing 'class' field
}
Solution Comment Format¶
Solutions SHOULD include structured comments to explain the algorithm, approach, and key insights.
File-Level Docstring¶
Every solution file SHOULD start with a docstring describing the problem:
"""
Problem: Two Sum
Link: https://leetcode.com/problems/two-sum/
Given an array of integers nums and an integer target, return indices
of the two numbers such that they add up to target.
Constraints:
- 2 <= nums.length <= 10^4
- -10^9 <= nums[i] <= 10^9
- -10^9 <= target <= 10^9
- Only one valid answer exists.
"""
Note: The Link field should NOT include /description/ suffix. Use the format: https://leetcode.com/problems/{slug}/
| Field | Required | Description |
|---|---|---|
Problem | β | Problem title |
Link | β | LeetCode URL (format: https://leetcode.com/problems/{slug}/, without /description/ suffix) |
| Description | Recommended | Brief problem statement |
Constraints | Recommended | Key constraints affecting algorithm choice |
Solution Block Comments¶
Each solution class SHOULD be preceded by a block comment explaining the approach.
Keep it concise: Block comments should have 3-4 bullet points maximum. Focus on key algorithmic insights, not exhaustive explanations.
No blank line between the comment block and the class definition:
# ============================================================================
# Solution 1: Sliding Window (Optimized with Jump)
# Time: O(n), Space: O(min(n, Ο))
# - Each character visited at most twice
# - Uses last-seen-index array for O(1) duplicate detection
# - Direct position jumping instead of incremental contraction
# ============================================================================
class SolutionSlidingWindow: # β No blank line
...
Format:
# ============================================
# Solution {N}: {Approach Name}
# Time: O(?), Space: O(?)
# - {Key insight 1}
# - {Key insight 2}
# - {Key insight 3 (optional)}
# ============================================
class ClassName: # β No blank line before class/function
| Component | Required | Description |
|---|---|---|
| Solution number & name | β | e.g., Solution 1: Sliding Window |
| Time/Space complexity | β | e.g., Time: O(n), Space: O(n) |
| Bullet points (3-4 max) | β | Key insights only, avoid verbose explanations |
| No blank line | β | Comment block directly followed by class/function |
What NOT to include in block comments:
- β Lengthy "Algorithm Insight" or "State Definition" sections
- β Detailed step-by-step pseudocode
- β Redundant restating of code logic
- β More than 4 bullet points
Dynamic Programming Solutions¶
For DP solutions, place the State/Base case/Transition definitions as class-level comments inside the class body, not in the block comment header.
Format:
# ============================================================================
# Solution 1: Interval DP (Character Printing)
# Time: O(nΒ³), Space: O(nΒ²)
# - Key insight: when s[k] == s[i], extend first print to cover s[k]
# - Preprocess: remove consecutive duplicates
# ============================================================================
class Solution:
# State: dp[i][j] = minimum turns to print s[i:j+1]
# Base case: dp[i][i] = 1 (single char needs 1 turn)
# Transition: dp[i][j] = min(dp[i+1][j] + 1,
# dp[i+1][k-1] + dp[k][j]) for s[k]==s[i]
def strangePrinter(self, s: str) -> int:
...
| Location | Content |
|---|---|
| Block comment | Algorithm name, Time/Space complexity, key insights (2-3 bullets) |
| Class-level comment | # State:, # Base case:, # Transition: definitions |
| Inline comments | Brief markers like # Base case, # Transition near code |
Rationale: DP definitions are implementation details that belong close to the code. Block comments should focus on high-level algorithmic insights.
Internal Function Comments¶
Internal comments within methods are acceptable and encouraged for documenting:
- Key state transitions
- Non-obvious logic or edge case handling
- Invariant maintenance
class Solution:
def maxPathSum(self, root: Optional[TreeNode]) -> int:
self.global_max = float('-inf')
def max_contribution(node: Optional[TreeNode]) -> int:
if not node:
return 0
# Prune negative branches - they can only decrease path sum
left_gain = max(0, max_contribution(node.left))
right_gain = max(0, max_contribution(node.right))
# Path through this node as apex: left β node β right
path_through_here = node.val + left_gain + right_gain
self.global_max = max(self.global_max, path_through_here)
# Return single-branch max to parent (can't fork upward)
return node.val + max(left_gain, right_gain)
max_contribution(root)
return self.global_max
Internal comment guidelines:
| β Good | β Bad |
|---|---|
| Explains WHY (non-obvious reasoning) | Restates WHAT the code does |
| Documents invariant or constraint | Comments obvious operations |
| Marks key algorithmic transitions | Excessive line-by-line comments |
SOLUTIONS Metadata¶
Required Schema¶
SOLUTIONS = {
"key": {
"class": str, # REQUIRED: Class name
"method": str, # REQUIRED: Method name (LeetCode original)
"complexity": str, # RECOMMENDED: Time/space complexity
"description": str, # RECOMMENDED: Brief description
},
}
Field Definitions¶
| Field | Type | Required | Description |
|---|---|---|---|
class | str | β | Python class name that implements the solution |
method | str | β | Method name to invoke (must match LeetCode signature) |
complexity | str | Recommended | Time and/or space complexity (e.g., "O(n) time, O(1) space") |
description | str | Recommended | Brief description of the approach |
Required Keys¶
"default"key is REQUIRED. This is used when no--methodflag is specified.- Additional keys are optional and represent alternative solution approaches.
Solution Keys (Shorthand Names)¶
The keys in SOLUTIONS are shorthand identifiers used to select solution variants via the --method flag:
| Key | Description | Example |
|---|---|---|
"default" | Default solution approach (required) | "default" |
"divide" | Divide and conquer approach | "divide" |
"greedy" | Greedy algorithm approach | "greedy" |
"heap" | Heap/priority queue approach | "heap" |
"sets" | Hash set-based approach | "sets" |
"bitmask" | Bitmask-based approach | "bitmask" |
"dp" | Dynamic programming approach | "dp" |
"backtrack" | Backtracking approach | "backtrack" |
Naming Guidelines: - Use lowercase, descriptive names - Keep names short (typically 1-2 words) - Use snake_case for multi-word names (e.g., "sliding_window") - Names should reflect the algorithm or data structure used
Usage:
# Run default solution
python runner/test_runner.py 0023
# Run specific solution by shorthand name
python runner/test_runner.py 0023 --method divide
python runner/test_runner.py 0023 --method greedy
Validation Rules¶
The runner validates SOLUTIONS at load time:
SOLUTIONSdictionary MUST exist"default"key MUST be present- Each entry MUST have
"class"field - Each entry MUST have
"method"field - The class specified in
"class"MUST exist in the module - The method specified in
"method"MUST exist on the class
Validation error example:
β [0023] Invalid SOLUTIONS format:
- SOLUTIONS['heap'] missing 'class' field
Expected format:
SOLUTIONS = {
"default": {"class": "Solution", "method": "twoSum", ...},
}
Complete Example¶
SOLUTIONS = {
"default": {
"class": "SolutionBacktrackSets",
"method": "solveNQueens",
"complexity": "O(N!) time, O(N) space",
"description": "Backtracking with hash sets for O(1) conflict detection",
},
"sets": {
"class": "SolutionBacktrackSets",
"method": "solveNQueens",
"complexity": "O(N!) time, O(N) space",
"description": "Backtracking with hash sets for O(1) conflict detection",
},
"bitmask": {
"class": "SolutionBacktrackBitmask",
"method": "solveNQueens",
"complexity": "O(N!) time, O(N) space",
"description": "Backtracking with bitmask for ultra-fast conflict detection",
},
}
Optional Extended Fields¶
For integration with the ontology system, these optional fields may be included:
| Field | Type | Description |
|---|---|---|
api_kernels | list[str] | API Kernels used (e.g., ["SubstringSlidingWindow"]) |
patterns | list[str] | Patterns applied (e.g., ["sliding_window_unique"]) |
families | list[str] | Problem families (e.g., ["substring_search"]) |
data_structures | list[str] | Data structures used (e.g., ["hash_map", "deque"]) |
algorithms | list[str] | Algorithms/techniques (e.g., ["two_pointers"]) |
tags | list[str] | Custom tags for categorization |
Validation (JUDGE_FUNC / COMPARE_MODE)¶
Validation Priority Order¶
The runner validates solution output in this priority order:
| Priority | Mode | Trigger | Description |
|---|---|---|---|
| 1 | JUDGE_FUNC | JUDGE_FUNC defined in module | Custom validation function |
| 2 | COMPARE_MODE | COMPARE_MODE defined in module | Framework-provided comparators |
| 3 | Exact | Default | String equality comparison |
JUDGE_FUNC Specification¶
Definition¶
Signature¶
def judge(actual, expected, input_data: str) -> bool:
"""
Custom validation function.
Args:
actual: Program output (parsed if possible, else raw string)
expected: Expected output (parsed if possible, else raw string, or None)
input_data: Raw input string
Returns:
bool: True if answer is correct, False otherwise
"""
Argument Parsing Rules¶
| Argument | Parsing Behavior |
|---|---|
actual | Parsed via ast.literal_eval() if valid Python literal; otherwise raw string |
expected | Parsed via ast.literal_eval() if valid Python literal; otherwise raw string; None if .out file missing |
input_data | Always raw string (no parsing) |
Return Types¶
| Return Value | Meaning |
|---|---|
True | Test passed |
False | Test failed |
Judge-Only Mode¶
When .out file is missing: - expected is None - JUDGE_FUNC MUST validate using only actual and input_data - This enables validation without precomputed expected outputs
π This mode is required for generated tests. See Generator Contract.
Example (N-Queens with judge-only support):
def judge(actual: list, expected, input_data: str) -> bool:
"""Validate N-Queens solution."""
n = int(input_data.strip())
# Known solution counts for N-Queens
KNOWN_COUNTS = {1: 1, 2: 0, 3: 0, 4: 2, 5: 10, 6: 4, 7: 40, 8: 92, 9: 352}
# 1. Verify each board is valid
for board in actual:
if not _is_valid_board(board, n):
return False
# 2. Check no duplicates
unique = set(tuple(row for row in board) for board in actual)
if len(unique) != len(actual):
return False
# 3. Check count
if expected is not None:
return len(actual) == len(expected)
else:
# Judge-only: use known counts
expected_count = KNOWN_COUNTS.get(n)
if expected_count is not None:
return len(actual) == expected_count
return True # Accept if count unknown
JUDGE_FUNC = judge
JUDGE_FUNC Comments (Optional)¶
When defining a JUDGE_FUNC, you MAY include a block comment explaining its purpose:
# ============================================
# JUDGE_FUNC - Required for generator support
# ============================================
# Uses brute force O(m+n) merge to compute the correct answer,
# then compares with the solution output.
# ============================================
def judge(actual, expected, input_data: str) -> bool: # β No blank line
...
JUDGE_FUNC = judge
COMPARE_MODE Specification¶
Definition¶
Supported Modes¶
| Mode | Behavior | Use Case |
|---|---|---|
"exact" | String equality after normalization | Default; exact answer required |
"sorted" | Sort lists before comparison | "Return in any order" problems |
"set" | Set comparison (ignores order and duplicates) | Unique elements, order doesn't matter |
Normalization¶
Before comparison, outputs are normalized: 1. Strip leading/trailing whitespace 2. Remove trailing whitespace from each line 3. Normalize line endings
Sorted Mode Details¶
For nested lists (e.g., List[List[str]]): - Convert inner lists to tuples - Sort outer list - Compare sorted results
# Example: N-Queens output
actual = [[".Q..", "...Q"], ["..Q.", "Q..."]]
expected = [["..Q.", "Q..."], [".Q..", "...Q"]]
# After sorting: both become the same β PASS
Validation Mode Reporting¶
The runner reports validation mode for each test case:
0051_n_queens_1: β
PASS [judge]
0051_n_queens_2: β
PASS [judge-only]
0001_two_sum_1: β
PASS [exact]
0015_3sum_1: β
PASS [sorted]
| Label | Meaning |
|---|---|
[judge] | JUDGE_FUNC used with .out file present |
[judge-only] | JUDGE_FUNC used without .out file |
[exact] | Exact string comparison |
[sorted] | Sorted comparison |
[set] | Set comparison |
[skip] | Skipped (no .out and no JUDGE_FUNC) |
Test Files¶
Directory Structure¶
neetcode/
βββ tests/ # Static test cases
β βββ {problem_id}_{slug}_{n}.in # Input file
β βββ {problem_id}_{slug}_{n}.out # Expected output file (optional if JUDGE_FUNC)
β
βββ generators/ # Dynamic test generators
β βββ {problem_id}_{slug}.py # Generator module
π For generator files, see Generator Contract.
Static Test Files¶
Naming Convention¶
Examples: - tests/0001_two_sum_1.in - tests/0001_two_sum_1.out - tests/0051_n_queens_2.in - tests/0051_n_queens_2.out
Input File Format (.in)¶
- Plain text, parsed by
solve()function - Line endings: LF or CRLF (both accepted)
- Recommended: Use canonical JSON literal format (one parameter per line)
Canonical Format (Recommended):
| Type | Format | Example |
|---|---|---|
| Integer | Plain number | 42 |
| Float | Plain number | 3.14 |
| Boolean | Lowercase JSON | true, false |
| String | JSON quoted | "hello" |
| Array | JSON literal | [1,2,3] |
| 2D Array | JSON literal | [[1,2],[3,4]] |
Example (0001_two_sum_1.in - Canonical Format):
π‘ Migration: Use
python -m codegen migrateto convert existing tests to canonical format.
Output File Format (.out)¶
- Plain text matching
print()output fromsolve() - MUST match exactly (after normalization) unless
COMPARE_MODEorJUDGE_FUNCspecified - Recommended: Use canonical JSON literal format
Output Categories:
| Category | Description | Lines | Example Problem |
|---|---|---|---|
| A | Simple return value | 1 | Two Sum |
| B | Return + modified state | 2+ | Remove Element |
| C | Custom judge required | 1+ | 3Sum |
Category A Example (0001_two_sum_1.out):
Category B Example (0027_remove_element_1.out):
k, Line 2: nums[:k] for verification) β οΈ Boolean Output: Use lowercase
true/false(JSON style), notTrue/False(Python style).π See Test File Format for complete output format specification.
Auto-Generating Test Files¶
Test files can be automatically generated from LeetCode examples:
# Generate solution skeleton with test files
python -m codegen new 1 --with-tests
# Generate tests for existing solution
python -m codegen.core.test_generator # See module for API
Optional .out Files¶
.out files are optional when: - JUDGE_FUNC is defined (judge-only mode)
Running Tests¶
Static Tests¶
With Generated Tests¶
# Static + N generated tests
python runner/test_runner.py 0001_two_sum --generate 10
# Reproducible with seed
python runner/test_runner.py 0001_two_sum --generate 10 --seed 12345
π See Generator Contract Β§ Running Generated Tests for all options.
Metadata Layers¶
Metadata Hierarchy¶
| Layer | Location | Scope | Change Frequency |
|---|---|---|---|
| Solution-level | solutions/{problem}.py | Per-solution variant | Often |
| Problem-level | meta/problems/{problem}.toml | Per-problem | Moderate |
| Ontology-level | ontology/*.toml | Global definitions | Rarely |
Solution-Level Metadata (Required)¶
Located in the solution file itself:
SOLUTIONS = {
"default": {
"class": "Solution", # REQUIRED
"method": "twoSum", # REQUIRED
"complexity": "O(n)", # RECOMMENDED
"description": "Hash map", # RECOMMENDED
},
}
# Optional validation
JUDGE_FUNC = judge # Custom validation
COMPARE_MODE = "sorted" # Comparison mode
Problem-Level Metadata (Optional)¶
Located in meta/problems/{problem_id}_{slug}.toml:
[problem]
id = "0001"
title = "Two Sum"
difficulty = "easy"
url = "https://leetcode.com/problems/two-sum/"
topics = ["array", "hash_table"]
companies = ["google", "amazon", "facebook"]
[[solutions]]
key = "default"
class = "Solution"
method = "twoSum"
complexity = "O(n) time"
api_kernels = []
patterns = ["hash_lookup"]
Ontology-Level Metadata¶
Located in ontology/ directory:
| File | Content |
|---|---|
api_kernels.toml | Reusable algorithmic cores |
patterns.toml | Problem-solving patterns |
families.toml | Problem family definitions |
algorithms.toml | Algorithm taxonomy |
data_structures.toml | Data structure taxonomy |
topics.toml | LeetCode topics |
difficulties.toml | Difficulty levels |
companies.toml | Company tags |
roadmaps.toml | Learning path metadata |
Consistency Checklist¶
When adding or modifying a solution, verify:
Solution File Consistency¶
-
SOLUTIONSdictionary exists with"default"key - Each entry has
"class"and"method"fields - Class names match actual classes in file
- Method names match actual methods on classes
- All solution classes implement the same method name
Test Consistency¶
- At least one
.infile exists intests/ -
.outfiles exist ORJUDGE_FUNCis defined - Input format in
.inmatchessolve()parsing logic - Output format in
.outmatchesprint()output
Generator Consistency (if applicable)¶
- Generator file exists in
generators/ -
generate()function yields correct format -
JUDGE_FUNCis defined in solution (required for generators) - Edge cases are included in generator
π See Generator Contract for generator requirements.
Quick Reference¶
File Structure Template¶
# solutions/{problem_id}_{slug}.py
"""
Problem: {Problem Title}
Link: https://leetcode.com/problems/{slug}/
{Brief problem description}
Constraints:
- {constraint 1}
- {constraint 2}
"""
**Note:** Link format must be `https://leetcode.com/problems/{slug}/` (without `/description/` suffix).
```python
from typing import List
from _runner import get_solver
# ============================================
# Optional: Custom validation
# ============================================
# JUDGE_FUNC = judge # For "any order" or complex validation
# COMPARE_MODE = "sorted" # For simple "any order" problems
# ============================================
# SOLUTIONS metadata (REQUIRED)
# ============================================
SOLUTIONS = {
"default": {
"class": "Solution",
"method": "methodName",
"complexity": "O(?)",
"description": "Brief description",
},
}
# ============================================
# Solution 1: {Approach Name}
# Time: O(?), Space: O(?)
# - {Key insight or implementation detail}
# - {Additional notes}
# ============================================
class Solution: # β No blank line after comment block
def methodName(self, ...):
...
# ============================================
# Entry point
# ============================================
def solve():
import sys
lines = sys.stdin.read().strip().split('\n')
# Parse input...
solver = get_solver(SOLUTIONS)
result = solver.methodName(...)
print(result)
if __name__ == "__main__":
solve()
CLI Reference¶
# Run tests
python runner/test_runner.py {problem} # Default solution
python runner/test_runner.py {problem} --method X # Specific solution
python runner/test_runner.py {problem} --all # All solutions
python runner/test_runner.py {problem} --benchmark # With timing
π See Test Runner Specification for full CLI reference.
Related Documentation¶
| Document | Content |
|---|---|
| Test File Format | Canonical .in/.out format specification |
| Generator Contract | generate(), generate_for_complexity(), edge cases |
| Test Runner Specification | CLI options, output format, troubleshooting |
| Architecture Migration | Polymorphic pattern migration guide |