Integer to Roman is a classic "build a string from a lookup table" problem. It teaches a clean greedy move: when symbols come in fixed denominations, always grab the largest one that still fits.
Problem. Given an integer n (1 ≤ n ≤ 3999), convert it to its Roman numeral string. Roman numerals
use I=1, V=5, X=10, L=50, C=100, D=500, M=1000, plus the six subtractive forms IV=4, IX=9, XL=40, XC=90, CD=400, CM=900.
Example: n = 1994 → answer "MCMXCIV" (M = 1000, CM = 900, XC = 90, IV = 4).
The slow way first
You could special-case every digit position — handle thousands, then hundreds, then tens, then ones, each with its own if ladder for the 4/9 subtractive forms. It works, but it is a sprawl of branches that is easy to get wrong, and it does not generalize.
The question to ask: what if I treated the six subtractive forms as real "denominations" too? If CM (900) and IV (4) are just entries in my table alongside M and I, then the whole problem collapses into one greedy loop — no digit logic at all.
The idea: greedily spend the largest denomination
List all 13 value→symbol pairs in descending order, with the subtractive forms (CM, CD, XC, XL, IX, IV) sitting between the plain ones. Then, like making change with the fewest coins: repeatedly take the largest value that is ≤ n, append its symbol, and subtract it. Keep doing that until n reaches 0.
Because the table is sorted descending and includes the subtractive forms, the greedy choice is always correct — there is never a reason to skip a bigger value that fits in favor of a smaller one.
Walk through it
Step through the animation with n = 1994. The pointer p walks the table left to right. At 1000 it fits, so we take M and n drops to 994. At 900 it fits, so we take CM and n drops to 94. We slide past 500, 400, 100 (all too big), take XC at 90 (n → 4), slide past 50 down to 5, then take IV at 4 (n → 0). The result string spells out MCMXCIV.
Pseudocode
pairs = [(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")]
result = ""
for (value, symbol) in pairs: # largest first
while n >= value: # while it still fits
result = result + symbol # append the symbol
n = n - value # subtract its value
return resultThe Python solution
def int_to_roman(n):
pairs = [
(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"),
]
result = ""
for value, symbol in pairs:
while n >= value:
result += symbol
n -= value
return resultpairsis the lookup table — every plain value plus the six subtractive forms, in descending order.resultis the string we build up symbol by symbol.- The
forloop visits each denomination once, largest first. - The inner
while n >= valueis the greedy heart: as long as the current value fits, we keep appending its symbol and subtracting. (Forn = 3000we appendMthree times here.) - When the value no longer fits, the
whileexits and theformoves to the next, smaller denomination.
Complexity
| Case | Time | Notes |
|---|---|---|
| Per call | O(1) (fast) | table has a fixed 13 entries; result has at most 15 chars for n < 4000 |
O(1) (fast)The loop bounds do not grow with n — the table is a constant 13 entries and the output never exceeds about 15 symbols. So both time and space are O(1) for the constrained input range.
When this pattern shows up
Whenever values come in a fixed set of denominations and you want the fewest pieces, reach for the greedy "largest that fits" loop over a descending table. Roman numerals, making change with canonical coin systems, and breaking a quantity into named units are all the same move.
The trick that makes greedy correct here is putting the subtractive forms in the table (CM, CD,
XC, XL, IX, IV). Forget them and the greedy choice breaks — n = 4 would wrongly become IIII
instead of IV.
Practice
Tracing n = 1994, after taking M (n = 994) and CM (n = 94), which table entry does the pointer take next, and what does n become?
1. Why are the subtractive forms (CM, XC, IV, ...) included as entries in the table?
2. Why must the table be sorted in descending order?
3. For n = 1994, how many times does the inner while loop run for value = 1000?
4. What is the time complexity for the constrained input (n < 4000)?