字符 | 数值 |
I | 1 |
V | 5 |
X | 10 |
L | 50 |
C | 100 |
D | 500 |
M | 1000 |
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 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.
02 . 思路分析这道题整体看起来有明确的规则,只需要梳理清楚罗马数字的表示规律即可,那我们现在就开始发现规律:跟我们常用的阿拉伯数字表示十进制数字相同,罗马数字也通过十进制来表示,只是比我们的 0 1 2 3 4 5 的阿拉伯数字复杂一些,而且每一位用的是不同的字母表示。我们先把 0 - 9 的罗马数字拿出来找规律:0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
空 | I | II | III | IV | V | VI | VII | VIII | IX |
- 0 ~3 就是 0 到 3 个 罗马数字 ‘‘I’’ 表示
- 4 就是 5 - 1 但是减数放在了前面 既 “IV”
- 5 ~ 8 就是用 一个 罗马数字 “V” 加上 0 ~ 3 个 罗马数字 “I” 来表示
- 9 就是 10 - 1 但是减数放在了前面 既 “IX”
0 | 1 | 2 | 3 | 4 |
空 | I | II | III | IV |
5 | 6 | 7 | 8 | 9 |
V | VI | VII | VIII | IX |
余 0 ~ 3 的时候是:(“空” 或者 “V” ) 加上 余数个“I”
余 4 的时候是:“I” 加上一个 “X”
如:个位就是 I V X 三个罗马数字,十位就是 X L D 三个罗马数字, 以此类推。到目前为止我们已经发现了具体规律,我们来尝试着编写代码:
/** * @param {number} num * @return {string} */var intToRoman = function(num) { let result = [] let unit = ['I', 'V', 'X', 'L', 'C', 'D', 'M'] let index = 0 while(num){ let n = num % 10 let pre = n >= 5 ? unit[index + 1] : '' let u1 = unit[index] let u3 = unit[index + ((n >= 5) ? 2 : 1)] || '' switch (n % 5) { case 1: pre += u1 break case 2: pre += u1 + u1 break case 3: pre += u1 + u1 + u1 break case 4: pre = u1 + u3 break } result.push(pre) index += 2 num = Math.floor(num / 10) } return result.reverse().join('')};
这道题目的具体代码实现比较简单,我这里就不逐行注释了,有一个细节需要说明,就是我在处理每一位的时候是用的push()来存入结果数字,在返回答案是先reverse(),在进行数组元素连接成字符串。
原因就是对于同样的结果操作,push() + reverse() 的操作 比 unshift() 操作快一些,这应该是js的引擎实现决定的,有更深入了解的同学欢迎留言去解释一下~。
在leetcode上,为了更快的运行结果,还可以用一些预先计算并直接体现在代码上,因为本题的要求是罗马数字在1 ~ 3999的范围,所以罗马数字组合只有四种,可以直接列出来。我们看一下leetcode上这道题目前最快的代码示例:
/** * @param {number} num * @return {string} */var intToRoman = function(num) { function TurnFive(n, one, five, ten){ if(n != 0){ if(n < 4){ return one.repeat(n); } if(n == 4){ return one + five; } if(n < 9){ let times = n-5; let I = one.repeat(times) return five + I; } if(n == 9){ return one + ten; } } return ""; } let than = Math.floor(num / 1000); let hon = Math.floor(num % 1000 / 100); let ten = Math.floor(num % 1000 % 100 /10); let ge = Math.floor(num % 1000 %100 % 10); return TurnFive(than, "M", "", "") + TurnFive(hon, "C", "D", "M") + TurnFive(ten, "X", "L", "C") + TurnFive(ge, "I", "V", "X");};
这种针对具体题目的优化在一些online judge的比赛中很常见,不过它的扩展性就会弱一些, 比如 范围要求是 1 ~3999999 那需要在代码中直接列出来的内容就有点多了。所以还是发现规律,写出更通用的代码才是我们追求的。
这道题到这里就结束了,大家可以自己练练手,最后祝大家周末愉快,明天我会做一个《动态规划解题的常见题集合》,不过只有一道题会从头开始分析,剩下的题目只讲解思路和特征,也算是给大家留一个联系的机会,让大家真是的练练手。
如果你觉得文章的内容能给你带来收获,欢迎关注 + 点赞在看 + 转发,更期待你能推荐给身边的小伙伴,让我们一起来梳理前端知识!一起加油!「 往期回顾 」动态规划(DP)解积雨问题
动态规划(DP)解最大连续子序列文章涉及到源码已经在github中开源
请在公众号中发送“源码”获取代码地址
让我知道你在看