string 找出所有数字 index_发现规律,解决整数转罗马数字

68ceba8c99f9f9a6279b6ecc3970af6d.gif

898df2a218a2e3b3ff9e06f34ee3c96b.png

嗨,各位! 我们又准时见面了,即将迎来难得的周末时光,我们今天来一道相对简单的题目逻辑梳理的题目,原定的动态规划的常见题型我们放在周末进行更新。 话不多说,我们先看题目:  01 . 题目 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。  
字符数值
I1
V5
X10
L50
C100
D500
M1000
例如, 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.
  02 . 思路分析 这道题整体看起来有明确的规则,只需要梳理清楚罗马数字的表示规律即可,那我们现在就开始发现规律: 跟我们常用的阿拉伯数字表示十进制数字相同,罗马数字也通过十进制来表示,只是比我们的 0 1 2 3 4 5 的阿拉伯数字复杂一些,而且每一位用的是不同的字母表示。 我们先把 0 - 9 的罗马数字拿出来找规律:
0123456789
IIIIIIIVVVIVIIVIIIIX
我们发现了如下规律:
  • 0 ~3  就是 0 到 3 个 罗马数字 ‘‘I’’ 表示
  • 4 就是 5 - 1 但是减数放在了前面 既 “IV”
  • 5 ~ 8 就是用 一个 罗马数字 “V” 加上 0 ~ 3 个 罗马数字 “I” 来表示
  • 9 就是 10 - 1 但是减数放在了前面 既 “IX”
如果我们把每一位数字分为两组 再去发现规律:
01234
IIIIIIIV
56789
VVIVIIVIIIIX
当 0 ~ 9 我们分别对5取余时,我们发现了更通用的一个规律:
  • 余 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)解最大连续子序列

4dfba5d518373f711b2e346677851bdd.png

文章涉及到源码已经在github中开源

请在公众号中发送“源码”获取代码地址

让我知道你在看

a7dd662c60e9063684a227ad68aeaf7c.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值