LeetCode 第 12 题:“整数转罗马数字”题解
题解地址:贪心算法(Python 代码、Java 代码)。
说明:文本首发在力扣的题解版块,更新也会在第 1 时间在上面的网站中更新,这篇文章只是上面的文章的一个快照,您可以点击上面的链接看到其他网友对本文的评论。
传送门:12. 整数转罗马数字。
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。 X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。 给定一个整数,将其转为罗马数字。输入确保在 1 到 3999 的范围内。
示例 1:
输入: 3 输出: "III" 示例 2:
输入: 4 输出: "IV" 示例 3:
输入: 9 输出: "IX" 示例 4:
输入: 58 输出: "LVIII" 解释: L = 50, V = 5, III = 3. 示例 5:
输入: 1994 输出: "MCMXCIV" 解释: M = 1000, CM = 900, XC = 90, IV = 4.
贪心算法(Python 代码、Java 代码)
特别注意:本文介绍的是“贪心算法”,但这种贪心选择性质的成立是有一定条件的。跟这里的“候选”数值有关,如果“候选”数值是另一些数字,“贪心算法”很可能就失效了,此时可能就要应用“动态规划”来解决。这一点在本文并不展开,本人也要进行相应的学习以后,才能解释清楚这个问题。也希望能解释清楚这件事情的朋友能够把您的理解分享给大家。
在生活中的例子:
在以前还使用现金购物的年代,如果我们不想让对方找钱,付款的时候我们会尽量拿面值大的纸币给对方,这样才会使得我们给对方的纸币张数最少,对方点钱的时候(因为对方要检验你给的钱对不对)也最方便。最极端的一种情况,你要是都拿零钱去买一个比较贵重的东西,我相信没有人是很高兴收到你的钱的,因为他们点钱费劲。
“整数转罗马数字”与上面的问题是一模一样的思想:在表示一个较大整数的时候,“罗马数字”不会让你都用 加起来,肯定是写出来的“罗马数字”的个数越少越好。
于这道问题,“纸币”有哪些,并不是只有题目中给出的对应关系,根据规则,还可以得到一些“纸币”的面值,不过都是有限个“纸币”,很快就能罗列出来。
于是解这道题的思路就出来了:
- “纸币”有哪些?
- 一个整数如何做“加法因子”的分解?
思路分析:
从题目中给出的“罗马数字”与阿拉伯数字的对应关系,和翻译规则,我们需要推导出“罗马数字”还有哪些组合。
罗马数字 | 阿拉伯数字 |
---|---|
I | |
V | |
X | |
L | |
C | |
D | |
M |
为此,我们要举例子帮助我们发现规律:
阿拉伯数字 | 转换规则 | 罗马数字 |
---|---|---|
直接看表 | I | |
,相同数字简单叠加 | II | |
,相同数字简单叠加 | III | |
不能写成 , 应该看做 | IV | |
直接看表 | V | |
6 | ,大数字在前,小数字在后 | VI |
7 | ,大数字在前,小数字在后,相同数字简单叠加 | VII |
,大数字在前,小数字在后,相同数字简单叠加 | VIII | |
不能写成 , 应该看做 | IX | |
直接看表 | X |
于是,我们发现(其实在题目中已经强调了这些特例),出现 、、、、、 (、 不讨论了,题目测试用例中有说,不会超过 )的情况比较特殊一些,做的是减法,把它们也加入到“罗马数字”与阿拉伯数字的对应关系表中,并且按照从大到小的顺序排列。
罗马数字 | 阿拉伯数字 |
---|---|
M | |
CM | |
D | |
CD | |
C | |
XC | |
L | |
XL | |
X | |
IX | |
V | |
IV | |
I |
于是,“将整数转换为阿拉伯数字”的过程,就是我们用上面这张表中右边的数字作为“加法因子”去分解一个整数,并且分解的整数个数越少越好,即尽量使用靠前的数字,这可以认为是一种贪心法则。
参考代码:
Python 代码:
class Solution:
def intToRoman(self, num: int) -> str:
# 把阿拉伯数字与罗马数字可能出现的所有情况和对应关系,放在两个数组中
# 并且按照阿拉伯数字的大小降序排列,这是贪心选择思想
nums = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
romans = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
index = 0
res = ''
while index < 13:
# 注意:这里是等于号,表示尽量使用大的"面值"
while num >= nums[index]:
res += romans[index]
num -= nums[index]
index += 1
return res
Java 代码:
public class Solution {
public String intToRoman(int num) {
// 把阿拉伯数字与罗马数字可能出现的所有情况和对应关系,放在两个数组中
// 并且按照阿拉伯数字的大小降序排列,这是贪心选择思想
int[] nums = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
String[] romans = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
StringBuilder stringBuilder = new StringBuilder();
int index = 0;
while (index < 13) {
// 特别注意:这里是等号
while (num >= nums[index]) {
// 注意:这里是等于号,表示尽量使用大的"面值"
stringBuilder.append(romans[index] + " ");
num -= nums[index];
}
index++;
}
return stringBuilder.toString();
}
}
复杂度分析:
- 时间复杂度:,虽然看起来是两层循环,但是外层循环的次数最多 ,内层循环的此时其实也是有限次的,综合一下,时间复杂度是 。
- 空间复杂度:,这里使用了两个辅助数字,空间都为 ,还有常数个变量,故空间复杂度是 。