LeetCode 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.


思路:

脑子里最初冒出来的是,if else....于是乎我还真傻呼呼的列出所有可能,一堆if elif,写完到最后千位的处理发现我是小猪仔~。~这写的跟算法就不沾边。 继续苦思冥想,无奈想不出处理办法,只能吸取题解大大们的经验。

看完提示,发现,我的思路也是可以行得通的,只是我没有把它转成结构处理让其简单起来。

这是件让自己觉得难过的事情Q_Q,不过经过前面题目的练习,也让自己进步很多,也明白一件事,题目还得多做,解题思路还要多想多学。

方法一:

找到罗马数和整数的规律,来自题解大大:薛定谔的猫的题解分享。

罗马数字单纯以单个数字来看是以5进制、2进制交替出现,和整数的10进制其实可以对照。
以整数的个位数举例,包含3个罗马数字 'I':1 , 'V':5 , 'X':10,
在 0 - 4 中,罗马数字分别是: --''----'I'----'II'----'III'--'IV'
在 5 - 9 中,罗马数字分别是: 'V'--'VI'--'VII'--'VIII'--'IX'
从而能够得到如下规律:
0 - 3 : 'V' * 0 + 'I' * [0 - 3] ------ 4 : 'I' + 'V'
5 - 6 : 'V' * 1 + 'I' * [0 - 3] ------ 9 : 'I' + 'X'

假设个位数字为 num ,就可以对个位数罗马数字进行编码:

luoma = ['I', 'V', 'X']
m, n = divmod(num, 5)       # 对数字num进行5进制拆分,divmod返回商和余数
result = ''
i = 0
if n == 4:                       # 先判断4和9
    result = luoma[i] + luoma[i + m + 1] + result
            #  'I'   +   'V' 或 'X'
else:
    result = luoma[i + 1] * m + luoma[i] * n + result
            # ''  或 'V'   +   'I'  *   n

以上,对于个位数的罗马数字就编码出来了,如果有十位百位只要循环就行,同时每次结束把 i 的指针向后移动2个位置。

注意: 这里因为对单个数字的判断中会调用到指针 i 中的后两位,因此需要在罗马数字列表最后预存两个 '' 空字符,本题的数字不会出现4000及以上数字,所以预存一个也可以。
这种解法的特点是:如果罗马数字出现新的字符表示4000及以上的数字,但是规律不变的前提下只需要维护罗马数字列表即可。

class Solution:
    def intToRoman(self, num):
        luoma = ['I', 'V', 'X', 'L', 'C', 'D', 'M', '']
        res = ''
        n = 0
        while True:
            m = num % 10
            i, j = divmod(m, 5)
            if j == 4:
                res = luoma[n] + luoma[n + i + 1] + res
            else:
                res = luoma[n + 1] * i + luoma[n] * j + res
            n += 2
            num /= 10
            if num == 0:
                break
        return res

方法二:

贪心算法,来自liweiwei1419大大的题解分享,感觉思路也很清晰,方法很棒。

本题“整数转罗马数字”也有类似的思想:在表示一个较大整数的时候,“罗马数字”的设计者不会让你都用
1 加起来,我们总是希望写出来的“罗马数字”的个数越少越好,以方便表示,并且这种表示方式还应该是唯一的。

思路分析

本题中,首先给出“罗马数字”与“阿拉伯数字”的对应关系如下:

罗马数字 阿拉伯数字
I 11
V 55
X 1010
L 5050
C 100100
D 500500
M 10001000

题目还给了一些特例,我们需要推导出“罗马数字”与“阿拉伯数字”其它的对应关系。为此,从头开始举例子,以发现规律:

说明:其实在题目中已经强调了一些特例,出现 4、9、40、90、400、900(4000、9000 不讨论了,题目测试用例中有说,不会超过 3999)的情况比较特殊一些,做的是减法,把它们也加入到“罗马数字”与阿拉伯数字的对应关系表中,并且按照从大到小的顺序排列

于是,"将整数转换为罗马数字"的过程,就是用上面这张表中右边的数字作为"加法因子""去分解一个整数,目的是"分解的整数个数"尽可能少,因此,对于这道问题,类似于用最少的纸币凑成一个整数,贪心算法的规则如下:

每一步都使用当前较大的罗马数字作为加法因子,最后得到罗马数字表示就是长度最少的。

class Solution(object):
    def intToRoman(self, num):
        # 把阿拉伯数字与罗马数字可能出现的所有情况和对应关系,放在两个数组中
        # 并且按照阿拉伯数字的大小降序排列,这是贪心选择思想
        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 = ''
        for i in range(len(nums)):
        	if (num >= nums[i]):
          	    res += ((num / nums[i]) * romans[i])
                    num %= nums[i]
        return res

或者构建元组来map data.

class Solution(object):

    def __init__(self):
        self._rules = (
            ('M', 1000),
            ('CM', 900), ('D', 500), ('CD', 400), ('C', 100),
            ('XC', 90), ('L', 50), ('XL', 40), ('X', 10),
            ('IX', 9), ('V', 5), ('IV', 4), ('I', 1),
        )
    def intToRoman(self, num):
        result = []
        for roman, dec in self._rules:
            if num >= dec:
                result.extend([roman] * (num / dec))
                num %= dec
        return ''.join(result)

方法三:

递归思想,这个也是可以,不过依然要列表找到对应的规律才好实现处理部分,相比较严谨清晰性而言,还是推荐上面的方法。

class Solution:
    def intToRoman(self, num):
        if num>3999 or num<1: return ''
        if num/1000>0:
            return (num//1000)*'M'+self.intToRoman(num%1000)
        elif num/500>0:
            if num/100==9: return 'CM'+self.intToRoman(num%100)
            return 'D'+self.intToRoman(num%500)
        elif num/100>0:
            if num/100 == 4:return 'CD'+self.intToRoman(num%100)
            return (num//100)*'C'+self.intToRoman(num%100)
        elif num/50>0:
            if num/10 == 9: return 'XC'+self.intToRoman(num%10)
            return 'L'+self.intToRoman(num%50)
        elif num/10>0:
            if num/10==4:return 'XL'+self.intToRoman(num%10)
            return (num/10)*'X'+self.intToRoman(num%10)
        elif num/5>0:
            if num == 9: return 'IX'
            return 'V'+self.intToRoman(num%5)
        else:
            if num==4: return 'IV'
            return num*'I'

执行用时 :56 ms, 在所有 Python 提交中击败了77.05%的用户

内存消耗 :12.6 MB, 在所有 Python 提交中击败了20.00%的用户

原文地址:https://www.cnblogs.com/xiaoqiangink/p/12973519.html