来源:LeetCode
难度:中等
问题详情:
罗马数字包含以下七种字符: I
, V
, X
, L
,C
,D
和 M
。
字符 | 数值 |
---|---|
I I I | 1 |
V V V | 5 |
X X X | 10 |
L L L | 50 |
C C C | 100 |
D D D | 500 |
M M M | 1000 |
例如, 罗马数字 2
写做 II
,即为两个并列的 1
。12
写做 XII
,即为 X + II
。 27
写做 XXVII
, 即为 XX + V + II
。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4
不写做 IIII
,而是 IV
。数字 1
在数字 5
的左边,所表示的数等于大数 5
减小数 1
得到的数值 4
。同样地,数字 9
表示为 IX
。这个特殊的规则只适用于以下六种情况:
给你一个整数,将其转为罗马数字。
示例1:
输入:num = 3
输出:“III”
示例2:
输入: num = 1994
输出: “MCMXCIV”
解释: M = 1000, CM = 900, XC = 90, IV = 4.
提示:
1 < = n u m < = 3999 1 <= num <= 3999 1<=num<=3999
在真正开始介绍各种算法前,先以表格形式展示各自的时间复杂度和空间复杂度,其中 n n n表示字符串 s s s的长度。
算法 | 时间复杂度 | 空间复杂度 |
---|---|---|
模拟 | O ( 1 ) O(1) O(1) | O ( 1 ) O(1) O(1) |
硬编码 | O ( 1 ) O(1) O(1) | O ( 1 ) O(1) O(1) |
自己的解法 | O ( 1 ) O(1) O(1) | O ( 1 ) O(1) O(1) |
图片来源。
我们先来看一下示例中的1994
是如何转换成罗马数字的。
首先数字1994
被拆解成1000+900+90+4
.
可以看出每一位都是用选取尽可能大的数值。
构建一个从大到小的元组对,遍历每一个元组对,如果当前num
值大于等于当前元组中的数值key
,那么就减去足够多的key
,使
n
u
m
<
k
e
y
numkey
,就要记录多少次key
对应的罗马字符。
直至
n
u
m
=
0
num=0
num=0,循环结束。
def intToRoman3(num: int) -> str:
"""对方法2中相减那一处的优化"""
VALUE_SYMBOLS = [
(1000, "M"),
(900, "CM"),
(500, "D"),
(400, "CD"),
(100, "C"),
(90, "XC"),
(50, "L"),
(40, "XL"),
(10, "X"),
(9, "IX"),
(5, "V"),
(4, "IV"),
(1, "I"),
]
result = ''
for key, value in VALUE_SYMBOLS:
if num >= key:
count = (num // key)
num -= count * key
result += count * value
if num == 0:
break
return result
时间复杂度为 O ( 1 ) O(1) O(1),空间复杂度为 O ( 1 ) O(1) O(1)
上述方案是从上图中的每一个“挡位”对应的罗马字符出发构建的。
而硬解码的方案则是从num
数值中每一位的角度出发。
由提示可以得到,num值最多为3999;所以我们先从千位出发,考虑其能够取到的值,是1、2、3,然后构建其对应的罗马数字’M‘,'MM','MM'
;
同理,我们构建了如下表格(表格来源):
代码如下:
THOUSANDS = ["", "M", "MM", "MMM"]
HUNDREDS = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"]
TENS = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"]
ONES = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"]
def intToRoman4(num: int) -> str:
"""硬编码,就是把每一位的所有可能都列出来,以空间换时间"""
return THOUSANDS[num // 1000] + HUNDREDS[num % 1000 // 100] + TENS[num % 100 // 10] + ONES[num % 10
时间复杂度为 O ( 1 ) O(1) O(1),空间复杂度为 O ( 1 ) O(1) O(1)
我的解法同样从每一位出发,但是不使用硬编码的形式,而是通过当前位数的数值分析其对应的罗马数字。
比如百位中如果是9
或者4
,则对应的是'CM'
,’CD‘
;
如果是7
,则大于等于5
的部分使用一个’D‘
表示,不超过的部分即(7-5=2)
使用’C‘×2
表示;
如果百位本身就是小于4
的数,比如3
,则直接使用’C‘×3
表示;
代码中构建了一个二维列表,每个元素中都存储了当前位数的两个对应的罗马字符。
比如百位,第0个元素是’D‘,第1个元素是’C‘。
def intToRoman(num: int) -> str:
special = {9: 'IX', 4: 'IV', 40: 'XL', 90: 'XC', 400: 'CD', 900: 'CM'}
index_str = [['V', 'I'], ['L', 'X'], ['D', 'C'], ['', 'M']]
index = [3, 2, 1, 0]
result = ''
for i in index:
cur, num = divmod(num, 10 ** i)
if cur == 4 or cur == 9:
result += special.get(cur * 10 ** i, '')
elif 9 > cur >= 5:
result += index_str[i][0]
result += (cur - 5) * index_str[i][1]
elif cur < 4:
result += cur * index_str[i][1]
return result
时间复杂度为 O ( 1 ) O(1) O(1),空间复杂度为 O ( 1 ) O(1) O(1)