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

相关推荐
还是鼠鼠1 小时前
Node.js 跨域 CORS 简单请求与预检请求的介绍
运维·服务器·vscode·中间件·node.js·express
我命由我123452 小时前
35.Java线程池(线程池概述、线程池的架构、线程池的种类与创建、线程池的底层原理、线程池的工作流程、线程池的拒绝策略、自定义线程池)
java·服务器·开发语言·jvm·后端·架构·java-ee
old_iron5 小时前
vim定位有问题的脚本/插件的一般方法
linux·编辑器·vim
爱知菜7 小时前
Windows安装Docker Desktop(WSL2模式)和Docker Pull网络问题解决
运维·docker·容器
做测试的小薄7 小时前
Nginx 命令大全:Linux 与 Windows 系统的全面解析
linux·自动化测试·windows·nginx·环境部署
影龙帝皖8 小时前
Linux网络之局域网yum仓库与apt的实现
linux·服务器·网络
月下雨(Moonlit Rain)8 小时前
Docker
运维·docker·容器
碎忆8 小时前
在VMware中安装虚拟机Ubuntu
linux·ubuntu
农民小飞侠8 小时前
ubuntu 安装pyllama教程
linux·python·ubuntu
打工人你好9 小时前
UNIX域套接字(Unix Domain Sockets, UDS) 的两种接口
服务器·unix