1. 题目
13. 罗马数字转整数 - 力扣(LeetCode)
题目描述
罗马数字包含以下七种字符: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) 满足s[i+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 等所有特例。