1. 题目
题目描述
罗马数字包含以下七种字符:I,V,X,L,C,D,M。
| 字符 | 数值 |
|---|---|
| I | 1 |
| V | 5 |
| X | 10 |
| L | 50 |
| C | 100 |
| D | 500 |
| M | 1000 |
特殊规则:
-
正常情况大数在右,直接相加,如
III=3、VI=6; -
减法特例:
I在V/X前 =4/9;X在L/C前 = 40/90;C在D/M前 = 400/900。
给定合法罗马字符串,转换为对应整数。
示例
输入:III → 3
输入:IV → 4
输入:IX → 9
输入:LVIII → 58
输入:MCMXCIV → 1994
约束
-
1≤s .length≤15
-
s 仅由
I,V,X,L,C,D,M组成,输入保证合法罗马数字
2. 最佳解题思路描述(哈希映射 + 后项比较,极简通用)
核心规律
罗马数字整体从左到右数值递减;
-
若当前字符值 < 右侧字符值:属于减法组合,总和减去当前值;
-
其余情况:总和加上当前值。
步骤:
-
建立字符到数字的映射表;
-
遍历字符串到倒数第二位:
-
map[s[i]] < map[s[i+1]]:sum -= map[s[i]] -
否则:
sum += map[s[i]]
-
-
最后单独加上末尾字符的值;
优势
-
代码短,无冗长 switch 分支;
-
统一一套判断逻辑,不用分七种字符单独处理特殊情况;
-
时间 O (n ),空间 O(1)(固定 7 个映射)。
3. 我的可优化代码(逻辑存在多处 bug,思路繁琐)
class Solution {
public:
int romanToInt(string s) {
int sum = 0;
for(int i=0;i<s.length();i++){
switch(s[i]){
case 'I':
if(s[i+1]=='V'||s[i+1]=='X')
break;
sum++;
break;
case 'V':
if(i>0 && s[i-1]=='I'){
sum+=4;
break;
}
sum+=5;
break;
case 'X':
if(i>0 && s[i-1]=='I'){
sum+=9;
break;
}
if(s[i+1]=='L'||s[i+1]=='C')
break;
sum+=10;
break;
case 'L':
if(i>0 && s[i-1]=='X'){
sum+=40;
break;
}
sum+=50;
break;
case 'C':
if(i>0 && s[i-1]=='X'){
sum+=90;
break;
}
if(s[i+1]=='D'||s[i+1]=='M')
break;
sum+=100;
break;
case 'D':
if(i>0 && s[i-1]=='C'){
sum+=400;
break;
}
sum+=500;
break;
case 'M':
if(i>0 && s[i-1]=='C'){
sum+=900;
break;
}
sum+=1000;
break;
default:
break;
}
}
return sum;
}
};
代码致命 bug
-
越界访问
s[i+1]i 走到最后一位时i+1超出字符串下标,访问非法内存,运行崩溃;
-
重复叠加数值
例如IV:i=0 (I) 满足si+1=='V'直接 break,不加 1;i=1 (V) 判断前一位是 I,sum +=4,结果正确;
但IX、XL、XC、CD、CM均会出现重复特殊值叠加逻辑,极容易算错;
-
分支逻辑割裂,极易漏写 / 写错特殊条件
七种字符分开处理,每个字符单独判断左右相邻,代码冗余庞大,维护困难;
-
错误判断:
X分支判断s[i-1]=='I'求 9,逻辑写反,IX是 I 在前 X 在后,该判断永远不会触发,IX计算直接出错。
整体缺陷
-
靠 switch 暴力分情况,代码冗长、边界越界、逻辑易出错;
-
没有统一的数学判断规则,靠人工枚举所有减法组合,扩展性差。
4. 最优标准代码(哈希映射,统一判断)
#include <unordered_map>
#include <string>
using namespace std;
class Solution {
public:
int romanToInt(string s) {
unordered_map<char, int> mp = {
{'I',1},{'V',5},{'X',10},{'L',50},
{'C',100},{'D',500},{'M',1000}
};
int sum = 0;
int n = s.size();
for(int i = 0; i < n - 1; i++){
if(mp[s[i]] < mp[s[i+1]]){
sum -= mp[s[i]];
}else{
sum += mp[s[i]];
}
}
// 最后一位一定只加不减
sum += mp[s.back()];
return sum;
}
};
5. 总结
-
你的 switch 暴力分支写法存在数组越界、逻辑判断错误,无法正常通过用例,不推荐;
-
通用核心规则:前小后大则减当前值,否则加当前值,一套逻辑覆盖所有情况;
-
遍历只到倒数第二位,避免访问
i+1越界,末尾字符单独累加; -
用哈希表存储字符数值映射,消除大量重复 if/switch 分支,代码简洁易读。
6. 相关知识拓展
拓展 1:反向遍历简化写法
从后往前遍历,记录最大值,当前值小于最大值则相减,否则更新最大值并相加:
int romanToInt(string s) {
unordered_map<char,int> mp={{'I',1},{'V',5},{'X',10},{'L',50},{'C',100},{'D',500},{'M',1000}};
int sum=0,maxVal=0;
for(int i=s.size()-1;i>=0;i--){
int cur=mp[s[i]];
if(cur<maxVal) sum-=cur;
else {
sum+=cur;
maxVal=cur;
}
}
return sum;
}
拓展 2:进阶题目 LC12 整数转罗马数字
逆向转换,采用贪心,从大到小匹配数值符号对;
拓展 3:复杂度对比
-
switch 暴力分支:代码冗余,存在越界 bug,理论时间 O (n);
-
哈希正向遍历最优解:时间 O (n),空间 O (1)(固定 7 组映射);
-
反向遍历:时间 O (n),空间 O (1)。
拓展 4:易错点记忆
-
正向遍历不要访问
s[n],循环上限n-1; -
减法组合本质:小数出现在大数左侧,统一做减法,无需单独枚举 IV、IX 等所有特例。