题目描述
罗马数字包含以下七种字符: I, V, X, L,C,D 和 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 到 3999 的范围内。
示例 1:
输入: "III"
输出: 3
示例 2:
输入: "IV"
输出: 4
示例 3:
输入: "IX"
输出: 9
示例 4:
输入: "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/roman-to-integer
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路1
一开始从头往后遍历,每次加上当前字母表示的数字,使用一个栈来保存遍历过的字符,因此每次循环时栈顶就表示上一个数字符号,给栈一个初始符号‘#’。这样就能判断题目中的三种规则。(就是3个if块)。
因为每次都是直接加,碰到V或X那三种情况时,再看栈顶是不是I,X,C,是则分别减2,20,200,(因为上一次循环中是加了它们,这次循环需要将它们减去,又因为那三个规则,所以都分别减两倍自身数值)。
C++代码
class Solution {
public:
int romanToInt(string s) {
stack <char> cs; // char stack
map<char, int> Dict = {{'I', 1}, {'V', 5}, {'X', 10},
{'L', 50}, {'C', 100}, {'D',500}, {'M',1000}};
int res = 0;
for (char c : s)
{
if((c == 'V' || c == 'X') && !cs.empty())
{
if (cs.top() == 'I')
res -= 2;
}
else if((c == 'L' || c == 'C') && !cs.empty())
{
if (cs.top() == 'X')
res -= 20;
}
else if((c == 'D' || c == 'M') && !cs.empty())
{
if (cs.top() == 'C')
res -= 200;
}
res += Dict[c];
cs.push(c);
}
return res;
}
};
当然,又是栈,又是多个if,运行时间肯定比较长。提交,运行32ms,确实。那能不能改进呢?
思路2
上面的思路核心是循环中处理当前字符时都看一下前面一个字符是否构成那三种规则,再加上栈的使用、多个if,导致运行时间长。
那么,不用栈怎么样?把栈去掉,只用一个字符变量表示上一个处理过的字符,这样栈的空间开销和时间开销都省了。再观察代码,三个if里的判断都有栈是否为空,去掉栈当然可以无视这个,但如果还是用栈的话,可以在栈里面初始化一个字符‘#’,那就省去了判断空栈了。
提交,运行,还是30几ms,看来问题不在栈上。当然不能满足于此。看了看大佬的题解,得到了新的思路。
思路3
三个规则的本质都是相邻两个字符,左边表示的数字比右边的小。那么直接一遍循环,当前字符比后一个字符表示的数字小,就减,否则就加,又快又简单。嗯,真香。
class Solution {
public:
int romanToInt(string s) {
int buf[100] = {0};
buf['I'] = 1;
buf['V'] = 5;
buf['X'] = 10;
buf['L'] = 50;
buf['C'] = 100;
buf['D'] = 500;
buf['M'] = 1000;
int res = 0, i;
for (i = 0 ; i < s.size() ; i++)
{
if (buf[s[i]] < buf[s[i + 1]])
res -= buf[s[i]];
else
res += buf[s[i]];
}
return res;
}
};
这里最妙的就是用数组来做字典,用那几个字符的ASCII码值作为索引。这里还需要注意一个问题,循环里是从头到尾,但最后一次循环时的buf[s[i+1]]没有引起越界错误。这是因为string内部存储字符串时也是像C里面的char数组一样,在最后给一个‘\0’字符表示结尾,所以索引时没有报错。
python代码
class Solution:
def romanToInt(self, s: str) -> int:
buf = {'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000, '#':-1}
res = 0
s += '#'
for i in range(s.__len__() - 1):
if buf[s[i]] < buf[s[i + 1]]:
res -= buf[s[i]]
else:
res += buf[s[i]]
return res
python实现的话,我再字典里加上了一个‘#’符号,表示结尾,在原字符串后面加上一个‘#’,否则直接像C++那样写的话,会发生越界错误。当然,也可以不加多于的字符,同样循环次数-1,在返回语句中再做一次加法。
return res + buf[s[-1]]