LeetCode 13. 罗马数字转整数(多解法详解 + Java Python 实现)

LeetCode 13. 罗马数字转整数(多解法详解 + Java/Python 实现)

题目链接

13. 罗马数字转整数 - 力扣(LeetCode)


题目描述

罗马数字包含以下七种字符:IVXLCDM

字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000

例如,罗马数字 2 写做 II,即为两个并列的 1。12 写做 XII,即为 X + II。27 写做 XXVII,即为 XX + V + II

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4。同样地,数字 9 表示为 IX

这个特殊的规则只适用于以下六种情况:

  • I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
  • X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
  • C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。

给定一个罗马数字,将其转换成整数。


示例

示例 1

text 复制代码
输入: s = "III"
输出: 3

示例 2

text 复制代码
输入: s = "IV"
输出: 4

示例 3

text 复制代码
输入: s = "IX"
输出: 9

示例 4

text 复制代码
输入: s = "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.

示例 5

text 复制代码
输入: s = "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.

解题思路

这道题的核心是理解罗马数字的规则,特别是 特殊情况 的处理。

核心思想

罗马数字通常是从大到小排列的,但有时小的数字会出现在大的数字左边,表示减法

关键判断 :遍历字符串时,如果当前字符代表的数值 小于 下一个字符代表的数值,说明这是一个特殊情况,当前数值应该被 减去 ;否则,当前数值应该被 加上

算法步骤

  1. 建立字符到数值的映射。
  2. 遍历字符串,对于每个字符:
    • 获取当前字符的数值。
    • 如果当前字符不是最后一个,且当前数值小于下一个字符的数值,则减去当前数值。
    • 否则,加上当前数值。
  3. 返回总和。

举例说明

s = "MCMXCIV" 为例:

索引 字符 当前值 下一个值 判断 操作 总和
0 M 1000 100 1000 > 100 +1000 1000
1 C 100 1000 100 < 1000 -100 900
2 M 1000 10 1000 > 10 +1000 1900
3 X 10 100 10 < 100 -10 1890
4 C 100 1 100 > 1 +100 1990
5 I 1 5 1 < 5 -1 1989
6 V 5 - 最后一个 +5 1994

最终结果为 1994


代码实现

Java 版本

版本 1:HashMap 字典版
java 复制代码
import java.util.HashMap;
import java.util.Map;

class Solution {
    public int romanToInt(String s) {
        Map<Character, Integer> map = new HashMap<>();
        map.put('I', 1);
        map.put('V', 5);
        map.put('X', 10);
        map.put('L', 50);
        map.put('C', 100);
        map.put('D', 500);
        map.put('M', 1000);

        int sum = 0;
        for (int i = 0; i < s.length(); i++) {
            int cur = map.get(s.charAt(i));
            if (i < s.length() - 1 && cur < map.get(s.charAt(i + 1))) {
                sum -= cur;
            } else {
                sum += cur;
            }
        }
        return sum;
    }
}
版本 2:ASCII 数组查表版(刷题首选)
java 复制代码
class Solution {
    public int romanToInt(String s) {
        int[] arr = new int[256];
        arr['I'] = 1;
        arr['V'] = 5;
        arr['X'] = 10;
        arr['L'] = 50;
        arr['C'] = 100;
        arr['D'] = 500;
        arr['M'] = 1000;

        int sum = 0;
        for (int i = 0; i < s.length(); i++) {
            int cur = arr[s.charAt(i)];
            if (i < s.length() - 1 && cur < arr[s.charAt(i + 1)]) {
                sum -= cur;
            } else {
                sum += cur;
            }
        }
        return sum;
    }
}
版本 3:Switch-case 倒序遍历版
java 复制代码
class Solution {
    public int romanToInt(String s) {
        int sum = 0;
        int prev = 0;
        for (int i = s.length() - 1; i >= 0; i--) {
            int cur = getVal(s.charAt(i));
            if (cur >= prev) {
                sum += cur;
            } else {
                sum -= cur;
            }
            prev = cur;
        }
        return sum;
    }

    private int getVal(char c) {
        switch (c) {
            case 'I': return 1;
            case 'V': return 5;
            case 'X': return 10;
            case 'L': return 50;
            case 'C': return 100;
            case 'D': return 500;
            case 'M': return 1000;
            default: return 0;
        }
    }
}

Python 版本

版本 1:字典 dict 版本(最标准)
python 复制代码
class Solution(object):
    def romanToInt(self, s):
        """
        :type s: str
        :rtype: int
        """
        roman = {
            'I': 1,
            'V': 5,
            'X': 10,
            'L': 50,
            'C': 100,
            'D': 500,
            'M': 1000
        }
        total = 0
        n = len(s)
        for i in range(n):
            cur = roman[s[i]]
            if i < n - 1 and cur < roman[s[i+1]]:
                total -= cur
            else:
                total += cur
        return total
