tags:
- 简单
- 完成
- 数组
- 哈希表
flag: green
style: summer
date: ‘2019-6-5’
13.罗马数字转整数
一 、题目
罗马数字包含以下七种字符: 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.
二、解法
- 常规解法:
第一次遍历一个字符把符合值加到结果里,第二次遍历两个字符从结果中减去符合值。
class Solution {
public int romanToInt(String s) {
int result = 0;
for(int i = 0; i < s.length(); i++) {
switch(s.charAt(i)) {
case 'I':
result += 1;
break;
case 'V':
result += 5;
break;
case 'X':
result += 10;
break;
case 'L':
result += 50;
break;
case 'C':
result += 100;
break;
case 'D':
result += 500;
break;
case 'M':
result += 1000;
break;
}
}
for(int j = 0; j < s.length() - 1; j++) {
switch("" + s.charAt(j) + s.charAt(j+1)) {
case "IV":
case "IX":
result -= 2;
break;
case "XL":
case "XC":
result -= 20;
break;
case "CD":
case "CM":
result -= 200;
break;
}
}
return result;
}
}
- 哈希表解法:
最基础情况
试想一下,假如没有那几条特殊的规则,就是最简单的文字转换。那像XIV这样就可以直接转换为10+1+5=16
注意:这是我们的整体思路,即字符的转换。
特殊情况
现在我们得知有三大种、六小种特殊情况,即:
I(1) 和 V(5) 或 X(10) 组成 IV(4) 和 IX(9)
X(10) 和 L(50) 或 C(100) 组成 XL(40) 和 XC(90)
C(100) 和 D(500) 或 M(1000) 组成 CD(400) 和 CM(900)
我把他们写成这样的形式相信大家一眼就可以看出来这其中的规律:当右边的数是左边的数5倍或10倍的时候,左边的数就去相反数与其相加即可。
举个例子,IV其中V(5)是I(1)的五倍,那么这时候两者在一起,只需将I取值为相反数-1,然后再相加即可。那么XIV也就可以写成10±1+5=14(可以看看后记哦)
需要注意的地方
因为需要用到前一个和后一个值做比较,所以需要考虑下标是否越界的问题,因此在遍历字符串时要在倒数第二个地方结束,即i只能取值0 <= i < s.length()-1。最后再加上字符串最后一位的值。
class Solution {
public int romanToInt(String s) {
HashMap<Character,Integer> map=new HashMap<Character,Integer>();//使用map把他们对应存储起来
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 res=0; //存放结果
int current=0; //第一个值
int next=0; //后一个值
for(int i=0;i<s.length()-1;++i){ //注意这里的s.length()-1是为了防止下标越界
current=map.get(s.charAt(i));
next=map.get(s.charAt(i+1));
if(next/current==5||next/current==10){ //如果满足后一个是前一个5倍或10倍
res-=map.get(s.charAt(i)); //结果减掉当前值(相当于加了相反数)
}else{
res+=map.get(s.charAt(i)); //否则加上当前值
}
}
res+=map.get(s.charAt(s.length()-1)); //最后再加上字符串末尾的值
return res;
}
}
后记
这里能有人会问,那万一是 V(5) 和 L(50) 放在一起呢?这也是十倍的关系啊!说实话一开始我也没有想到这一点,就是在写这篇文章的时候才发现的。但事实上这是不符合题目假设的,因为题目说明除了6种情况外,小的数必须在大的数右边。
但本着杠精的思想,我提交了测试,发现LeetCode返回的结果是VL=45,这让我很惊讶!于是我大胆尝试测试了一个V(5)和C(100)组合是什么结果,结果更让我惊讶的是VC=95,但按照我之前写的思路,应该是VC=105。这样看来我萌生了一个大胆的想法,只要是左边的数比右边的数小,那么左边的数就要变成相反数,基于这一想法我修改代码,再次提交。再次惊讶,竟然运行时间比之前减小了20%。咱也不知道为啥,咱也不敢问。多半是判断条件减少了所以快了吧。不知道这种小数在大数左边的到底该怎么判定,不该返回0或者错误吗??
这是修改后的代码(就改了个判断条件)
class Solution {
public int romanToInt(String s) {
HashMap<Character,Integer> map=new HashMap<Character,Integer>();
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 res=0;
int current=0;
int next=0;
for(int i=0;i<s.length()-1;++i){
current=map.get(s.charAt(i));
next=map.get(s.charAt(i+1));
if(next>current){
res-=map.get(s.charAt(i));
}else{
res+=map.get(s.charAt(i));
}
}
res+=map.get(s.charAt(s.length()-1));
return res;
}
}
欢迎大家讨论这个VC的问题
作者:godk-1
链接:https://leetcode-cn.com/problems/two-sum/solution/zhua-zhu-guan-jian-qu-bie-xiang-xi-fen-xi-ti-mu-by/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。