LeetCode 13简单数学
罗马数字转整数 图解题解
这道题到底在问什么
给一个罗马数字字符串 s,转成整数。符号 I=1 V=5 X=10 L=50 C=100 D=500 M=1000;遇到 IV / IX / XL / XC / CD / CM 这种「小符号在大符号左边」要做减法。
- s
- "MCMXCIV"
- 输出
- 1994
- 拆解
- M(1000)+CM(900)+XC(90)+IV(4)
最优解:一步一步想明白
- 3切块法要先想清楚「哪两个字符要合并」,边界一多就容易写错。有没有不用提前切块的办法?
- 4不用切块!逐个字符看:cur < 右邻 → 减,否则 → 加。比如 IV 的 I(1) 比右边 V(5) 小,就减 1;V 没有右边,就加 5。
- 5盯住三件事:cur 指针在哪、当前符号和右邻谁大、total 怎么变。绿色 visited = 已处理完的格子。
- 6cur 落在第0格 M(1000)cur 停在第 0 格 M(1000)。规则:要看它和右边那格谁大,才知道该加还是减。
- 7右邻 = 第1格 C(100)右边那格(变暗的)是 C(100)。把这两个数拎出来比:M 1000、C 100。
- 8cur=M,右邻=C,1000>100右邻是 C(100)。1000 > 100,不是减法形式 → 正常加。total 从 0 变 1000。
- 9M 已处理,cur → 第1格 C第 0 格 M 处理完变绿,cur 右移到第 1 格 C(100)。又要和右邻比了。
- 10cur=C,右邻=M,100<1000右邻是 M(1000)。100 < 1000 → 这个 C 是减法形式 CM 的前半,要减!total 反而从 1000 降到 900。这正是减法的精髓。
- 11C 已处理,cur → 第2格 M前两格 M、C 都处理完了(绿)。cur 来到第 2 格 M(1000),继续和右邻比。
- 12cur=M,右邻=X,1000>10右邻 X(10)。1000 > 10 → 正常加。total 升到 1900。这个 M 就是 CM 里的「大」那一半,加回来了。
- 13cur → 第3格 Xcur 移到第 3 格 X(10)。又是熟悉的动作:看右边。
- 14cur=X,右邻=C,10<100右邻 C(100)。10 < 100 → X 是减法形式 XC 的前半,减!total 降到 1890。
- 15cur → 第4格 Ccur 到第 4 格 C(100)。注意同样是 C,这次右边不一样,结果就可能不同——继续比。
- 16cur=C,右邻=I,100>1右邻 I(1)。100 > 1 → 这个 C 正常加(它是 XC 里的大半)。total 升到 1990。
- 17cur → 第5格 Icur 到第 5 格 I(1),只剩最后两格了。还是先看右边。
- 18cur=I,右邻=V,1<5右邻 V(5)。1 < 5 → I 是减法形式 IV 的前半,减!total 降到 1989。
- 19cur → 第6格 V,无右邻最后一格 V(5),右边没有字符了。没有右邻就不可能是减法形式 → 直接加。total = 1994!
- 20MCMXCIV → 1994 ✓7 格全部走完。三处「小在大左边」的格子(C、X、I)做了减,其余做加,合起来正好 MCMXCIV = 1994。整条规则就这么简单。
- 21只有 2 格:I、X换个短例子 IX 巩固减法。cur 在第 0 格 I,右邻是 X。
- 22右邻 = X(10)把右邻 X(10) 拎出来(变暗格)和 I(1) 比。明显 X 更大。
- 231<10 → 减1 < 10 → I 减掉,total 先变成 −1。别慌,下一步 X 加回来。
- 24cur → X,无右邻 → 加X 没有右邻 → 加 10,total = −1 + 10 = 9。可见「先减小的、再加大的」自然就把 IX 算成 9,完全不用提前识别 IX 是个整体。
- 25比如 IV:先加 I(=1),到 V 时发现上一个 I 比 V 小,于是 total = 1 + 5 − 2×1 = 4。和「向右看」是同一回事,代码里两种都常见,理解一种即可。
- 29把 IV 全加 → 6 ✗假如不做减法判断,IV 会被算成 1+5=6。正因为 I 在比它大的 V 左边要减,答案才是 4。这就是减法判断不能省的原因。
⚠️ 容易写错的地方
✗ 错:无脑全加
✓ 对:判断 cur < 右邻 时要减
全加会把 IV 算成 1+5=6(应为 4),IX 算成 11(应为 9)
✗ 错:看右邻时数组越界
✓ 对:先判 i+1 < n 再访问 s[i+1]
最后一格没有右邻,不判断会越界崩溃
完整代码(Python / C++ / Java)
Python
class Solution:
def romanToInt(self, s):
d = {'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}
total = 0
n = len(s)
for i in range(n):
if i + 1 < n and d[s[i]] < d[s[i+1]]: # 小在大左边
total -= d[s[i]] # 减
else:
total += d[s[i]] # 加
return totalC++
class Solution {
public:
int romanToInt(string s) {
unordered_map<char,int> d = {
{'I',1},{'V',5},{'X',10},{'L',50},
{'C',100},{'D',500},{'M',1000}};
int total = 0, n = s.size();
for (int i = 0; i < n; i++) {
if (i + 1 < n && d[s[i]] < d[s[i+1]]) total -= d[s[i]];
else total += d[s[i]];
}
return total;
}
};Java
class Solution {
public int romanToInt(String s) {
Map<Character,Integer> d = new HashMap<>();
d.put('I',1); d.put('V',5); d.put('X',10); d.put('L',50);
d.put('C',100); d.put('D',500); d.put('M',1000);
int total = 0, n = s.length();
for (int i = 0; i < n; i++) {
if (i + 1 < n && d.get(s.charAt(i)) < d.get(s.charAt(i+1)))
total -= d.get(s.charAt(i));
else
total += d.get(s.charAt(i));
}
return total;
}
}复杂度
时间复杂度
O(n)
n = 字符串长度。每个字符只看一次,顺带瞄一眼右邻(O(1) 查表),总共扫一遍
空间复杂度
O(1)
符号值表固定 7 项,只用一个 total 累加,与输入长度无关
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 罗马数字转整数 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么向右看一格就够?+
罗马数字的减法形式都是恰好两个字符(小+大),当前字符是否要减,只取决于它和紧邻右边那个谁大,无需看更远。
不查右邻能做吗?+
能。等价写法是默认全加,但若上一个符号比当前小,就把上一个多减两倍(total += 5; 到 V 时发现前面 I 更小,减 2×1)。
反过来整数转罗马怎么做?+
把 13 个面值(含 CM/CD/XC… 减法形式)从大到小排好,贪心地用 while「能减就减」拼符号。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 罗马数字转整数 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。