罗马数字转整数

  • 题目
  • 函数原型
  • 边界判断
  • 算法设计:查表法
  • 算法设计:模拟法



 


题目

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符          数值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000

例如, 罗马数字 2 写做 [13].罗马数字转整数_罗马数字 ,即为两个并列的 1。12 写做 [13].罗马数字转整数_罗马数字_02 ,即为 [13].罗马数字转整数_算法设计_03 。 27 写做 [13].罗马数字转整数_罗马数字_04, 即为 [13].罗马数字转整数_算法设计_05

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 [13].罗马数字转整数_查表法_06,而是 [13].罗马数字转整数_算法设计_07。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 [13].罗马数字转整数_算法设计_08。这个特殊的规则只适用于以下六种情况:

  • [13].罗马数字转整数_查表法_09 可以放在 [13].罗马数字转整数_算法设计_10 (5) 和 [13].罗马数字转整数_算法设计_11
  • [13].罗马数字转整数_算法设计_11 可以放在 [13].罗马数字转整数_查表法_13 (50) 和 [13].罗马数字转整数_算法设计_14
  • [13].罗马数字转整数_算法设计_14 可以放在 [13].罗马数字转整数_查表法_16 (500) 和 [13].罗马数字转整数_算法设计_17

给定一个罗马数字,将其转换成整数。输入确保在 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.

 


函数原型

C的函数原型:

int romanToInt(char * s){}

 


边界判断

题目里有说,输入确保在 1 到 3999 的范围内。

不过这个怎么判断呢,用户输入的是罗马数字,而不是阿拉伯数字…

你问我,我也不知道啊。

但可以对输入参数做检查。

if( s == NULL || *s == '\0')  // 指针是否为NULL
    return 0;

 


算法设计:查表法

因为只包含了 7 个数字,我们可以建一个表来映射罗马数字与阿拉伯数字之间的关系。

int map[90] = {'\0'};
// 建表, 来映射数字间的关系;大写字母的范围是 65(A)-90(Z)
字符          数值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000

建表
map['I'] = 1;
map['V'] = 5;
map['X'] = 10;
map['L'] = 50;
map['C'] = 100;
map['D'] = 500;
map['M'] = 1000;

读了示例后,发现罗马数字主要有俩种情况(从左往右看):

  • 左加:左的数字(前一个数)比右边的数字(后一个数)大、相等时,加上前一个数
  • 右减:左的数字(前一个数)比右边的数字(后一个数)小时,减去前一个数

例如,[13].罗马数字转整数_算法设计_07

[13].罗马数字转整数_查表法_19,[前一个数[13].罗马数字转整数_查表法_20] 比 [后一个数[13].罗马数字转整数_算法设计_21] 小,就要减掉前一个数[13].罗马数字转整数_查表法_20

int romanToInt(char * s){
    if( s == NULL || *s == '\0')  // 指针是否为NULL
        return 0;

	int map[90] = {'\0'};
	// 建表, 来映射数字间的关系
	map['I'] = 1;
	map['V'] = 5;
	map['X'] = 10;
	map['L'] = 50;
	map['C'] = 100;
	map['D'] = 500;
	map['M'] = 1000;

    int Roman_val = 0; 						// 定义一个变量,保存罗马数字转换后的值
    
    // 俩种情况,分别讨论
    for(int i=0; i<strlen(s); i++)         // 从左往右看
        if( map[s[i]] >= map[s[i+1]] )     // 左加(前一个数 >= 后一个数)
            Roman_val += map[s[i]];
        else							   // 右减(前一个数 < 后一个数)
            Roman_val -= map[s[i]];
            
    return Roman_val;
}

AC。

查表法的复杂度:

  • 时间复杂度:[13].罗马数字转整数_查表法_23
  • 空间复杂度:[13].罗马数字转整数_算法设计_24

查表法是使用空间换时间,这个题目需要的空间很小,毕竟只有 7 种状态。

而大部分能使用查表法的题目,可能就需要大量的空间。虽然会让时间复杂度很好,但占用内存太多了。

在时间复杂度上,查表是 [13].罗马数字转整数_算法设计_25

但是,在真实的计算机中,内存和处理器之间还有一个高速缓存,程序和数据要先从内存进入高速缓存,才能运行。

高速缓存的空间非常有限,通常只有几兆([13].罗马数字转整数_罗马数字_26),查表占用的内存空间可能是缓存容量的上千倍,这肯定是放不下的,遇到这种情况,计算机本身要进行上千次额外操作,把内存的内容往缓存倒腾。

也就是说,如果建立一个大表,虽然查表只需要做一次,但是准备工作可能要做上千次。

其实划不来,当然,也有一种补救的方法。

把一张大表拆分为几个小表,每个表查找一次,再把几次的相加,虽然查找次数多了,但占用的内存就很少的,这样就即有查表法的优点(查表时间复杂度是 [13].罗马数字转整数_算法设计_25),又补救了占用大量内存空间的缺陷。

 


算法设计:模拟法

研究【右减】的情况,前一个数比后一个数小,就要减掉前一个数。

而这个规则只适用于以下六种情况:

  • [13].罗马数字转整数_查表法_09 可以放在 [13].罗马数字转整数_算法设计_10 (5) 和 [13].罗马数字转整数_算法设计_11
  • [13].罗马数字转整数_算法设计_11 可以放在 [13].罗马数字转整数_查表法_13 (50) 和 [13].罗马数字转整数_算法设计_14
  • [13].罗马数字转整数_算法设计_14 可以放在 [13].罗马数字转整数_查表法_16 (500) 和 [13].罗马数字转整数_算法设计_17

如果第 [13].罗马数字转整数_算法设计_37 个元素是 [13].罗马数字转整数_查表法_20,第 [13].罗马数字转整数_罗马数字_39 个元素比第 [13].罗马数字转整数_算法设计_37 个元素大,那就只有 [13].罗马数字转整数_算法设计_41

如果第 [13].罗马数字转整数_算法设计_37 个元素是 [13].罗马数字转整数_查表法_43,第 [13].罗马数字转整数_罗马数字_39 个元素比第 [13].罗马数字转整数_算法设计_37 个元素大,那就只有 [13].罗马数字转整数_罗马数字_46

如果第 [13].罗马数字转整数_算法设计_37 个元素是 [13].罗马数字转整数_罗马数字_48,第 [13].罗马数字转整数_罗马数字_39 个元素比第 [13].罗马数字转整数_算法设计_37 个元素大,那就只有 [13].罗马数字转整数_查表法_51

int romanToInt(char * s){
    int count = 0;
	while (*s){
	    // 左加(前一个数比后一个数大)
		if (*s == 'V')         count += 5;
		else if (*s == 'L')    count += 50;
		else if (*s == 'D')    count += 500;
		else if (*s == 'M')    count += 1000;
        
        // 右减(前一个数比后一个数小,就要减掉前一个数)
		else if (*s == 'I')
			count = (*(s + 1) == 'V' || *(s + 1) == 'X') ? count - 1 : count + 1;
		else if (*s == 'X')
			count = (*(s + 1) == 'L' || *(s + 1) == 'C') ? count - 10 : count + 10;
		else if (*s == 'C')
			count = (*(s + 1) == 'D' || *(s + 1) == 'M') ? count - 100 : count + 100;
		s++;
	}
	return count;
}

过程模拟复杂度:

  • 时间复杂度:[13].罗马数字转整数_查表法_23
  • 空间复杂度:[13].罗马数字转整数_算法设计_24