🧩 Problem: Roman to Integer (LeetCode 13)
Convert a string representing a Roman numeral into its integer value. Roman numerals are typically written from largest to smallest (left to right), except for six well-known subtractive cases:
- I before V or X → 4, 9
- X before L or C → 40, 90
- C before D or M → 400, 900
✅ Clean Idiomatic Solution (One-Pass)
class Solution:
SYMBOLS = {
'I': 1, 'V': 5, 'X': 10,
'L': 50, 'C': 100,
'D': 500, 'M': 1000
}
def romanToInt(self, s: str) -> int:
total = 0
prev_value = 0
for c in s:
value = self.SYMBOLS[c]
total += value - 2 * prev_value if value > prev_value else value
prev_value = value
return total
- Single loop
- Detects subtractive patterns with
value > prev_value - Clean inline logic
✅ Runtime: 3 ms (faster than 79.17%)
✅ Memory: 17.6 MB (less than 90.19%)
🔁 Overengineered (but fun!) Functional Version
def romanToGenerator(s):
from operator import add, sub
SYMBOLS = {
'I': 1, 'V': 5, 'X': 10,
'L': 50, 'C': 100,
'D': 500, 'M': 1000
}
total = 0
prev_val = 0
for c in s:
val = SYMBOLS[c]
op = sub if prev_val < val else add
delta = val if op is add else val - 2 * prev_val
total += delta
yield total
- Uses
operator.add/operator.sub - Yields intermediate results step-by-step
- Great for teaching or tracing, but slower
📊 Benchmarks
| Version | Avg. time (100K runs) | Notes |
|---|---|---|
| Idiomatic version | ≈145 ms | Fastest and cleanest |
| Overengineered (list) | ≈205 ms | Stores operation pairs |
| Overengineered (yield) | ≈195 ms | Generator-based, incremental |
🧮 Complexity Analysis
-
Time Complexity:
- Best / Worst / Average: O(n) — single pass
-
Space Complexity:
- Idiomatic: O(1) (constant variables)
- Overengineered (list): O(n) (stores ops)
- Overengineered (yield): O(1) (no intermediate structure)
⏱️ Time Invested
- Reading and planning: 20 minutes
- Coding and analysis: 50 minutes
- Total: 1h10min
🧠 What I Learned
- Subtractive notation in Roman numerals can be generalized with
value > prev_value. - The difference between clean idiomatic solutions and overengineered abstractions in Python.
- How to benchmark Python code using
timeit, and what tradeoffs come with generators and operator functions. - The value of exploring multiple angles of a problem, even if the first solution works.
🌍 Real-World Applications
- Token-based parsing of custom numeric notations or symbolic expressions.
- Building interpreters or compilers that evaluate streams of symbols (e.g., financial codes, scientific notation).
- Teaching clean code versus functional design tradeoffs.
- Performance benchmarking and complexity analysis in educational content.
💬 Personal Note
What started as a simple Roman numeral conversion turned into an exploration of Python idioms, operator functions, generator design, and performance benchmarking.
Even the simplest problems can reveal new learning paths if you’re curious enough 🧠
📂 Code Repository
You can find my full source code for this solution on GitHub:
- 📦 Repo: github.com/fredericogago/leetcode
- 📄 File:
Roman to Integer.py
👨💻 Like this post? Let’s connect on LinkedIn or check out more on GitHub!