罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
例如, 罗马数字 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.
提示:
- 题目所给测试用例皆符合罗马数字书写规则,不会出现跨位等情况。
- IC 和 IM 这样的例子并不符合题目要求,49 应该写作 XLIX,999 应该写作 CMXCIX 。
- 关于罗马数字的详尽书写规则,可以参考 罗马数字 - Mathematics 。
思路一:
1 class Solution { 2 public int romanToInt(String s) { 3 4 // 把几个字符的值存入hashMap, 几个特殊的值也存入hashmap 5 HashMap<String, Integer> map = new HashMap<>(); 6 map.put("I", 1); 7 map.put("V", 5); 8 map.put("X", 10); 9 map.put("L", 50); 10 map.put("C", 100); 11 map.put("D", 500); 12 map.put("M", 1000); 13 map.put("IV", 4); 14 map.put("IX", 9); 15 map.put("XL", 40); 16 map.put("XC", 90); 17 map.put("CM", 900); 18 map.put("CD", 400); 19 20 int res = 0; 21 int len = s.length(); 22 // 遍历字符串 23 for(int i = 0; i < len; i++){ 24 // 如果下个字符比当前字符小,直接加上当前字符的值 25 if(i + 1 >= len || map.get(s.charAt(i) + "") >= map.get(s.charAt(i+1) + "")){ 26 res += map.get(s.charAt(i) + ""); 27 }else{ 28 // 如果下个字符比当前字符大,判断这两个连续的字符形成的子串是否是特殊子串 29 String temp = s.substring(i, i+2); 30 if(map.containsKey(temp)){ // 如果是特殊子串,直接加上相对应的值,位置后移一位 31 res += map.get(temp); 32 i++; 33 }else{ // 如果不是,直接加上当前字符的值 34 res += map.get(s.charAt(i)); 35 } 36 } 37 } 38 return res; 39 } 40 }
复杂度分析:
时间复杂度:遍历了整个字符串,所以时间复杂度为O(n)。
空间复杂度:O(1)。所有变量或者集合的大小都是常数级别的,所以空间复杂度为O(1)。
思路二:
判断当前字符和下个字符形成的子串是否是特殊子串,如果是直接加上对应特殊子串的值,索引后移一位,否则直接就是单个字符对应的值,直接加上当前字符对应的值即可。
1 class Solution { 2 public int romanToInt(String s) { 3 4 // 把几个字符的值存入hashMap, 几个特殊的值也存入hashmap 5 HashMap<String, Integer> map = new HashMap<>(); 6 map.put("I", 1); 7 map.put("V", 5); 8 map.put("X", 10); 9 map.put("L", 50); 10 map.put("C", 100); 11 map.put("D", 500); 12 map.put("M", 1000); 13 map.put("IV", 4); 14 map.put("IX", 9); 15 map.put("XL", 40); 16 map.put("XC", 90); 17 map.put("CM", 900); 18 map.put("CD", 400); 19 20 int res = 0; 21 int len = s.length(); 22 // 遍历字符串 23 for(int i = 0; i < len; i++){ 24 // 如果下个字符比当前字符小,直接加上当前字符的值 25 String cur = s.charAt(i) + ""; 26 if(i + 1 >= len){ 27 res += map.get(cur); 28 continue; 29 } 30 String next = s.charAt(i+1) + ""; 31 String temp = s.substring(i, i+2); 32 // 判断这两个连续的字符形成的子串是否是特殊子串 33 if(map.containsKey(temp)){ // 如果是特殊子串,直接加上相对应的值,位置后移一位 34 res += map.get(temp); 35 i++; 36 }else{ // 如果不是,直接加上当前字符的值 37 res += map.get(s.charAt(i) + ""); 38 } 39 } 40 return res; 41 } 42 }
leetcode 执行用时:18 ms, 在所有 Java 提交中击败了5.46%的用户
内存消耗:39 MB, 在所有 Java 提交中击败了47.09%的用户
思路三:对思路一的改进
思路一之所以需要在下个字符比当前字符分值大时,判断这两个连续的字符形成的子串是否是特殊子串后加上特殊子串的分值;而非直接减去当前字符的分值,是因为担心有IC, IM等子串的出现,但是观察题目之后,发现这些子串是不合法的罗马数字,而题目要求是输入合法的罗马数字,所以不用考虑这些子串。所以把原来判断这两个连续的字符形成的子串是否是特殊子串后加上特殊子串的分值改成直接减去当前字符的分值。
1 class Solution { 2 public int romanToInt(String s) { 3 4 // 把几个字符的值存入hashMap, 几个特殊的值也存入hashmap 5 HashMap<Character, Integer> map = new HashMap<>(); 6 map.put('I', 1); 7 map.put('V', 5); 8 map.put('X', 10); 9 map.put('L', 50); 10 map.put('C', 100); 11 map.put('D', 500); 12 map.put('M', 1000); 13 14 int res = 0; 15 int len = s.length(); 16 // 遍历字符串 17 for(int i = 0; i < len; i++){ 18 19 Character cur = s.charAt(i); 20 // 如果当前字符是最后一个字符,直接加上当前字符的值 21 if(i + 1 >= len){ 22 res += map.get(cur); 23 continue; 24 } 25 Character next = s.charAt(i + 1); 26 // 如果是下个字符的分值大于当前字符的分值,说明是特殊情况,直接减去当前字符的分值 27 if(map.get(cur) < map.get(next)){ 28 res -= map.get(cur); 29 }else{ // 如果不是特殊情况,直接加上当前字符的值 30 res += map.get(cur); 31 } 32 } 33 return res; 34 } 35 }
复杂度分析:
时间复杂度:遍历了整个字符串,所以时间复杂度为O(n)。
空间复杂度:O(1)。所有变量或者集合的大小都是常数级别的,所以空间复杂度为O(1)。
思路四:对思路二的改进
使用switch 表达式代替HashMap的键值对映射
1 class Solution { 2 public int romanToInt(String s) { 3 4 int res = 0; 5 int len = s.length(); 6 int preScore = getScore(s.charAt(0)); 7 8 // 遍历字符串 9 for(int i = 1; i < len; i++){ 10 int curScore = getScore(s.charAt(i)); 11 // 如果是上个字符的分值小于当前字符的分值,说明是特殊情况,直接减去当前字符的分值 12 if(preScore < curScore){ 13 res -= preScore; 14 }else{ // 如果不是特殊情况,直接加上当前字符的值 15 res += preScore; 16 } 17 preScore = curScore; 18 } 19 res += preScore; 20 return res; 21 } 22 23 public int getScore(char ch){ 24 switch(ch){ 25 case 'I': return 1; 26 case 'V': return 5; 27 case 'X': return 10; 28 case 'L': return 50; 29 case 'C': return 100; 30 case 'D': return 500; 31 case 'M': return 1000; 32 default: return 0; 33 } 34 } 35 }
leetcode执行用时:4 ms, 在所有 Java 提交中击败了99.98%的用户, 避免对HashMap这种集合的操作,效率再次上升。