文章目录
罗马数字与整数之间的转换
转换规则
由于Leetcode上转换的数字就是在1~3999中,所以只给出一部分的罗马数字转换表:
七个不同的符号代表罗马数字,其值如下:
符号 | 值 |
---|---|
I | 1 |
V | 5 |
X | 10 |
L | 50 |
C | 100 |
D | 500 |
M | 1000 |
罗马数字是通过添加从最高到最低的小数位值转换而形成的。将小数位转换为罗马数字有以下规则:
1.如果该值不是以 4 或 9 开头,请选择可以从输入中减去的最大值的符号,将该符号附加到结果,减去其值,然后将其余部分转换为罗马数字。
2.如果该值以 4 或 9 开头,使用 减法形式,表示从以下符号中减去一个符号,例如 4 是 5 (V) 减 1 (I): IV ,9 是 10 (X) 减 1 (I):IX。仅使用以下减法形式:4 (IV),9 (IX),40 (XL),90 (XC),400 (CD) 和 900 (CM)。
3.只有 10 的次方(I, X, C, M)最多可以连续附加 3 次以代表 10 的倍数。你不能多次附加 5 (V),50 (L) 或 500 (D)。如果需要将符号附加4次,请使用 减法形式。
了解上述规则后,就可以来操作下面这些要求了。
罗马数字转整数
原题链接:Leetcode 13
我们先来做罗马数字转整数的题,这个比整数转罗马数字会简单一些。
在了解了罗马数字的要求后,我们知道:对于罗马数字,其实就是一堆的数位符号相加而成。只不过对于4或9开头的数位是需要使用减法形式的。
解题思路
比如罗马数字:"MCMXCIV"转化为整数是1994,我们如何操作呢?
我们先把每个位置上的罗马数字转化为对应的整数:
即1000 100 1000 10 100 1 5
但是这样子直接相加肯定就不对的,因为罗马数字代表的含义就是把个十百千位的数字从左往右、从高位到低位去写的。随意正常来讲,罗马数字前面的符号应该是大于等于后面的。除非是4和9开头的数字需要使用减法。
我们定义一个变量sum用来接收转化后的值。但是在此之前,得先写一个函数或者表,使得罗马字符和数字能有对应关系。由于当前知识有限,所以就先使用函数进行映射:
cpp
int Transit(char a){
switch(a){
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
}
return 0;
}
所以我们对转化后的整数序列进行操作:
对于序列1000 100 1000 10 100 1 5,定义一个遍历器int i = 0。
从第一个位置开始判断,如果当前的数字大于等于后面一个位置的数字,那就直接加在sum中,然后i向后走一步。
反之则需要将后一个位置的数字与当前数字之差的值加入到sum中,然后i向后走两步。
直到走到i为字符串中的‘\0’位置就可以退出循环了。
这里有人会担心如果走到最后一个位置了,那么后面一个位置是‘\0’,那还能比较吗?答案是可以的,因为后面‘\0’这个字符映射后的值我们写成0,是一定比最后一个罗马字符的值小的。
代码实现:
cpp
class Solution {
public:
int Transit(char a){
switch(a){
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
}
return 0;
}
int romanToInt(string s) {
int sum = 0, i = 0;
while(i < s.size()){
if(Transit(s[i]) >= Transit(s[i + 1])){
sum += Transit(s[i]);
++i;
}
else{
sum += Transit(s[i + 1]) - Transit(s[i]);
i += 2;
}
}
return sum;
}
};
这样子就很简单的完成转化了:
时间复杂度和空间复杂度
空间复杂度很明显是O(1),因为没有开辟任何其余的空间进行string的操作。
时间复杂度是O(N),因为只需要遍历一次string就可以完成转换。
整数转罗马数字
那如果反过来呢?如何将一个整数转化为罗马字符序列呢?
原题链接:Leetcode 12
解题思路
一样的,我们还是需要右一个映射关系:
cpp
char Transit(int val) {
switch (val) {
case 1: return 'I';
case 5: return 'V';
case 10: return 'X';
case 50: return 'L';
case 100: return 'C';
case 500: return 'D';
case 1000: return 'M';
}
return '\0';
}
根刚刚的关系就是倒过来而已。
假设还是以1994这个数为例,该如何转化为序列"MCMXCIV"呢?
我们需要紧抓定义,整数转化为罗马数字就是不断地拆数位,将每个数位转化为字符即可。
对于1994 = 1000 + 900 + 90 + 4,也就是说,我们需要分别对这四个数字进行操作。
1.拆数位
我们得先考虑如何拆出数位:
千位数1是可以通过(1994 / 1000)得来,1000就是(1994 / 1000) * 1000
百位数9是可以通过(994 / 100)得来,900就是(994 / 100) * 100
十位数9是可以通过(94 / 10)得来,90就是(94 / 10) * 10
个位数4是可以通过(4 / 1)得来,4就是(4 / 1) * 1
本质上就是对该位得权重进行操作。
于此我们就发现一个规律,我们可以求出一个数字一共有几位数,记录在int count中,然后最高位的权重应该是10count - 1,所以我们让count的值变为最高位的权重。
后每一次就是执行上述规律的操作,然后让权重缩小十倍,直到权重为0的时候即结束拆数位
为了防止主功能代码太过冗杂,将求取数字的位数的功能也交给一个函数来做:
cpp
int DigitNum(int num) {
int count = 0;
while (num) {
count++;
num /= 10;
}
return count;
}
2.对小数位进行转换
然后拆分出一系列小数位后,就可以进行转换了。
为了方便转换,定义两个变量:int TransitNum和int TransitNumHead分别指向需要被转化的数字和这个数字的开头数。如(1000,TransitNum是1000,而TransitNumHead就是1)。
后续为了更简单地阐述过程,将TransitNum称为转换数,TransitNumHead称为开头数。
题目要求返回一个string,所以要定义一个string s,将转换后的序列插入s中。
我们需要重点分析一下:
1.考虑到会出现减法的形式,所以先判断一下,如果开头数字是4或9,那就执行减法操作:
对于9这个数字就是10 - 1,4就是5 - 1。
对于90这个数字就是100 - 1,40就是50 - 10。
...
发现被减的数字正好是对应数位的权重,所以先执行s += Transit(count);
然后被减数是当前转换数 + 当前权重,所以再执行s += Transit(TransitNum + count);
2.开头数为0
如果开头为0是不需要担心的,不需要转化,已经在前面的数位转化过了。
3.开头数为1或者5
在开头数是1或者5的情况下,此时的转化数是可以在映射表中找到直接对应的符号的,所以直接执行操作:s += Transit(TransitNum);
4.剩下的开头数为 2 3 6 7 8
如果开头数字为2或者3,那么还是很好办的,直接插入对应开头数次的权重即可。
2就插入2次1对应的符号,3就是3次
20就插入2次10对应的符号,30就是三次
...
如果开头数为6 7 8也简单。只不过需要先插入一次5 * 当前权重对应的符号,然后再让开头数减掉5,执行和上面一样的操作就好。
6 = 5 + 1,所以先插入5对应符号再插入1次1对应符号。
7 = 5 + 2,所以先插入5对应符号再插入2次1对应符号。
8 = 5 + 3,所以先插入5对应符号再插入3次1对应符号。
...
这样子就可以操作完成了。
代码实现
cpp
class Solution {
public:
int DigitNum(int num) {
int count = 0;
while (num) {
count++;
num /= 10;
}
return count;
}
char Transit(int val) {
switch (val) {
case 1: return 'I';
case 5: return 'V';
case 10: return 'X';
case 50: return 'L';
case 100: return 'C';
case 500: return 'D';
case 1000: return 'M';
}
return '\0';
}
string intToRoman(int num) {
int count = (int)pow(10, DigitNum(num) - 1);
string s;
while (num) {
int TransitNum = (num / count) * count;
int TransitNumHead = num / count;
if (TransitNumHead == 4 || TransitNumHead == 9) {
s += Transit(count);
s += Transit(TransitNum + count);
}
else {
if (TransitNumHead == 5 || TransitNumHead == 1)
s += Transit(TransitNum);
else {
if (TransitNumHead > 5) {
s += Transit(5 * count);
TransitNumHead -= 5;
}
while (TransitNumHead--)
s += Transit(count);
}
}
num %= count;
count /= 10;
}
return s;
}
};
注意每执行一次数位的操作后,权重要缩小,被转换的数字由于最高位已经被转换了,所以需要调整(num %= count操作)。
时间复杂度和空间复杂度
空间复杂度为O(N),因为开辟了空间存储字符串。
时间复杂度为O(N),因为只需要遍历整数的数位次就可以完成。