Python · 3 分钟阅读
13:罗马数字转整数
目录
题目
罗马数字包含以下七种字符:I、V、X、L、C、D 和 M。给定一个罗马数字,
将其转换成整数。
字符值
| 字符 | 数值 |
|---|---|
| I | 1 |
| V | 5 |
| X | 10 |
| L | 50 |
| C | 100 |
| D | 500 |
| M | 1000 |
特殊规则(减法)
通常罗马数字中 小的数字在大的数字右边表示相加。但也有 6 种特例(小的数字在
大的数字左边表示相减):
| 组合 | 数值 | 含义 |
|---|---|---|
| IV | 4 | 5 - 1 |
| IX | 9 | 10 - 1 |
| XL | 40 | 50 - 10 |
| XC | 90 | 100 - 10 |
| CD | 400 | 500 - 100 |
| CM | 900 | 1000 - 100 |
示例
输入: s = "III"
输出: 3
输入: s = "LVIII"
输出: 58
解释: L(50) + V(5) + III(3) = 58
输入: s = "MCMXCIV"
输出: 1994
约束
1 <= s.length <= 15s仅含字符I, V, X, L, C, D, M- 题目数据保证
s是 有效的罗马数字,且表示范围[1, 3999]
思路
把规则统一为一句话:从左到右遍历,若当前字符代表的值 < 下一个字符,则减去当前
值;否则加上当前值。最后再加上最后一个字符即可。
镜像地也可以 从右向左:维护
prev,当前值< prev就减,否则加。
方法一:从左到右
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 - 1):
if values[s[i]] < values[s[i + 1]]:
total -= values[s[i]]
else:
total += values[s[i]]
return total + values[s[-1]]
复杂度
- 时间:
O(n),n为字符串长度。 - 空间:
O(1)。
方法二:从右到左(推荐,更不易出错)
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
prev = 0
for ch in reversed(s):
cur = values[ch]
# 当前值比"前一个已处理值"小,就该减;否则加
if cur < prev:
total -= cur
else:
total += cur
prev = cur
return total
为什么不容易出错?循环里只看 当前值 与 前一个已处理的值,每个字符只
被访问一次,逻辑对称。
方法三:把特例也写到映射里(最直观)
class Solution:
def romanToInt(self, s: str) -> int:
# 提前把两字符的特例也列出来,匹配优先
table = {
"IV": 4, "IX": 9,
"XL": 40, "XC": 90,
"CD": 400, "CM": 900,
"I": 1, "V": 5, "X": 10,
"L": 50, "C": 100, "D": 500, "M": 1000,
}
i = total = 0
while i < len(s):
# 优先匹配两字符组合
if i + 1 < len(s) and s[i:i + 2] in table:
total += table[s[i:i + 2]]
i += 2
else:
total += table[s[i]]
i += 1
return total
复杂度
- 时间:
O(n)。 - 空间:
O(1)(映射大小固定为 13)。
测试用例
def test():
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("XL") == 40
assert s.romanToInt("CD") == 400
assert s.romanToInt("MMMDCCCLXXXVIII") == 3888
print("all passed")
test()
方法比较
| 方法 | 思路 | 代码量 | 可读性 | 推荐 |
|---|---|---|---|---|
| 方法一 | 左 → 右,加减交替 | 较短 | 中等 | ★★ |
| 方法二 | 右 → 左,标准模板 | 较短 | 较好 | ★★★ |
| 方法三 | 把特例写进映射 | 略长 | 最直观 | ★★ |
易错点
- 不要漏掉最后一个字符(方法一)。
for i in range(n-1)只处理了n-1个,
所以要+ values[s[-1]]。 - 不要对特例重复加:方法一里
IV的处理是“I减 1,V加 5”,自然合成 4。
不要先加 1 又加 5。 - 输入保证是 有效罗马数字(且 ≤ 3999),所以不需要额外校验。