LeetCode Solutions
#13 — Roman to Integer
Look ahead, subtract, and conquer — the Roman numeral decoder
Problem statement
Roman numerals are represented by seven different symbols:
I,V,X,L,C,DandM.
Symbol Value I 1 V 5 X 10 L 50 C 100 D 500 M 1000 For example,
2is written asIIin Roman numeral, just two ones added together.12is written asXII, which is simplyX + II. The number27is written asXXVII, which isXX + V + II.Roman numerals are usually written largest to smallest from left to right. However, the numeral for four is not
IIII. Instead, the number four is written asIV. Because the one is before the five we subtract it making four. The same principle applies to the number nine, which is written asIX. There are six instances where subtraction is used:
Ican be placed beforeV(5) andX(10) to make 4 and 9.Xcan be placed beforeL(50) andC(100) to make 40 and 90.Ccan be placed beforeD(500) andM(1000) to make 400 and 900.Given a roman numeral, convert it to an integer.
Example 1:
Example 2:
Example 3:
Constraints:
1 <= s.length <= 15
scontains only the characters('I', 'V', 'X', 'L', 'C', 'D', 'M').- It is guaranteed that
sis a valid roman numeral in the range[1, 3999].
The naive way
The most direct translation of the rules: walk left to right, map each symbol to its value, and if you see a smaller value before a larger one (like I before V), subtract it instead of adding. That’s a perfectly valid approach, and it works right away.
def romanToInt_naive(s: str) -> int:
values = {'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000}
total = 0
i = 0
while i < len(s):
# if current < next, it's a subtractive pair
if i + 1 < len(s) and values[s[i]] < values[s[i+1]]:
total += values[s[i+1]] - values[s[i]]
i += 2
else:
total += values[s[i]]
i += 1
return total
Initial complexity
This is already O(n) time and O(1) space — we only touch each character a couple of times. The code is clear and easy to follow. But the problem becomes even cleaner when we invert the perspective: instead of looking for pairs, we simply look ahead and decide whether the current symbol should be added or subtracted. That small shift removes the need for a jumpy index.
A smoother single pass
The key observation: in Roman numerals, if a symbol is followed by one of greater value, it acts as a subtractor. Otherwise, it adds. So we can walk the string once, compare the current value with the next one, and add or subtract accordingly. The last symbol is always added because there’s nothing after it.
This version reads almost like the mental process: “M is 1000, next is C (100) which is smaller, so add 1000. C is 100, next is M (1000) — larger, so subtract 100…” It’s elegant and uses only one pass with no special-case jumps.
def romanToInt(s: str) -> int:
values = {
'I': 1,
'V': 5,
'X': 10,
'L': 50,
'C': 100,
'D': 500,
'M': 1000
}
total = 0
n = len(s)
for i in range(n):
# if current value is less than next value, subtract it
if i < n - 1 and values[s[i]] < values[s[i+1]]:
total -= values[s[i]]
else:
total += values[s[i]]
return total
Let’s trace the trickiest example: s = "MCMXCIV" (1994).
| i | symbol | value | next symbol | next value | action | total after step |
|---|---|---|---|---|---|---|
| 0 | M | 1000 | C | 100 | 1000 ≥ 100 → add | 1000 |
| 1 | C | 100 | M | 1000 | 100 < 1000 → subtract | 900 |
| 2 | M | 1000 | X | 10 | 1000 ≥ 10 → add | 1900 |
| 3 | X | 10 | C | 100 | 10 < 100 → subtract | 1890 |
| 4 | C | 100 | I | 1 | 100 ≥ 1 → add | 1990 |
| 5 | I | 1 | V | 5 | 1 < 5 → subtract | 1989 |
| 6 | V | 5 | — | — | last symbol → add | 1994 |
Notice how the subtraction for C (100) and the later addition of M (1000) combine to give the net +900 of CM. The same happens for XC (90) and IV (4).
Final complexity
Time complexity: O(n) — we traverse the string exactly once, performing a constant‑time dictionary lookup and comparison per character.
Space complexity: O(1) — the dictionary size is fixed at 7 entries, and we use only a few integer variables. No extra structures scale with the input.
This solution is fast, memory‑efficient, and mirrors exactly how an experienced human would parse a Roman numeral: glance ahead and decide.
Happy coding, and may your complexity always be low.
References & further reading
- LeetCode Roman to Integer Problem Page — official problem and discussion forums.
- Wikipedia: Roman numerals — history, rules, and more complex examples.
- Real Python: Convert Roman Numerals With Python — a deeper dive with both directions (to and from Roman).
- GeeksforGeeks: Converting Roman Numerals to Decimal — alternative implementations in multiple languages.
Can't get enough from lumænaut? Read this...
- Chapter 2 | Everything I Don't Know About Blogging — the logbook behind the site: domains, SEO, plugins, and learning in public.
- The 8 Algorithm Paradigms — eight mental models you’ll keep reusing across problems, interviews, and production code.
- How the Game Loop Works — the heartbeat of a game frame: update, render, and steady timing in plain language.