DSAPrep
EasyStringsMath & Geometry

Roman to Integer

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

I1
V5
X10
L50
C100
D500
M1000

For example, 2 is written as II just as 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, four is written as IV: because the one comes before the five we subtract it, making four. The same principle applies to nine, 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; and 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

            Input: s = "III"
            Output: 3
            
            
          

Example 2

            Input: s = "IV"
            Output: 4
            
            
          

Example 3

            Input: s = "IX"
            Output: 9
            
            
          

Example 4

            Input: s = "LVIII"
            Output: 58
            

            
                Explanation: L = 50, V = 5, III = 3.
              
          

Example 5

            Input: s = "MCMXCIV"
            Output: 1994
            

            
                Explanation: M = 1000, CM = 900, XC = 90, IV = 4.
              
          

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].
View original on LeetCode โ†—

A roman numeral is just a sum of symbol values โ€” except for six special pairs. The pattern insight is the subtractive-pair rule: a symbol whose value is strictly smaller than the symbol to its right does not add itself; it subtracts from the running total (it discounts the bigger neighbor that follows it). Everything else just adds its face value. Once you see that, the whole problem collapses into a single left-to-right scan where each symbol only needs to compare itself against the next one โ€” never the rest of the string.

Brute Force

Time O(nยฒ)Space O(1)

Take the rule literally: a symbol is subtracted if and only if some larger-valued symbol occurs anywhere to its right. For each symbol (at index i) that means scanning the whole suffix from i + 1 to the end to look for any strictly larger value โ€” a nested loop. It is correct because subtractive pairs always sit so that the small symbol has a bigger value ahead, but it is needlessly expensive: every early symbol re-examines almost the entire remaining string.

class Solution:
def romanToInt(self, 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):
v = values[s[i]]
# Subtractive iff a larger value appears to the right.
if any(values[s[j]] > v for j in range(i + 1, n)):
total -= v
else:
total += v
return total

Why it is quadratic: the any(...) check walks the entire suffix for each of the n positions, so this is n + (n-1) + ... + 1 โ‰ˆ nยฒ/2 comparisons โ€” O(nยฒ) time. It gets every answer right (e.g. for IV, the I finds a 5 ahead and subtracts), but it re-reads the tail of the string for every symbol even though only the immediate neighbor can ever matter. Space is O(1) aside from the fixed lookup table.

Single Pass Comparing Next

OptimalTime O(n)Space O(1)

A valid roman numeral only ever subtracts when the symbol immediately to its left is smaller than the one immediately after โ€” a contiguous (small, big) pair. So you never need to look further than one symbol ahead. Scan left to right; at each symbol, compare its value with the next symbolโ€™s value. If current is strictly smaller, subtract it (it is the small half of a subtractive pair); otherwise add it. The last symbol has no next, so it always adds.

class Solution:
def romanToInt(self, 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):
v = values[s[i]]
if i + 1 < n and v < values[s[i + 1]]:
total -= v # small half of a subtractive pair
else:
total += v
return total

Watch the one-glance-past pointer + per-symbol decision on the file example, s = "MCMXCIV" โ€” every one of its three subtractive pairs (CM, XC, IV) is resolved as (current < next) โ†’ subtract:

starttotal = 0
M1000
C100
M1000
X10
C100
I1
V5

scan pointer reads the current symbol; the one to its right is compared โ€” subtrahends discount their bigger neighbor, adds carry it

1 / 9
currentcomparingresultdiscarded

The whole trick lives in the SUBTRACTIVE PAIR rule: scan left to right, and a symbol whose value is strictly smaller than the NEXT symbol is subtracted, otherwise it is added. Only I, X and C ever play the small subtractive role, and each only sits before a specific bigger neighbor (I before V or X, X before L or C, C before D or M). MCMXCIV exercises three of those six pairings, so it is the perfect test drive: read it as M + CM + XC + IV.

Then the add-only side, s = "LVIII" โ€” note how the run of equal Is still adds, because the rule compares with a strict less-than:

starttotal = 0
L50
V5
I1
I1
I1

scan pointer reads the current symbol; the one to its right is compared โ€” subtrahends discount their bigger neighbor, adds carry it

1 / 7
currentcomparingresultdiscarded

LVIII shows the ADD-only side of the rule. L is 50, V is 5, and the three I are each 1. No symbol here is followed by a strictly bigger one, so nothing ever subtracts and the total is a plain running sum. The subtle case to watch is the run of equal I: equal neighbors STILL add.

Why it is linear: each of the n symbols is read exactly once and does a single O(1) comparison against one neighbor, giving O(n) time โ€” the same asymptotic class as the brute force but with a suffix-scan per position replaced by one neighbor check. This is optimal: every symbol must be read at least once to know its value, so no algorithm can beat O(n). Space is O(1) beyond the fixed seven-entry lookup table.