13. 罗马数字转整数

158 阅读7分钟

题目

罗马数字包含以下七种字符: I, V, X, LCD 和 M

字符          数值
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:

输入: s = "III"
输出: 3

示例 2:

输入: s = "IV"
输出: 4

示例 3:

输入: s = "IX"
输出: 9

示例 4:

输入: s = "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.

示例 5:

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

提示:

  • 1 <= s.length <= 15
  • s 仅含字符 ('I', 'V', 'X', 'L', 'C', 'D', 'M')
  • 题目数据保证 s 是一个有效的罗马数字,且表示整数在范围 [1, 3999] 内
  • 题目所给测试用例皆符合罗马数字书写规则,不会出现跨位等情况。
  • IL 和 IM 这样的例子并不符合题目要求,49 应该写作 XLIX,999 应该写作 CMXCIX 。
  • 关于罗马数字的详尽书写规则,可以参考罗马数字 - Mathematics 

解题思路:

转换的核心在于理解罗马数字的规则,例如,通常情况下小的数字在大的数字右边,但是有六种特殊情况(如IV、IX等),小的数字在大的数字左边,表示大数减小数。

首次尝试:

最初的代码使用了一个字典来存储罗马字符与其对应的数值,并进行从右到左的遍历。

# 将代码注释改为中文
class Solution:
    def romanToInt(self, s: str) -> int:
        # 罗马数字到整数的映射关系
        roman_map = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
        
        total = 0
        prev_value = 0

        # 从右向左遍历罗马数字
        for i in reversed(s):
            # 获取当前罗马数字对应的整数值
            int_value = roman_map[i]

            # 如果当前罗马数字的整数值小于前一个,则从总数中减去,否则加上
            if int_value < prev_value:
                total -= int_value
            else:
                total += int_value

            # 更新前一个数字的值
            prev_value = int_value

        return total

执行效率:

这个初始方法在执行时的效率取决于输入字符串的长度。在实际测试中,它在时间复杂度上表现为O(n),但存在优化空间。

image.png

优化思路:

为了优化执行效率,我们考虑以下几点:

  • 减少字典查找次数: 字典查找虽然快,但仍有优化空间。
  • 避免反转字符串: 可以从左到右遍历字符串,减少计算步骤。
  • 减少操作次数: 当前值小于之前值时,需要调整已加的值。

优化方法:

优化后的代码从左到右遍历字符串,并且只在必要时调整总数,这减少了操作次数。

class Solution:
    def romanToInt(self, s: str) -> int:
        # 罗马数字到整数的映射关系
        roman_map = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
        
        total = 0
        prev_value = 0  # 初始化为0,因为第一个字符前面没有字符

        # 从左向右遍历罗马数字
        for i in s:
            int_value = roman_map[i]  # 获取当前罗马数字对应的整数值

            # 如果当前罗马数字的整数值小于下一个,则减去当前数值;否则加上当前数值
            if int_value > prev_value:
                total += int_value - 2 * prev_value
            else:
                total += int_value

            prev_value = int_value  # 更新前一个数字的值

        return total

执行效率:

优化后的代码在各种输入条件下的执行时间都有所降低。特别是在一些复杂的罗马数字字符串中,由于减少了字符串操作和字典查找的次数,所以运行时间更快。

image.png

总结:

字符串解析和转换是一类常见的问题,它要求程序能够理解和转换各种格式的数据。罗马数字转整数问题就属于这一类,它不仅考验了对古老数学系统的理解,也考验了编程语言中字符串处理的技能。

问题适用性:

本题适合使用解析类算法,这类算法特别适用于需要解释或转换表示为字符序列的数据的情况。在这个问题中,我们使用了解析方法来理解罗马数字的组成,并将其转换成对应的整数值。

算法理论:

在最初的解法中,我们使用了贪心算法的思想,从罗马数字的末尾开始解析,每次取最大可用的数值,直到整个字符串被解析完毕。这种方法简单直观,但在每次迭代时都需要进行字典查找和字符串操作,这在长字符串中尤其耗时。

优化方法:

针对初步解法中的效率问题,我们进行了优化。优化后的方法仍然使用了贪心算法的思想,但是通过从左到右的单次遍历来减少操作次数。当我们遇到一个值比前一个值大时,意味着出现了如 "IV" 或 "IX" 这样的特殊组合,此时我们需要从总数中减去两倍的前一个值(因为在之前的迭代中已经加过一次了)。这个优化减少了对字符串的反复迭代和字典的查找次数,特别是在解析那些包含特殊组合的罗马数字时,效率提升更为明显。

实际应用:

在实际应用中,这种优化方法特别适合于需要高效处理大量数据的场景,例如,在数据库中转换存储的罗马数字或在实时系统中解析来自用户输入的罗马数字。优化后的算法能够减少计算时间和资源消耗,从而提高系统的响应速度和处理能力。

总的来说,罗马数字转整数问题展示了如何通过理解问题的特性和应用适

当的算法理论来提高代码的效率。对于解析和转换类问题,贪心算法提供了一种强大的解决方案,尤其当我们能够减少不必要的操作和优化数据访问时。在这种情况下,贪心算法以其简洁和直接的方式,能够提供既正确又高效的解决方案。通过分析每个字符并仅在必要时调整累积值,我们避免了不必要的计算和内存操作,这是在处理需要高性能解析的大数据问题时至关重要的。

在进行算法优化时,我们首先识别了代码的瓶颈(如字典查找和多次迭代),然后应用了改进措施(如单次遍历和减少查找次数)。这种方法论不仅适用于当前的问题,也可以广泛应用于其他算法优化场景。例如,当处理排序、搜索或数据压缩等问题时,适当的优化可以显著提高算法的执行效率,特别是在资源受限或时间敏感的应用中。

最后,这个练习强调了编码实践中持续评估和改进算法性能的重要性。优化算法不仅提高了程序的性能,还帮助我们深入理解了问题的本质和算法的内在工作机制,这是每个程序员提升技能的宝贵过程。

题目链接

罗马数字转整数