题目链接:https://leetcode.com/problems/roman-to-integer/和https://leetcode.com/problems/integer-to-roman/
这两道姊妹题是十分经典有趣的字符串题目。
第一题:
要求我们将罗马数字转为整数,这个下面的规则说的很清晰,我最先想到的是暴力枚举所有规则:
class Solution {
public int romanToInt(String s) {
int i=0;
int n=s.length();
int sum=0;
while(i<n-1)
{
switch(s.charAt(i))
{
case 'I':
{
if(s.charAt(i+1)=='V')
{
sum += 4;
i+=2;
}
else if(s.charAt(i+1)=='X')
{
sum+=9;
i+=2;
}
else
{
sum+=1;
i++;
}
break;
}
case 'V':
{
sum+=5;
i++;
break;
}
case 'X':
{
if(s.charAt(i+1)=='L')
{
sum += 40;
i+=2;
}
else if(s.charAt(i+1)=='C')
{
sum+=90;
i+=2;
}
else
{
sum+=10;
i++;
}
break;
}
case 'L':
{
sum+=50;
i++;
break;
}
case 'C':
{
if(s.charAt(i+1)=='D')
{
sum += 400;
i+=2;
}
else if(s.charAt(i+1)=='M')
{
sum+=900;
i+=2;
}
else
{
sum+=100;
i++;
}
break;
}
case 'D':
{
sum+=500;
i++;
break;
}
case 'M':
{
sum+=1000;
i++;
break;
}
}
}
if(i==n)
return sum;
if(i==n-1)
{
switch(s.charAt(i))
{
case 'I':
{
sum+=1;
break;
}
case 'V':
{
sum+=5;
break;
}
case 'X':
{
sum+=10;
break;
}
case 'L':
{
sum+=50;
break;
}
case 'C':
{
sum+=100;
break;
}
case 'D':
{
sum+=500;
break;
}
case 'M':
{
sum+=1000;
break;
}
}
}
return sum;
}
}
的确很暴力!!!但是性能不咋地。
上述代码枚举规则中没有估计罗马数字从左往右由大到小的特点,利用好这一特点就能够优化性能,考虑使用一个与罗马数字字符串等长的整数数组来存储各个字母对应的数字值,违背“从大到小规则的连续两个字符”这一规则的需要将前一个字符对应的整数值减去而不是相加。
class Solution {
public int romanToInt(String s) {
int[] record=new int[s.length()];
for(int i=0;i<record.length;i++)
{
switch(s.charAt(i))
{
case 'I':
record[i]=1;
break;
case 'V':
record[i]=5;
break;
case 'X':
record[i]=10;
break;
case 'L':
record[i]=50;
break;
case 'C':
record[i]=100;
break;
case 'D':
record[i]=500;
break;
case 'M':
record[i]=1000;
break;
}
}
int sum=0;
int i=0;
for(;i<record.length-1;i++)
{
if(record[i]<record[i+1])
sum-=record[i];
else
sum+=record[i];
}
return sum+record[i];
}
}
这个性能提高很多!!!
第二题:
这个题目一拿到之后,我就试图借鉴上一题的第二种解法的思想,首先来看这张表:
表中箭头表示可能涉及的4,9,40,90,400,900的转化,那么什么时候要转化,该定义什么样的规则呢?我们先来举几个例子看看未经转化最原始的整除的结果,读者可以自己尝试填填表格:
深入理解除法的人容易观察到:D、L、V上的值只能取0,1 M、C、X、I对应的值只能取0,1,2,3,4,其中MDC、CLX、XVI三组每组内都是相互独立的转化系统,我们可以逐一考虑 。
这里面涉及到转换的例子和一些简单的例子,先看未经转换的初步结果initial这一列:
看红色框的部分都是需要转换的,不难发现,D=1且C=4(900) L=1且X=4(90) V=1且I=4(9)三组分别需要M、C、X来做减法(减去C-100、X-10、I-1)。而C=4(400) X=4(40) I=4(4)三组分别需要D、L、V来做减法(减去C-100、X-10,I-1)。三部分可以用同一个逻辑单元来处理
需要注意的是:MDC、CLX、XVI三组中,当前面的组发生转化时,需要将D、X对应的商值置0,避免重复累计。
class Solution {
public static int[] record;
public static int[] divisor={1000,500,100,50,10,5,1};
public static char[] Rome={'M','D','C','L','X','V','I'};
public static String intToRoman(int num) {
record=new int[7];
for(int i=0;i<7;i++)
{
record[i] = num / divisor[i];
num %= divisor[i];
}
StringBuilder ret=new StringBuilder();
for(int i=2;i<7;i+=2)
{
int k=record[i-2];
ret=repeat(k,ret,i-2);
if(record[i]==4 && record[i-1]==1)
{
ret.append(Rome[i]).append(Rome[i-2]);
record[i]=0;
}
else if(record[i]==4) {
ret.append(Rome[i]).append(Rome[i - 1]);
record[i] = 0;
}
else
{
ret=repeat(record[i-1],ret,i-1);
ret=repeat(record[i],ret,i);
record[i]=0;
}
}
return ret.toString();
}
public static StringBuilder repeat(int k,StringBuilder sb,int i)
{
while(k>0)
{
sb.append(Rome[i]);
k--;
}
return sb;
}
}
效率还算一般: