Leetcode12 13——罗马数字与整数之间的转换

文章目录

罗马数字与整数之间的转换

转换规则

由于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 TransitNumint 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),因为只需要遍历整数的数位次就可以完成。

相关推荐
lsnm7 分钟前
【LINUX网络】IP——网络层
linux·服务器·网络·c++·网络协议·tcp/ip
全糖去冰吃不了苦7 分钟前
ELK 集群部署实战
运维·jenkins
不掰手腕20 分钟前
在UnionTech OS Server 20 (统信UOS服务器版) 上离线安装PostgreSQL (pgsql) 数据库
linux·数据库·postgresql
Lynnxiaowen1 小时前
今天继续昨天的正则表达式进行学习
linux·运维·学习·正则表达式·云计算·bash
努力学习的小廉1 小时前
深入了解linux系统—— POSIX信号量
linux·运维·服务器
刘一说1 小时前
CentOS部署ELK Stack完整指南
linux·elk·centos
从零开始的ops生活1 小时前
【Day 50 】Linux-nginx反向代理与负载均衡
linux·nginx
IT成长日记1 小时前
【Linux基础】Linux系统配置IP详解:从入门到精通
linux·运维·tcp/ip·ip地址配置
夜无霄1 小时前
安卓逆向(一)Ubuntu环境配置
linux·运维·爬虫·ubuntu
田野里的雨1 小时前
manticore离线安装(Ubuntu )
linux·运维·服务器·全文检索