LeetCode 13. 罗马数字转整数

1826 字
9 分钟
LeetCode 13. 罗马数字转整数

LeetCode 13. 罗马数字转整数#

标签#

  • 平台:LeetCode
  • 难度:简单
  • 数据结构:字符串
  • 算法:模拟
  • 解题模式:无

一、题目概述#

给定一个罗马数字字符串,将它转换成对应的整数。当前实现支持常见罗马数字字符,并在遇到非法字符时抛出 IllegalArgumentException。对于 null 或空字符串,当前实现返回 0

二、解题思路#

罗马数字通常从左到右按值累加,但当较小值出现在较大值左侧时,需要使用减法规则,例如 IV 表示 4IX 表示 9

当前实现从左到右遍历字符串。每次取当前字符的值,并在右侧还有字符时取下一个字符的值。如果当前值小于下一个值,说明当前字符参与减法规则,需要从结果中减去当前值;否则加上当前值。

三、执行过程#

  1. 如果输入为 null 或空字符串,返回 0
  2. 初始化结果 result = 0
  3. 从左到右遍历罗马数字字符串。
  4. 通过 getValue 获取当前字符对应的整数值。
  5. 如果右侧还有字符,比较当前值和下一个值。
  6. 当前值小于下一个值时,执行减法并继续下一轮。
  7. 否则把当前值加到结果中。
  8. 遍历结束后返回 result

例如 MCMXCIVC 小于 M,按减法处理;X 小于 C,按减法处理;I 小于 V,按减法处理,最终得到 1994

四、代码实现#

public class LeetCode0013RomanToInteger {
public int romanToInt(String roman) {
if (roman == null || roman.isEmpty()) {
return 0;
}
int result = 0;
for (int i = 0; i < roman.length(); i++) {
int currentValue = getValue(roman.charAt(i));
if (i + 1 < roman.length()) {
int nextValue = getValue(roman.charAt(i + 1));
if (currentValue < nextValue) {
result -= currentValue;
continue;
}
}
result += currentValue;
}
return result;
}
private int getValue(char romanCharacter) {
return switch (romanCharacter) {
case 'I' -> 1;
case 'V' -> 5;
case 'X' -> 10;
case 'L' -> 50;
case 'C' -> 100;
case 'D' -> 500;
case 'M' -> 1000;
default -> throw new IllegalArgumentException("非法的罗马数字字符:" + romanCharacter);
};
}
}

五、关键代码说明#

  • roman == null || roman.isEmpty():当前实现把空输入转换为 0
  • getValue:把单个罗马数字字符映射为整数。
  • currentValue < nextValue:识别减法规则。
  • continue:当前字符按减法处理后,跳过本轮后面的累加逻辑。
  • switchdefault 分支用于处理非法字符。

六、复杂度分析#

  • 时间复杂度:O(n),其中 n 是字符串长度,每个字符最多被处理常数次。
  • 空间复杂度:O(1),只使用常量变量。

七、注意事项#

  • 判断空输入时必须使用短路或 ||,避免 roman == null 时继续调用 isEmpty()
  • 当前实现会接受任意“小值在大值左侧”的减法形式,没有额外校验罗马数字格式是否合法。
  • 非法字符会在 getValue 中抛出异常。

八、面试知识点:字符串模拟与规则映射#

1. 为什么这题属于模拟?#

这题没有复杂的数据结构,关键是把罗马数字规则翻译成代码。

普通情况下,罗马数字从左到右累加;如果较小值出现在较大值左边,就表示减法规则。当前实现正是逐字符扫描,并根据当前字符和下一个字符的大小关系决定加还是减。

2. 罗马数字的核心映射是什么?#

常见字符和数值关系是:

I -> 1
V -> 5
X -> 10
L -> 50
C -> 100
D -> 500
M -> 1000

当前代码使用 switch 把字符映射成整数。这样写的优点是直接、清晰,并且非法字符会进入 default 分支抛出异常。

3. 什么是减法规则?#

当小值出现在大值左边时,不是加上当前值,而是减去当前值。

例如:

IV = 5 - 1 = 4
IX = 10 - 1 = 9
XL = 50 - 10 = 40
CM = 1000 - 100 = 900

当前实现通过比较当前值和下一个值判断是否需要减法:

if (currentValue < nextValue) {
result -= currentValue;
continue;
}

4. 为什么只需要看右边一个字符?#

罗马数字的减法规则只和当前字符及其右侧紧邻字符有关。只要当前值小于下一个值,就说明当前字符应该被减掉。

因此当前实现不需要回溯,也不需要复杂解析器,只需要从左到右扫描一遍。

5. switch 和 HashMap 映射怎么选?#

这题也可以用 Map<Character, Integer> 保存映射关系。

switch 的优点:

  • 没有额外集合初始化;
  • 分支清晰;
  • 非法字符处理集中在 default
  • 字符种类固定时很适合。

HashMap 的优点:

  • 映射关系可以数据化;
  • 如果字符规则很多,扩展更方便。

当前题只有 7 个固定字符,用 switch 更简洁。

6. 为什么空字符串和 null 返回 0?#

当前仓库实现约定:

if (roman == null || roman.isEmpty()) {
return 0;
}

这不是所有平台题解都会采用的行为,但它是当前源码和测试明确覆盖的行为。

这里必须使用 || 的短路特性。如果 roman == null 已经成立,后面的 roman.isEmpty() 不会再执行,避免空指针异常。

7. 当前实现有没有校验罗马数字合法格式?#

当前实现会校验字符是否合法,但不会完整校验罗马数字格式是否合法。

例如只要字符都在 I,V,X,L,C,D,M 中,并且出现“小值在大值左侧”,代码就会按减法处理。它没有额外限制 I 只能放在 VX 前面,也没有限制重复次数。

面试时要区分:

  • 字符合法性校验:当前实现做了;
  • 罗马数字格式合法性校验:当前实现没有完整做。

8. 时间复杂度为什么是 O(n)?#

代码只从左到右遍历一次字符串。每个字符最多会被当前轮作为 currentValue 处理,也可能被前一轮作为 nextValue 查看,但都是常数次操作。

因此时间复杂度是 O(n),空间复杂度是 O(1)

9. 字符串模拟题常见思路#

字符串模拟题通常要先把规则拆成局部判断:

  1. 当前字符代表什么;
  2. 当前字符和相邻字符有没有特殊关系;
  3. 遇到特殊规则时是跳过、累加还是扣减;
  4. 非法输入如何处理;
  5. 边界位置是否会越界。

本题的边界关键是 i + 1 < roman.length(),只有右边还有字符时才访问下一个字符。

10. 回到本题,面试时怎么回答?#

可以这样组织回答:

这道题我按字符串模拟来做。先用 switch 把罗马字符映射成整数,然后从左到右遍历字符串。对于每个字符,如果右边还有字符,就比较当前值和下一个值;如果当前值小于下一个值,说明出现了类似 IVIXCM 的减法规则,于是从结果中减去当前值,否则加上当前值。

当前实现对 null 和空字符串返回 0,对非法字符抛出 IllegalArgumentException。整体只扫描一遍字符串,时间复杂度是 O(n),只使用常量变量,空间复杂度是 O(1)

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

LeetCode 13. 罗马数字转整数
https://firefly-mu-weld.vercel.app/posts/leetcode-0013-罗马数字转整数/
作者
Daisy
发布于
2026-06-11
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Daisy
Hello, I'm Daisy.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签

文章目录