• 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%的用户

  • 相关阅读:
    redisLock redis分布式锁
    Mabitis中的#与$符号区别及用法介绍
    pring Scheduler定时器原理分析
    SpringBoot几种定时任务
    线程池的理解
    JVM的方法区和永久带是什么关系
    JVM老年代和新生代的比例
    IO 与 NIO
    对mysql乐观锁、悲观锁、共享锁、排它锁、行锁、表锁概念的理解
    TCP滑动窗口控制流量的原理
  • 原文地址:https://www.cnblogs.com/xiaoqiangink/p/12973519.html
Copyright © 2020-2023  润新知