MediumArrays & Strings

Integer to Roman โ€” Solution

Problem

Given an integer between 1 and 3999, convert it to its Roman numeral representation. Roman numerals use seven symbols (I, V, X, L, C, D, M) along with a subtractive notation for six special cases โ€” for example, 4 is written IV rather than IIII.

  • Input: num = 1994
  • Output: "MCMXCIV"
  • Explanation: 1994 = 1000 (M) + 900 (CM) + 90 (XC) + 4 (IV)

Counter-example: num = 1400 โ†’ "MCD" (not "MCCCC"), because 400 uses the subtractive form CD.

Intuition

Roman numeral conversion is inherently greedy: always pick the largest symbol that fits into the remaining value. The six subtractive forms (IV, IX, XL, XC, CD, CM) look like special cases, but they're just additional entries in the symbol table. Once you include all 13 symbol-value pairs in descending order, the same greedy loop handles everything uniformly โ€” no branching required.

Solution โ€” Greedy Symbol Table

Build a lookup table of all 13 (value, symbol) pairs ordered from largest (1000) to smallest (1), including the six subtractive pairs. Repeatedly peel off the largest symbol that fits.

  1. Define 13 pairs: the seven standard symbols plus the six subtractive forms (900 โ†’ CM, 400 โ†’ CD, 90 โ†’ XC, 40 โ†’ XL, 9 โ†’ IX, 4 โ†’ IV).
  2. For each pair in order, while the remaining number is โ‰ฅ the value, append the symbol and subtract the value.
  3. Return the accumulated string.
1def int_to_roman(num: int) -> str:
2    symbol_table = [
3        (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"),
4        (100,  "C"), (90,  "XC"), (50,  "L"), (40,  "XL"),
5        (10,   "X"), (9,   "IX"), (5,   "V"), (4,   "IV"),
6        (1,    "I"),
7    ]
8    result = []
9    for value, symbol in symbol_table:
10        while num >= value:          # peel off as many copies of this symbol as fit
11            result.append(symbol)
12            num -= value
13    return "".join(result)

Time: O(1) โ€” the loop runs a bounded number of iterations since num โ‰ค 3999 and each symbol subtracts at least 1. Space: O(1) โ€” the result string length is bounded (at most ~15 characters, e.g. "MMMDCCCLXXXVIII" for 3888).

Complexity Summary

ApproachTimeSpaceWhen to use
Greedy Symbol TableO(1)O(1)Always โ€” it's the canonical solution for bounded integer-to-Roman conversion

Common Mistakes

  • Omitting the subtractive pairs from the table โ€” writing separate if-else branches for IV, IX, XL, XC, CD, CM instead of including them as entries; this produces incorrect output for inputs like 4, 9, 40, 90, 400, and 900.
  • Getting the table order wrong โ€” the pair (900, "CM") must appear before (500, "D") so the greedy loop always picks the largest symbol first; an out-of-order table produces wrong results like "DCD" instead of "CM" for 900.
  • Forgetting CD (400) or XL (40) โ€” programmers commonly recall IV, IX, XC, and CM but miss the two middle subtractive forms, causing errors for inputs like 1400 or 1440.
  • Using string concatenation in a loop in Java โ€” result = result + symbol allocates a new String on every iteration; use StringBuilder.append() to avoid quadratic allocations.
  • Adding a guard for num = 0 โ€” the problem guarantees 1 โ‰ค num โ‰ค 3999, so a zero check is unnecessary dead code.

Related Problems

  • Roman to Integer โ€” inverse conversion; uses the same symbol table in reverse
  • Add Binary โ€” string construction from a different numeric base
  • Multiply Strings โ€” producing a string result from integer operations without built-in conversion
  • Reverse Integer โ€” working with individual digits of a bounded integer
  • Largest Number โ€” greedy ordering to build an optimal string representation from numbers

Ready to practice? Try it on SkillFlow

Adaptive problems, AI follow-up interviews, and a skill score that shows exactly where you need to improve.

Practice This Problem โ†’