📘 題目敘述
給你一個整數 num,請把它轉換成羅馬數字並回傳。
羅馬數字符號與數值如下:
I= 1V= 5X= 10L= 50C= 100D= 500M= 1000
羅馬數字通常由大到小排列,並且用下列特殊規則表示減法:
I可以放在V(5)和X(10)前面,形成IV(4)和IX(9)X可以放在L(50)和C(100)前面,形成XL(40)和XC(90)C可以放在D(500)和M(1000)前面,形成CD(400)和CM(900)
題目保證輸入 num 會在 1 到 3999 的範圍內。
條件限制
1 <= num <= 3999
🧠 解題思路(一)if else判斷
我的想法就是把數字拆成每一位(千、百、十、個),然後把每一位所有可能情況都判斷完。
因為羅馬數字的規則其實非常固定,每一位都只會遇到幾種形式:
- 0:不加任何字
- 1~3:重複加 1 的符號(例如百位
C、十位X、個位I) - 4:用減法寫成
1在5前面(例如IV、XL、CD) - 5:直接加
5的符號(例如V、L、D) - 6~8:先加
5的符號,再補 1 的符號(例如VI、LXX、DCCC) - 9:用減法寫成
1在10前面(例如IX、XC、CM)
所以我就按照「千位 → 百位 → 十位 → 個位」的順序,一段一段把字串加上去,最後得到完整羅馬數字。
所有變數
num:題目輸入的整數(我會一路用%=把已處理的高位扣掉)s:最後要回傳的羅馬數字字串(一邊處理一邊往後加)digit:目前處理那一位的數字(千位、百位、十位時用它)
🪜 主要流程步驟
1. 先處理千位:只會出現 M 的重複
我先算 digit = num / 1000,代表千位是幾。千位的羅馬數字只有一種形式:重複 M,所以直接加 digit 次 M。處理完後我用 num %= 1000 把千位去掉,準備處理百位。
2. 再處理百位:把 4、9、5~8 這些特殊情況先判斷掉
百位用 digit = num / 100。我先處理最特殊的兩種減法:
digit == 4→ 加"CD"digit == 9→ 加"CM"
如果不是 4 或 9,接著處理大於等於 5 的情況:
digit > 4→ 先加'D',並讓digit -= 5
剩下的 digit 就只會是 0~3,我再補上 digit 次 'C'。最後 num %= 100,進入十位。
3. 十位的寫法完全同百位,只是符號換成 X / L / C
十位用 digit = num / 10。同樣先判斷:
digit == 4→"XL"digit == 9→"XC"digit > 4→ 先加'L'並digit -= 5- 補上
digit次'X'
最後 num %= 10,剩下就是個位。
4. 個位同樣處理 4、9、5~8,再補 I
個位直接用剩下的 num:
num == 4→"IV"num == 9→"IX"num > 4→ 先加'V'並num -= 5- 最後補上
num次'I'
5. 回傳答案字串
所有位數處理完後,回傳 s。
這個方法的優缺點
這個方法用了一堆 if else 的條件判斷數字大小條件,但如果位數一多其實就會很麻煩,所以還有更好的方式來做。但也因為只有用條件判斷,其實速度很快而且記憶體大小也不大。
💻 程式碼實作(一)
class Solution {
public:
string intToRoman(int num) {
string s = "";
int digit = num / 1000; // 千位
if (digit > 0) {
for (int i = 0; i < digit; i++) {
s += 'M';
}
num %= 1000;
}
digit = num / 100; // 百位
if (digit > 0) {
if (digit == 4) {
s += "CD";
digit = 0;
} else if (digit == 9) {
s += "CM";
digit = 0;
} else if (digit > 4) {
s += 'D';
digit -= 5;
}
for (int j = 0; j < digit; j++) {
s += 'C';
}
num %= 100;
}
digit = num / 10; // 十位
if (digit > 0) {
if (digit == 4) {
s += "XL";
digit = 0;
} else if (digit == 9) {
s += "XC";
digit = 0;
} else if (digit > 4) {
s += 'L';
digit -= 5;
}
for (int j = 0; j < digit; j++) {
s += 'X';
}
num %= 10;
}
if (num > 0) { // 個位
if (num == 4) {
s += "IV";
num = 0;
} else if (num == 9) {
s += "IX";
num = 0;
} else if (num > 4) {
s += 'V';
num -= 5;
}
for (int j = 0; j < num; j++) {
s += 'I';
}
}
return s;
}
};
🧠 解題思路(二)用Greedy
這份寫法我用的是「把所有可能的羅馬符號組合先列出來,然後從大到小貪心地扣」。
因為羅馬數字其實可以被視為一種「固定面額的表示法」:
- 除了基本符號(M, D, C, L, X, V, I)以外
- 還有題目規則允許的減法符號(CM, CD, XC, XL, IX, IV)
只要我把這些符號按數值從大到小排好,接下來就可以:
- 只要
num還大於等於目前的面額n[pos]- 就把對應符號
a[pos]加進答案 - 並把
num減掉這個面額
- 就把對應符號
- 如果
num小於目前面額- 就換下一個較小的面額
這樣做會得到正確羅馬數字,原因是題目允許的表示法本來就是由這些面額組成,而且「每次優先用最大面額」會讓字串自然維持由大到小的順序,也會自動處理 4、9、40、90、400、900 這些減法情況。
所有變數
num:題目輸入的整數(我會一路扣到 0)a:字串陣列,存每個面額對應的羅馬符號(已按數值由大到小排列)n:整數陣列,存每個羅馬符號對應的面額(與a同索引對齊)s:最後要回傳的羅馬數字字串pos:目前正在嘗試使用的面額索引(從 0 往後走,面額越來越小)
🪜 主要流程步驟
1. 先把所有「合法符號」按面額由大到小排好
我用兩個陣列對齊存資料:
n[pos]是面額(1000, 900, 500, ... , 1)a[pos]是對應符號("M", "CM", "D", ... , "I")
這裡把減法符號(例如 900 的 "CM")也當作一種面額一起處理,後面就不用特判 4/9 這些情況。
2. 用 pos 從大面額一路往小面額掃
我從 pos = 0 開始(面額最大),只要 num 還沒變成 0 就一直做:
- 如果
num >= n[pos]- 代表目前面額可以用
- 把
a[pos]加到答案字串s num -= n[pos],把這一塊扣掉- 注意這時
pos不動,因為同一面額可能要用很多次(例如 3000 會用三次 "M")
- 否則(
num < n[pos])- 代表這個面額太大,不能用
pos++換下一個較小面額繼續試
3. num 扣到 0 就結束,答案字串完成
當 num 變成 0,代表所有值都已經被表示完畢,回傳 s。
💻 程式碼實作(二)
class Solution {
public:
string intToRoman(int num) {
string a[] = {"M", "CM", "D", "CD", "C", "XC", "L",
"XL", "X", "IX", "V", "IV", "I"};
int n[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
string s = "";
int pos = 0;
while (num > 0) {
if (num >= n[pos]) {
s += a[pos];
num -= n[pos];
} else {
pos++;
}
}
return s;
}
};
🔗 題目連結
https://leetcode.com/problems/integer-to-roman/