---
# System prepended metadata

title: 12. Integer to Roman
tags: [Leetcode]

---

# 12. Integer to Roman

**練習日期：** 2026-02-09
**難度：** Medium
**類型：** 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` 會在 `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的條件判斷數字大小條件，但如果位數一多其實就會很麻煩，所以還有更好的方式來做。
但也因為只有用條件判斷，其實速度很快而且記憶體大小也不大。

## 💻 程式碼實作（一）

```cpp
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`。

## 💻 程式碼實作（二）

```cpp
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/description/