12. Integer to Roman
題目 / Problem
中文:
給你一個整數 num,請將它轉換成羅馬數字並回傳對應的字串。
七個羅馬符號對應的數值:
| 符號 | 數值 |
|---|---|
| I | 1 |
| V | 5 |
| X | 10 |
| L | 50 |
| C | 100 |
| D | 500 |
| M | 1000 |
轉換規則: - 從高位往低位依序處理每一個十進位位數。 - 一般情況下挑「能扣掉的最大值」對應符號附加上去,再扣除繼續處理。 - 若該位以 4 或 9 開頭,要用「減法形式」:4=IV、9=IX、40=XL、90=XC、400=CD、900=CM。 - I、X、C、M 最多連寫 3 次;V、L、D 不能連寫。
限制: 1 <= num <= 3999
English:
Given an integer num (with 1 <= num <= 3999), convert it to a Roman numeral string. The seven Roman symbols I, V, X, L, C, D, M map to 1, 5, 10, 50, 100, 500, 1000 respectively. Numerals are built by processing decimal place values from highest to lowest, using the subtractive forms IV, IX, XL, XC, CD, CM whenever a place starts with 4 or 9. Powers of ten (I, X, C, M) may repeat at most three times in a row; V, L, D never repeat.
Worked example: num = 1994 → 1000 = M, 900 = CM, 90 = XC, 4 = IV → "MCMXCIV".
名詞解釋 / Glossary
- 羅馬數字 / Roman numeral — 一種古代記數系統,用 I、V、X、L、C、D、M 七個字母組合表示數字。An ancient numeral system using seven letters; numbers are built by adding (and occasionally subtracting) symbol values.
- 減法形式 / Subtractive form — 當某位是 4 或 9 時,用「較小符號放在較大符號左邊」表示相減(例如 IV=4、CM=900)。Six special two-letter pairs (IV, IX, XL, XC, CD, CM) where a smaller symbol before a larger one means "subtract."
- 貪心法 / Greedy algorithm — 每一步都挑當下能扣掉的最大數值,無需回溯。重複到剩 0 為止。At each step pick the largest value that fits; never reconsider. Works here because the Roman system is designed so this strategy is always optimal.
- 平行陣列 / Parallel arrays — 兩個長度相同的陣列,索引一一對應(一個放數值、一個放符號字串)。Two arrays of equal length where index
iof each refers to the same logical item — used here to pair values with their Roman strings. - 字串拼接 / String concatenation — 把多個字串接在一起。在 C 裡用
strcpy/手動複製字元;在 C++ 用+=或append。Joining strings end-to-end; in C we copy characters manually, in C++ we use+=. - 時間複雜度 O(1) / Constant time — 不論輸入大小,操作次數都有固定上限。Here the input is bounded (
num <= 3999) so the loop runs a bounded number of times.
思路
中文:最直觀的暴力做法是逐位(千、百、十、個)查表,每位各自寫一段 if/else 處理「是不是 4 或 9」、「要不要 V/L/D 加上幾個 I/X/C」。這樣能解,但要寫四段幾乎一樣的程式碼,又容易少打或打錯字母。觀察羅馬數字的設計:所有「會用到」的數值其實只有 13 個——七個基本符號 1, 5, 10, 50, 100, 500, 1000 加上六個減法形式 4, 9, 40, 90, 400, 900。如果把這 13 個數值從大到小排好,搭配對應的羅馬字串,就可以用「貪心法」:每次挑出當下能扣掉的最大值,把對應字串接到答案後面,再從 num 扣掉這個值,重複直到 num=0。為什麼貪心一定對?因為羅馬數字系統本身就是設計成「每一個十進位位數最多消耗一個減法形式 + 最多三個重複符號」,當我們把減法形式也視為一個原子單位放進候選清單,每個位都能在常數步內被消掉,不會出現「先選大的反而吃虧」的情況。關鍵不變式:迴圈每跑一輪,num 至少減少候選清單中當前最大可用值,因此一定會單調下降到 0。
English: The brute-force idea is to handle each decimal place (thousands, hundreds, tens, ones) with its own if/else chain that checks "is the digit 4 or 9, otherwise how many V/L/D plus I/X/C do I need?" It works but means writing four nearly identical blocks and is easy to typo. The cleaner observation is that the Roman system only ever needs 13 distinct values: the seven base symbols (1, 5, 10, 50, 100, 500, 1000) plus the six subtractive pairs (4, 9, 40, 90, 400, 900). List those 13 values from largest to smallest with their string forms and run a greedy loop: subtract the largest value that fits, append its string, repeat until num is 0. Greedy is provably correct here because Roman numerals are designed so each decimal place is consumed by at most one subtractive pair plus at most three repeats of a base symbol — treating the subtractive pairs as atomic candidates means we never have to backtrack. The loop invariant is that num strictly decreases on every iteration, so termination is guaranteed.
逐步走查 / Walkthrough
Trace with num = 3749. Candidate table (value → symbol), largest first:
1000→M, 900→CM, 500→D, 400→CD, 100→C, 90→XC, 50→L, 40→XL, 10→X, 9→IX, 5→V, 4→IV, 1→I.
| Step | num before |
Largest value ≤ num |
Append | num after |
Result so far |
|---|---|---|---|---|---|
| 1 | 3749 | 1000 | M |
2749 | M |
| 2 | 2749 | 1000 | M |
1749 | MM |
| 3 | 1749 | 1000 | M |
749 | MMM |
| 4 | 749 | 500 | D |
249 | MMMD |
| 5 | 249 | 100 | C |
149 | MMMDC |
| 6 | 149 | 100 | C |
49 | MMMDCC |
| 7 | 49 | 40 | XL |
9 | MMMDCCXL |
| 8 | 9 | 9 | IX |
0 | MMMDCCXLIX |
迴圈在 num = 0 時結束,輸出 "MMMDCCXLIX",與題目期望一致。Loop exits when num reaches 0, producing "MMMDCCXLIX" — matches the expected output.
Solution — C
// 演算法:把 13 個「值 → 羅馬符號」候選由大到小排好,每次扣掉能放的最大值並把符號接到答案後面。
// Algorithm: list 13 (value, symbol) candidates largest-first; greedily subtract the biggest
// value that fits into num and append its symbol to the answer until num reaches 0.
#include <stdlib.h> // malloc / 動態配置記憶體 / for dynamic memory allocation
#include <string.h> // strcpy / 字串複製 / for copying C-strings
char* intToRoman(int num) {
// 平行陣列:values[i] 對應 symbols[i] / Parallel arrays: values[i] pairs with symbols[i].
// 從大到小排好,這是貪心法的關鍵 / Sorted largest-first — required for the greedy loop.
int values[13] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
const char* symbols[13] = {"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
// 最壞情況 3999 = MMMCMXCIX,長度 15;給 20 個字元的緩衝足夠且簡單 / Worst-case length
// is 15 (for 3999); 20 bytes is a safe, simple upper bound including the '