LeetCode Solutions
#13 — Roman to Integer

Look ahead, subtract, and conquer — the Roman numeral decoder

Color ASCII art: an outdated computer on a desk with a bookshelf in the background, suggesting a classic coding workspace.
Illustration in ASCII art style: an outdated computer on a desk with a bookshelf behind it.

Problem statement

Roman numerals are represented by seven different symbols: I, V, X, L, C, D and M.

SymbolValue
I1
V5
X10
L50
C100
D500
M1000

For example, 2 is written as II in Roman numeral, just two ones added together. 12 is written as XII, which is simply X + II. The number 27 is written as XXVII, which is XX + 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 as IV. Because the one is before the five we subtract it making four. The same principle applies to the number nine, which is written as IX. There are six instances where subtraction is used:

  • I can be placed before V (5) and X (10) to make 4 and 9.
  • X can be placed before L (50) and C (100) to make 40 and 90.
  • C can be placed before D (500) and M (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

  • s contains only the characters ('I', 'V', 'X', 'L', 'C', 'D', 'M').

  • It is guaranteed that s is 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.

Python

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.

Python

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).

Step‑by‑Step Simulation: s = "MCMXCIV"
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
The decision is made by peeking at the next character. Subtractive pairs (CM, XC, IV) are handled naturally by subtracting the smaller value before adding the larger one in the next iteration.

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

Can't get enough from lumænaut? Read this...