Algorithm之路十二:Integer to Roman

来源:互联网 发布:南京办公软件培训班 编辑:程序博客网 时间:2024/06/05 03:48

题目:

给出一个整数,返回其对应的罗马数字,整数的范围是1-3999。

举例:

1   --->   "I"

1321   --->   "MCCCXXI"

思路:

罗马数字是阿拉伯数字传入之前使用的一种数码。罗马数字采用七个罗马字母作数字、即Ⅰ(1)、X(10)、C(100)、M(1000)、V(5)、L(50)、D(500)。记数的方法:
  1. 相同的数字连写,所表示的数等于这些数字相加得到的数,如 Ⅲ=3;
  2. 小的数字在大的数字的右边,所表示的数等于这些数字相加得到的数,如 Ⅷ=8、Ⅻ=12;
  3. 小的数字(限于 Ⅰ、X 和 C)在大的数字的左边,所表示的数等于大数减小数得到的数,如 Ⅳ=4、Ⅸ=9;

还有一条规矩是不能出现四个或四个以上连续的相同字符,所以9,90,900这样的数字应表示成IX,XC,CM,并且向8,80,800这样的数字,只能写成VIII,LXXX,DCCC这样的形式,如IIX,XXC,CCM这样的表示方法是不对的,这样的表示方法没有办法读,也不符合逻辑。

经过以上分析,需要将小数字放在大数字坐标的情况,只有输入的整数中出现9时才会用到,那么只需要把是1-3999中各个数量级的9单独列出来就可以,看代码更容易理解。

代码:

public static String intToRoman(int num){String [] roman = {"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};int [] value = {1000,900,500,400,100,90,50,40,10,9,5,4,1};String ret = "";while(num != 0){for(int i = 0;i < 13;i++)if(num >= value[i]){ret += roman[i];num -= value[i];break;}}return ret;}

问题:

思路很清晰,但是上面的代码耗费的时间却很长,原因不仅仅是因为算法的原因,而主要是因为基层的运行方式决定的,下面是这道题中运行最快的大佬写的。

private static int[] nums = new int[]{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};    private static String[] strings = new String[]{"M",                                                    "CM", "D",                                                   "CD", "C",                                                   "XC", "L",                                                   "XL", "X", "IX", "V", "IV", "I"};    public String intToRoman(int num) {        StringBuilder res = new StringBuilder();        for (int i  = 0 ; i<nums.length; i++) {            while (num >= nums[i]) {                res.append(strings[i]);                num -= nums[i];            }        }        return res.toString();    }


这个和我的思路没有什么不同,但是运行时间差了50ms左右。这是因为他所采用的容器的基层结构决定的,这个就和java的翻译有关了,然而本人身为菜鸟一枚,并不懂。

从我的代码中可以看出,num在任何一个数量级的时候,num >= nums[i]都是从i=0开始执行的,这样显然有多余的循环部分,比如说当前num=9,那么直接从个位数的部分开始找就可以了,没有必要从头开始。所以把代码更改下,果然快了一些。

public static String intToRoman(int num){String [] roman = {"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};int [] value = {1000,900,500,400,100,90,50,40,10,9,5,4,1};String ret = "";while(num != 0){if(num < 100 && num >= 10){for(int i = 5;i < 13;i++){if(num >= value[i]){ret += roman[i];num -= value[i];break;}}}else if(num < 10){for(int i = 9;i < 13;i++){if(num >= value[i]){ret += roman[i];num -= value[i];break;}}}else{for(int i = 0;i < 13;i++){if(num >= value[i]){ret += roman[i];num -= value[i];break;}}}}return ret;}


如果将数字的范围划分的再细一些,会减少迭代的次数,应该会更快一点,于是实验了一下,确实快了些。

public static String intToRoman(int num){String [] roman = {"M","CM","DCCC","DCC","DC","D","CD","CCC","CC","C","XC","LXXX","LXX","LX","L","XL","XXX","XX","X","IX","V","IV","I"};int [] value = {1000,900,800,700,600,500,400,300,200,100,90,80,70,60,50,40,30,20,10,9,5,4,1};String ret = "";while(num != 0){if(num < 100 && num >= 10){for(int i = 10;i < 23;i++){if(num >= value[i]){ret += roman[i];num -= value[i];break;}}}else if(num < 10){for(int i = 19;i < 23;i++){if(num >= value[i]){ret += roman[i];num -= value[i];break;}}}else{for(int i = 0;i < 23;i++){if(num >= value[i]){ret += roman[i];num -= value[i];break;}}}}return ret;}

以上两种改进方式纯属搞笑,如有雷同,不胜荣幸。

时间复杂度:

我的最初的代码中,到一个新的数量级,迭代的次数最大值是一定的。下面举例说明:

num = 3832,在千位数量级的时候,while循环最多三次之后,num就会降低一个数量级。

num = 832,在百位数量级的时候,while循环最多四次之后(因为800需要经过D,C,C,C之后才能转换成功),num就会降低一个数量级。

num = 32,在十位数量级的时候,while循环最多四次(当十位数为8的时候,while循环需要四次),num就会降低一个数量级。

...

根据以上分析,在1-3999之间的任何数,转换成罗马数字,最多只需要3+4+4+4共15次循环即可得出最终结果。也就是说可以在一个常数时间内完成运行。

所以时间复杂度为O(1)。

空间复杂度:

需要存储String数组和int数组,常数量空间,O(1)。

原创粉丝点击