题目跟前面13题描述一样,就是问题变为整数转成罗马数字。
上一道题罗马数字转整数比较简单,因为不存在罗马数字表示冲突的问题,即不存在一个罗马数字对应多个整数。而这个问题中,就要考虑一下这个问题了,因为如果不加以约束的话,一个整数可以用多种罗马数字来表示。比如对于2000:
2000 = 1000 + 1000 = M + M,即MM
2000 = 1000 + 900 + 100 = M + CM + C,即MCMC
于是,我特地查了一下,罗马数字的排列规则是从左到右尽可能用最大的数字来表示。那么,就像之前那样,准备好一个字典,反过来以整数为键,罗马数字为值,存放13种键-值组合。
然后,只要整数num(待转换的数)不为0,就在每次循环里找到一个离他最近的罗马数字对应的整数n,记录该罗马数字,然后让num-n,继续循环,直到num=0为止。
伪代码描述:
buf = {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’}
res = “”
while num != 0
找到最接近num的整数n
res += buf[n]
num -= n
return res
class Solution:
def intToRoman(self, num: int) -> str:
buf = {
1:'I', 5:'V', 10:'X', 50:'L', 100:'C',
500:'D', 1000:'M', 4:'IV', 9:'IX',
40:'XL', 90:'XC', 400:'CD', 900:'CM'}
d_keys = list(buf.keys())
d_keys.sort(reverse = True)
if num in d_keys:
return buf[num]
res = ''
t = 0
while num != 0:
for n in d_keys:
t = num - n
if t >= 0:
res += buf[n]
num -= n
break
return res
这是第一版代码,运行时间80ms,慢得一*,观察代码,可以看见真的憨憨,创建字典时竟然乱序,后面还要排序,而且在while里每次for都是从第一个元素开始,这无疑浪费时间,从上次break的位置接着往后找他不香吗?而且再仔细想想,也没必要先判断num是否是正好对应现成的,反正in操作的原理也是循环,所以这里可以删掉。
class Solution:
def intToRoman(self, num: int) -> str:
buf = {
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'}
d_keys = list(buf.keys())
res = ''
idx = 0
size = d_keys.__len__()
while num != 0:
for i in range(idx, size):
t = num - d_keys[i]
if t >= 0:
res += buf[d_keys[i]]
num -= d_keys[i]
break
idx = i
return res
提交,运行时间64ms,还是有点慢,无奈去看了看官方题解,原来我前面的思路是贪心算法的思路,这我还真没想到。
官方题解的代码给了我灵感,原来可以这样写:
class Solution:
def intToRoman(self, num: int) -> str:
buf = {
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'}
res = ""
for n in buf.keys():
if num == 0:
break
quotient, num = divmod(num, n)
res += buf[n] * quotient
return res
这样写的好处就在于一遍循环就能解决问题,那58来说,前面的代码在执行到num = 3时,会做3次for循环,而这3次循环都都几乎是同样的操作,效率极低,而这里用到的字符串“乘法”操作,在这种情况下就极大的提高效率,真香!
class Solution {
public:
string intToRoman(int num) {
string res;
/*map > dict = {
{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"}};
for(auto &t : dict)
{
if(num == 0)
{
break;
}
while(num >= t.first)
{
num -= t.first;
res += dict[t.first];
}
}*/
//更快的解法
int key[] = {
1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
string values[] = {
"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
for(int i = 0; i < 13; i++)
{
if(num == 0)
{
break;
}
while(num >= key[i])
{
num -= key[i];
res += values[i];
}
}
return res;
}
};
注释掉的地方是用的map,运行时间32ms,只比python快了20ms,也许是创建map比较耗时的原因吧,我觉得还能更快,所以把键值对拆开成两个数组,这样下来运行时间是12ms。
还有就是,就算给map的初值是按顺序写的,但map内部实现还是无序,因此还要在map的类型说明里加上greater参数,指明按int(这里就是键)降序排序。我第一次没加这个参数,结果答案全是“IIIIIIII…”,看了其他大佬的博客才明白,学到了学到了。