版本 2:倒序遍历版本
python 复制代码
class Solution(object):
    def romanToInt(self, s):
        """
        :type s: str
        :rtype: int
        """
        roman = {
            'I': 1,
            'V': 5,
            'X': 10,
            'L': 50,
            'C': 100,
            'D': 500,
            'M': 1000
        }
        total = 0
        prev = 0
        for char in reversed(s):
            cur = roman[char]
            if cur >= prev:
                total += cur
            else:
                total -= cur
            prev = cur
        return total

代码说明

正向遍历(版本 1 和 2)

  1. 建立映射 :使用 HashMap 或数组存储字符到数值的映射。
  2. 遍历字符串:从左到右遍历。
  3. 判断特殊情况
    • 如果当前字符的数值小于下一个字符的数值,说明是特殊情况(如 IVIX),当前数值应该被减去。
    • 否则,加上当前数值。
  4. 边界处理:最后一个字符没有下一个字符,直接加上。

倒序遍历(版本 3)

  1. 从右到左遍历:从字符串末尾开始。
  2. 记录前一个值prev 保存已经处理过的字符的最大数值。
  3. 判断
    • 如果当前值大于等于 prev,加上当前值。
    • 如果当前值小于 prev,减去当前值(特殊情况)。
  4. 倒序遍历的优点 :不需要判断 i+1 边界,逻辑更简洁。

复杂度分析

所有版本的时间复杂度和空间复杂度都相同:

  • 时间复杂度O(n),其中 n 是字符串的长度,只需要遍历一次。
  • 空间复杂度O(1),使用固定大小的映射(HashMap 或数组),不随输入规模变化。

版本对比

版本 优点 缺点 适用场景
HashMap 逻辑清晰,易于理解 哈希开销,速度较慢 学习理解
ASCII 数组 代码简短,速度快 需要理解 ASCII 码 日常刷题首选
Switch 正向 无额外内存,性能高 代码较长 性能要求高
Switch 倒序 不需要判断边界 逻辑需要转换 拓展思路

常见问题解答

Q1:为什么只有六种特殊情况?

因为罗马数字的规则中,只有 IXC 可以作为减数,且只能放在特定的大数前面。其他组合不符合规则。

Q2:倒序遍历的原理是什么?

倒序遍历时,我们维护 prev 变量记录已经处理过的字符的最大数值。如果当前字符的数值小于 prev,说明当前字符是作为减数出现的(特殊情况),应该减去;否则加上。

Q3:为什么 ASCII 数组的大小是 256?

因为 ASCII 字符的编码范围是 0-255,使用 256 大小的数组可以覆盖所有可能的字符。实际上,罗马数字字符的 ASCII 码都小于 100,但 256 是一个安全的默认值。


总结

这道题是字符串处理的经典题目,核心要点:

  1. 理解罗马数字规则:小的数字在大的数字左边表示减法。
  2. 正向遍历:判断当前值和下一个值的大小关系。
  3. 倒序遍历 :维护 prev 变量,避免边界判断。
  4. 多种实现方式HashMap、数组、switch-case 各有优劣。

关键点回顾:

  • 建立字符到数值的映射
  • 正向遍历:当前值 < 下一个值则减去,否则加上
  • 倒序遍历:当前值 < prev 则减去,否则加上
  • 时间复杂度 O(n),空间复杂度 O(1)

这道题虽然简单,但很好地考察了对字符串处理和映射的理解。掌握多种实现方式有助于应对不同的面试场景。

如果觉得本文对你有帮助,欢迎点赞、收藏、关注,我会持续更新更多 LeetCode 题解和算法干货!

相关推荐
省长1 小时前
Sa-Token v1.46.0 发布 🚀,来看看有没有令你心动的功能!
java·后端·开源
.道阻且长.1 小时前
10.LeetCode算法习题讲解--滑动窗口--最大连续为一的个数
算法·leetcode·职场和发展
machnerrn1 小时前
智慧交通系列(一)-十字路口车辆闯红灯检测告警抓拍系统(附含数据+源码+模型)
人工智能·python·深度学习
现代野蛮人1 小时前
【深度学习实验】—— 利用 RNN 模型进行心脏病预测
pytorch·python·tensorflow·ml
LayZhangStrive1 小时前
融360 一面
java·面试·后端开发
2601_956319882 小时前
2026年用示例、拆解和练习提升量化理解效率
人工智能·python
城管不管2 小时前
重生——第十次面试之开源中国一面挂
java·linux·开发语言·算法·面试·职场和发展·开源
Java小白笔记2 小时前
Windows系统免软件命令激活
java·网络·人工智能·windows·ai·ai编程
王志来137944730082 小时前
从分散到集成:工控服务器机箱采购如何实现“一站式”破局
运维·服务器·人工智能·python