12. Integer to Roman

練習日期:2026-02-09

難度:Medium

類型:Hash Table、Math、String

📘 題目敘述

給你一個整數 num,請把它轉換成羅馬數字並回傳。

羅馬數字符號與數值如下:

  • I = 1
  • V = 5
  • X = 10
  • L = 50
  • C = 100
  • D = 500
  • M = 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 會在 13999 的範圍內。

條件限制

  • 1 <= num <= 3999

🧠 解題思路(一)if else判斷

我的想法就是把數字拆成每一位(千、百、十、個),然後把每一位所有可能情況都判斷完

因為羅馬數字的規則其實非常固定,每一位都只會遇到幾種形式:

  • 0:不加任何字
  • 1~3:重複加 1 的符號(例如百位 C、十位 X、個位 I
  • 4:用減法寫成 15 前面(例如 IVXLCD
  • 5:直接加 5 的符號(例如 VLD
  • 6~8:先加 5 的符號,再補 1 的符號(例如 VILXXDCCC
  • 9:用減法寫成 110 前面(例如 IXXCCM

所以我就按照「千位 → 百位 → 十位 → 個位」的順序,一段一段把字串加上去,最後得到完整羅馬數字。

所有變數

  • num:題目輸入的整數(我會一路用 %= 把已處理的高位扣掉)
  • s:最後要回傳的羅馬數字字串(一邊處理一邊往後加)
  • digit:目前處理那一位的數字(千位、百位、十位時用它)

🪜 主要流程步驟

1. 先處理千位:只會出現 M 的重複

我先算 digit = num / 1000,代表千位是幾。千位的羅馬數字只有一種形式:重複 M,所以直接加 digitM。處理完後我用 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/

作者:scottnick
撰寫日期:2026-02-09
類別:
原文連結:https://scottnick.github.io/cpp-notes/all%20problems/12-integer-to-roman.html