[13].罗马数字转整数

 


题目

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

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

例如, 罗马数字 2 写做 I I II II ,即为两个并列的 1。12 写做 X I I XII XII ,即为 X + I I X + II X+II 。 27 写做 X X V I I XXVII XXVII, 即为 X X + V + I I XX + V + II XX+V+II

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 I I I I IIII IIII,而是 I V IV IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 I X IX IX。这个特殊的规则只适用于以下六种情况:

  • I I I 可以放在 V V V (5) 和 X X X (10) 的左边,来表示 4 和 9。
  • X X X 可以放在 L L L (50) 和 C C C (100) 的左边,来表示 40 和 90。
  • C C C 可以放在 D D D (500) 和 M M 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.

 


函数原型

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;

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

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

例如, I V IV IV

I < V I < V I<V,[前一个数 I I I] 比 [后一个数 V V V] 小,就要减掉前一个数 I I I

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。

查表法的复杂度:

  • 时间复杂度: Θ ( n ) \Theta(n) Θ(n)
  • 空间复杂度: Θ ( 1 ) \Theta(1) Θ(1)

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

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

在时间复杂度上,查表是 Θ ( 1 ) \Theta(1) Θ(1)

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

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

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

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

把一张大表拆分为几个小表,每个表查找一次,再把几次的相加,虽然查找次数多了,但占用的内存就很少的,这样就即有查表法的优点(查表时间复杂度是 Θ ( 1 ) \Theta(1) Θ(1)),又补救了占用大量内存空间的缺陷。

 


算法设计:模拟法

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

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

  • I I I 可以放在 V V V (5) 和 X X X (10) 的左边,来表示 4 和 9。
  • X X X 可以放在 L L L (50) 和 C C C (100) 的左边,来表示 40 和 90。
  • C C C 可以放在 D D D (500) 和 M M M (1000) 的左边,来表示 400 和 900。

如果第 i i i 个元素是 I I I,第 i + 1 i+1 i+1 个元素比第 i i i 个元素大,那就只有 V 、 X V、X VX

如果第 i i i 个元素是 X X X,第 i + 1 i+1 i+1 个元素比第 i i i 个元素大,那就只有 L 、 C L、C LC

如果第 i i i 个元素是 C C C,第 i + 1 i+1 i+1 个元素比第 i i i 个元素大,那就只有 D 、 M D、M DM

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;
}

过程模拟复杂度:

  • 时间复杂度: Θ ( n ) \Theta(n) Θ(n)
  • 空间复杂度: Θ ( 1 ) \Theta(1) Θ(1)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值