12. Integer to Roman
Difficulty: Medium
Given an integer, convert it to a roman numeral. Input is guaranteed
to be within the range from 1 to 3999.
给定一个整数,转化为罗马数字。输入的数字范围为1~3999.
重点
罗马数字与整数之间的转换规则:
- 左减右加,IV=4=5-1
- 左减不能跨越一个位数,99=90+9=XCIX(正确), =100-1=IC(错误)
- 左减不能超过1位,8=VIII(正确), =10-1-1=IIX(错误)
- 右加不能超过3位,14=10+4=XIV(正确), =10+1+1+1+1=XIIII(错误)
- 左减数字仅限于I,X,C,45=40+5=XLV(正确), =50-5=VL(错误)
- L不能左减I,D不能左减X,所以
49=40+9=XLIX(正确), =50-1=IL(错误)
490=400+90=CDXC(正确), =500-10=XD(错误)
解法1
缺点:逻辑复杂,不容易理解,代码冗长,特殊情况太多,不适用。
const int Count = 7;
const int roman[Count] = { 1, 5, 10, 50, 100, 500, 1000 };
const string str_roman[Count] = {"I","V","X","L","C","D","M"};
class Solution {
private:
int CalcNums(int a);
public:
string intToRoman(int num);
};
//计算并返回整数a的位数
int Solution::CalcNums(int a)
{
int nums = 0;
while (a > 0)
{
a /= 10;
nums++;
}
return nums;
}
//整数转为罗马数字
string Solution::intToRoman(int num)
{
string str;
if (num <= 0)
{
return str;
}
if(num==49)
{
return "XLIX";
}
else if(num==490)
{
return "CDXC";
}
int note = 0;
int sign = 0;
int temp;
while (note < Count)
{
if (num <= roman[note])
break;
note++;
}
if (note == Count) //大于1000,相加
{
sign = 1;
}
else if (num == roman[note]) //已经找到
{
str = str_roman[note];
return str;
}
if (roman[note] - roman[note - 1] > num) //减一次仍太大(左减不能超过1位),只能相加
{
sign = 1;
}
if (sign == 0) //相减
{
temp = roman[note] - num;
for (int k = note - 1; k >= 0; k--)
{
if (temp == roman[k])
{
int n1 = CalcNums(roman[note]);
int n2 = CalcNums(roman[k]);
if ((n1 - n2 > 1)||(k!=0 && k!=2 && k!=4)) //左减不能跨越一个位数,左减的数字仅限于I、X、C,否则只能相加
{
sign = 1;
break;
}
else
{
str = str_roman[k];
str += str_roman[note];
return str;
}
}
else if (temp > roman[k]) //找不到可以左减的值,只能相加
{
sign = 1;
break;
}
}
}
if (sign == 1) //相加
{
int k = 0;
temp = num;
while (k < 3) //检查是否可由相同数字相加得到(右加不能超过3位)
{
temp -= roman[note - 1];
k++;
if (temp < 0)
break;
if (temp == 0)
{
for (int i = 0; i < k; i++)
{
str += str_roman[note - 1];
}
return str;
}
}
int n = CalcNums(num); //不同数字相加
if (n == 1) //只有1位,整数6-8
{
str = "V";
num -= 5;
while (num > 0)
{
str += "I";
num--;
}
}
else //两位以上
{
int sep1;
int sep2;
temp = num % (int)pow(10, n - 1);
if (temp == 0)
{
sep1 = roman[note - 1];
sep2 = num - sep1;
str = str_roman[note - 1];
str += intToRoman(sep2); //递归
}
else
{
sep2 = num % (int)pow(10, n - 1); //分成两部分
sep1 = num - sep2;
str = intToRoman(sep1); //递归
str += intToRoman(sep2); //递归
}
}
return str;
}
return str;
}
解法2
优点:逻辑清晰,简洁
思路:化减为加
基本数字:
I—-1
V—5
X—10
L—50
C—100
D—500
M—1000
根据规则2,3,5,6,可以左减的搭配为
IV—-4
IX—-9
XL—40
XC—90
CD—400
CM—900
扩展为13个基本数字,且基本数字之间组合相加可以得到结果,不需要再考虑相减的情况。
根据贪心算法,每次求最优解,每次匹配最大值
class Solution {
public:
string intToRoman(int num);
};
const int roint[13]={1000,900,500,400,100,90,50,40,10,9,5,4,1}; //化减为加
const string rostr[13]={"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
string Solution::intToRoman(int num)
{
string ret;
int i=0;
//贪心算法,每次匹配最大值
while(num>0)
{
if(num>=roint[i])
{
num-=roint[i];
ret+=rostr[i];
}
else
{
i++;
}
}
return ret;
}
问题扩展:罗马数字转数字,范围为1~3999。
(另外一篇博客有过解答,这里提供更简洁的方法)
思路:比右边大,为 +;比右边小,为 - 。
class Solution {
private:
int funTrans(char c);
public:
int romanToInt(string s) {
int num=0;
int i;
for(i=0;i<s.size()-1;i++)
{
if(funTrans(s[i])>=funTrans(s[i+1])) //比右边大,为+
{
num+=funTrans(s[i]);
}
else //比右边小,为 -
{
num-=funTrans(s[i]);
}
}
num+=funTrans(s[i]);
return num;
}
};
int Solution::funTrans(char c) //也可以用hash表代替
{
int num;
switch(c)
{
case 'I':return 1;
break;
case 'V':return 5;
break;
case 'X':return 10;
break;
case 'L':return 50;
break;
case 'C':return 100;
break;
case 'D':return 500;
break;
case 'M':return 1000;
break;
default:return 0;
}
}