A detailed explanation of converting a Roman numeral string into an integer using symbol values and the subtraction rule.
Problem Restatement
We are given a Roman numeral string s.
We need to convert it into an integer.
Roman numerals use seven symbols:
| Symbol | Value |
|---|---|
I | 1 |
V | 5 |
X | 10 |
L | 50 |
C | 100 |
D | 500 |
M | 1000 |
Usually, Roman numerals are written from largest to smallest.
For example:
VIII = 5 + 1 + 1 + 1 = 8But there are six subtractive cases:
| Roman | Value |
|---|---|
IV | 4 |
IX | 9 |
XL | 40 |
XC | 90 |
CD | 400 |
CM | 900 |
The input is guaranteed to be a valid Roman numeral in the range [1, 3999]. The string length is between 1 and 15, and it contains only I, V, X, L, C, D, and M.
Input and Output
| Item | Meaning |
|---|---|
| Input | A valid Roman numeral string s |
| Output | The integer value of s |
| Constraint | 1 <= s.length <= 15 |
| Range | Roman numeral represents a value in [1, 3999] |
Example function shape:
def romanToInt(s: str) -> int:
...Examples
Example 1:
s = "III"Value:
1 + 1 + 1 = 3Output:
3Example 2:
s = "LVIII"Break it down:
L = 50
V = 5
III = 3Total:
50 + 5 + 3 = 58Output:
58Example 3:
s = "MCMXCIV"Break it down:
M = 1000
CM = 900
XC = 90
IV = 4Total:
1000 + 900 + 90 + 4 = 1994Output:
1994First Thought: Check Special Pairs Manually
One direct approach is to scan the string and check whether the current character and the next character form one of the six subtractive pairs:
IV, IX, XL, XC, CD, CMIf we see one of these pairs, add its special value and skip two characters.
Otherwise, add the value of the current symbol and move one character.
This works.
But there is an even simpler rule.
Key Insight
When a smaller Roman value appears before a larger Roman value, we subtract it.
Otherwise, we add it.
For example:
IVI is smaller than V, so:
-1 + 5 = 4For:
VIV is larger than I, so:
5 + 1 = 6So for each symbol:
- if its value is less than the next symbol’s value, subtract it
- otherwise, add it
This rule correctly handles all six subtractive cases.
Visual Walkthrough
Use:
s = "MCMXCIV"Map each symbol:
M = 1000
C = 100
M = 1000
X = 10
C = 100
I = 1
V = 5Now scan left to right.
M is followed by C.
1000 >= 100
add 1000C is followed by M.
100 < 1000
subtract 100M is followed by X.
1000 >= 10
add 1000X is followed by C.
10 < 100
subtract 10C is followed by I.
100 >= 1
add 100I is followed by V.
1 < 5
subtract 1V is the last symbol, so add it.
Total:
1000 - 100 + 1000 - 10 + 100 - 1 + 5 = 1994Algorithm
- Store Roman symbol values in a dictionary.
- Initialize
total = 0. - Scan every character by index.
- For each character:
- get its value
- if the next character exists and has a larger value, subtract current value
- otherwise, add current value
- Return
total.
Correctness
The Roman numeral is valid, so every subtractive case appears as a smaller symbol before a larger symbol.
The algorithm detects exactly that situation by comparing the current value with the next value.
If the current value is smaller than the next value, the current symbol contributes negatively as part of a subtractive pair.
Otherwise, the current symbol contributes normally and is added.
Every symbol is processed once.
For additive groups such as VIII, all symbols are added.
For subtractive groups such as IV, the first symbol is subtracted and the second symbol is added, producing the correct value.
Therefore the final total equals the integer represented by the Roman numeral.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We scan the string once |
| Space | O(1) | The value map has fixed size |
Since s.length <= 15, this is constant-bounded for the original constraints.
Implementation
class Solution:
def romanToInt(self, s: str) -> int:
value = {
"I": 1,
"V": 5,
"X": 10,
"L": 50,
"C": 100,
"D": 500,
"M": 1000,
}
total = 0
for i in range(len(s)):
current = value[s[i]]
if i + 1 < len(s) and current < value[s[i + 1]]:
total -= current
else:
total += current
return totalCode Explanation
The dictionary stores the value of each Roman symbol:
value = {
"I": 1,
"V": 5,
"X": 10,
"L": 50,
"C": 100,
"D": 500,
"M": 1000,
}We scan the string by index:
for i in range(len(s)):For each symbol, read its value:
current = value[s[i]]If the next symbol exists and is larger:
if i + 1 < len(s) and current < value[s[i + 1]]:then this symbol is part of a subtractive pair, so subtract it:
total -= currentOtherwise, add it:
total += currentFinally:
return totalTesting
def run_tests():
s = Solution()
assert s.romanToInt("III") == 3
assert s.romanToInt("LVIII") == 58
assert s.romanToInt("MCMXCIV") == 1994
assert s.romanToInt("IV") == 4
assert s.romanToInt("IX") == 9
assert s.romanToInt("XL") == 40
assert s.romanToInt("XC") == 90
assert s.romanToInt("CD") == 400
assert s.romanToInt("CM") == 900
assert s.romanToInt("MMMCMXCIX") == 3999
print("all tests passed")
run_tests()| Test | Why |
|---|---|
"III" | Repeated additive symbols |
"LVIII" | Mixed additive symbols |
"MCMXCIV" | Multiple subtractive cases |
"IV" | Ones-place subtractive case |
"IX" | Ones-place subtractive case |
"XL" | Tens-place subtractive case |
"XC" | Tens-place subtractive case |
"CD" | Hundreds-place subtractive case |
"CM" | Hundreds-place subtractive case |
"MMMCMXCIX" | Maximum value, 3999 |