在 LeetCode 简单题中,罗马数字转整数是一道经典的“规则理解+逻辑实现”类题目。它不仅考察对罗马数字计数规则的掌握,更能体现代码的简洁性与高效性。本文将从题目规则拆解入手,逐步分析解题思路,最终详解代码实现,并补充优化方向,帮助大家彻底吃透这道题。
一、题目核心规则梳理
罗马数字由 7 个基础字符组成,对应固定数值,这是解题的基础:
| 罗马字符 | 对应数值 |
|---|---|
| I | 1 |
| V | 5 |
| X | 10 |
| L | 50 |
| C | 100 |
| D | 500 |
| M | 1000 |
罗马数字的计数有两个核心规则,直接决定解题逻辑:
-
常规规则:小数值字符在大数值字符右侧,数值为两者之和(如 XII = 10 + 1 + 1 = 12,XXVII = 10 + 10 + 5 + 1 + 1 = 27)。
-
特殊规则:仅 6 种特例,小数值字符在大数值字符左侧,数值为大数减小数(如 IV = 5 - 1 = 4,IX = 10 - 1 = 9;XL = 50 - 10 = 40,XC = 100 - 10 = 90;CD = 500 - 100 = 400,CM = 1000 - 100 = 900)。
二、解题思路推导
拿到题目后,最直观的想法是“逐个字符处理,结合相邻字符判断加减”。核心逻辑在于:遍历每个字符时,对比当前字符与下一个字符的数值大小:
-
若当前字符数值 < 下一个字符数值:符合特殊规则,需用结果减去当前数值(后续加上下一个数值,等价于“大数减小数”)。
-
若当前字符数值 ≥ 下一个字符数值:符合常规规则,直接将当前数值加入结果。
这个思路的优势的是“一次遍历即可完成计算”,时间复杂度为 O(n)(n 为罗马数字字符串长度),空间复杂度为 O(1)(仅用有限变量存储结果和映射关系),是最优解之一。
三、代码实现与逐行解析
基于上述思路,给出 TypeScript 代码实现,并逐行拆解逻辑:
function romanToInt(s: string): number {
// 1. 建立罗马字符与数值的映射表,方便快速查询
const search: Record<string, number> = {
'I': 1,
'V': 5,
'X': 10,
'L': 50,
'C': 100,
'D': 500,
'M': 1000
}
// 2. 将字符串拆分为字符数组(也可直接通过索引访问,无需拆分,此处为直观起见)
const sArr: string[] = s.split('');
// 3. 初始化结果变量
let res = 0;
// 4. 遍历字符数组,结合索引和值处理
for (let [index, value] of sArr.entries()) {
// 5. 获取下一个字符的数值,若为最后一个字符,下一个数值默认为 0(避免越界)
const next = search[sArr[index + 1]] || 0;
// 6. 对比当前值与下一个值,判断加减
if (search[value] < next) {
res -= search[value];
} else {
res += search[value];
}
}
// 7. 返回最终结果
return res;
};
关键细节说明
-
映射表设计:用对象(Record 类型)存储映射关系,查询时间复杂度为 O(1),比数组查询更高效。
-
越界处理:当遍历到最后一个字符时,index + 1 超出数组长度,此时 next 设为 0,确保逻辑统一(最后一个字符必然满足“当前值 ≥ 0”,直接加至结果)。
-
字符数组拆分:s.split('') 并非必需,可直接通过 s[index] 和 s[index+1] 访问字符,进一步简化代码(下文优化会提及)。
四、代码优化与拓展
优化版本(精简代码,去除数组拆分)
无需将字符串拆分为数组,直接通过索引遍历字符串,减少内存占用:
function romanToInt(s: string): number {
const map: Record<string, number> = { 'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000 };
let res = 0;
for (let i = 0; i < s.length; i++) {
// 直接通过字符串索引访问,无需数组
if (map[s[i]] < map[s[i+1]]) {
res -= map[s[i]];
} else {
res += map[s[i]];
}
}
return res;
};
该版本逻辑与原版本一致,但代码更简洁,空间复杂度进一步优化(无额外字符数组占用)。
其他解题思路(了解即可,效率略低)
除了“逐字符对比法”,还可通过“替换特殊组合”的思路解题:
-
将 6 种特殊组合(IV、IX、XL、XC、CD、CM)替换为对应的数值字符串(如 IV 替换为 4,IX 替换为 9)。
-
遍历替换后的字符串,累加每个字符对应的数值。
该思路代码可读性较差,且替换操作会增加时间开销,不如“逐字符对比法”高效,仅作为思路拓展。
五、测试用例验证
为确保代码正确性,可通过以下典型测试用例验证:
-
常规用例:s = "III" → 3;s = "LVIII" → 58(50 + 5 + 3)。
-
特殊用例:s = "IV" → 4;s = "IX" → 9;s = "MCMXCIV" → 1994(1000 + 900 + 90 + 4)。
-
边界用例:s = "M" → 1000(单个字符);s = "CMXCIX" → 999(全为特殊组合)。
将上述用例代入代码,均能得到正确结果,说明逻辑无漏洞。
六、总结
罗马数字转整数的核心是吃透特殊规则,通过相邻字符对比简化逻辑。本文给出的最优解法具备 O(n) 时间复杂度和 O(1) 空间复杂度,代码简洁且易维护。
这道题的启示的是:面对有“特殊规则”的题目,无需单独处理特殊情况,而是尝试将特殊规则融入通用逻辑中,实现代码的简洁性与高效性。后续遇到类似“字符串遍历+规则判断”的题目,可借鉴这种“相邻元素对比”的思路。