Soluciones LeetCode
#13 — Romano a entero

Mira adelante, resta y conquista — el decodificador de números romanos

Arte ASCII a color: una computadora anticuada sobre un escritorio con una estantería al fondo, que sugiere un espacio de trabajo clásico para programar.
Ilustración en estilo ASCII: una computadora anticuada sobre un escritorio con una estantería detrás.

Enunciado del problema

Los números romanos se representan mediante siete símbolos diferentes: I, V, X, L, C, D y M.

SímboloValor
I1
V5
X10
L50
C100
D500
M1000

Por ejemplo, 2 se escribe como II en números romanos, simplemente dos unos sumados. 12 se escribe como XII, que es simplemente X + II. El número 27 se escribe como XXVII, que es XX + V + II.

Los números romanos generalmente se escriben de mayor a menor de izquierda a derecha. Sin embargo, el numeral para cuatro no es IIII. En su lugar, el número cuatro se escribe como IV. Como el uno está antes del cinco, lo restamos para obtener cuatro. El mismo principio se aplica al número nueve, que se escribe como IX. Hay seis casos donde se usa la resta:

  • I puede colocarse antes de V (5) y X (10) para formar 4 y 9.
  • X puede colocarse antes de L (50) y C (100) para formar 40 y 90.
  • C puede colocarse antes de D (500) y M (1000) para formar 400 y 900.

Dado un número romano, conviértelo a entero.

Ejemplo 1:

Ejemplo 2:

Ejemplo 3:

Restricciones:

  • 1 <= s.length <= 15

  • s contiene solo los caracteres ('I', 'V', 'X', 'L', 'C', 'D', 'M').

  • Se garantiza que s es un número romano válido en el rango [1, 3999].

El enfoque ingenuo

La traducción más directa de las reglas: recorre de izquierda a derecha, mapea cada símbolo a su valor, y si ves un valor menor antes de uno mayor (como I antes de V), réstalo en lugar de sumarlo. Es un enfoque perfectamente válido y funciona de inmediato.

Python

def romanoAEntero_ingenuo(s: str) -> int:
    valores = {'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':1000}
    total = 0
    i = 0
    while i < len(s):
        # si el actual es menor que el siguiente, es un par sustractivo
        if i + 1 < len(s) and valores[s[i]] < valores[s[i+1]]:
            total += valores[s[i+1]] - valores[s[i]]
            i += 2
        else:
            total += valores[s[i]]
            i += 1
    return total
            

Complejidad inicial

Esto ya es O(n) en tiempo y O(1) en espacio — solo tocamos cada carácter un par de veces. El código es claro y fácil de seguir. Pero el problema se vuelve aún más limpio cuando invertimos la perspectiva: en lugar de buscar pares, simplemente miramos hacia adelante y decidimos si el símbolo actual debe sumarse o restarse. Ese pequeño cambio elimina la necesidad de un índice que salte.

Una sola pasada más fluida

La observación clave: en números romanos, si un símbolo es seguido por uno de mayor valor, actúa como un restador. De lo contrario, suma. Así que podemos recorrer la cadena una vez, comparar el valor actual con el siguiente, y sumar o restar en consecuencia. El último símbolo siempre se suma porque no hay nada después de él.

Esta versión se lee casi como el proceso mental: “M es 1000, siguiente es C (100) que es menor, así que sumo 1000. C es 100, siguiente es M (1000) — mayor, así que resto 100…” Es elegante y usa solo una pasada sin saltos de casos especiales.

Python

def romanoAEntero(s: str) -> int:
    valores = {
        'I': 1,
        'V': 5,
        'X': 10,
        'L': 50,
        'C': 100,
        'D': 500,
        'M': 1000
    }
    
    total = 0
    n = len(s)
    
    for i in range(n):
        # si el valor actual es menor que el siguiente, réstalo
        if i < n - 1 and valores[s[i]] < valores[s[i+1]]:
            total -= valores[s[i]]
        else:
            total += valores[s[i]]
            
    return total
            

Sigamos el ejemplo más complicado: s = "MCMXCIV" (1994).

Simulación paso a paso: s = "MCMXCIV"
i símbolo valor símbolo sig. valor sig. acción total tras paso
0 M 1000 C 100 1000 ≥ 100 → sumar 1000
1 C 100 M 1000 100 < 1000 → restar 900
2 M 1000 X 10 1000 ≥ 10 → sumar 1900
3 X 10 C 100 10 < 100 → restar 1890
4 C 100 I 1 100 ≥ 1 → sumar 1990
5 I 1 V 5 1 < 5 → restar 1989
6 V 5 último símbolo → sumar 1994
La decisión se toma mirando el siguiente carácter. Los pares sustractivos (CM, XC, IV) se manejan naturalmente restando el valor menor antes de sumar el mayor en la siguiente iteración.

Observa cómo la resta de C (100) y la posterior suma de M (1000) se combinan para dar el neto +900 de CM. Lo mismo ocurre con XC (90) e IV (4).

Complejidad final

Complejidad temporal: O(n) — recorremos la cadena exactamente una vez, realizando una búsqueda en diccionario y una comparación de tiempo constante por carácter.
Complejidad espacial: O(1) — el tamaño del diccionario está fijo en 7 entradas, y usamos solo unas pocas variables enteras. Ninguna estructura adicional escala con la entrada.

Esta solución es rápida, eficiente en memoria y refleja exactamente cómo un humano experimentado analizaría un número romano: echar un vistazo adelante y decidir.

Feliz programación, y que tu complejidad siempre sea baja.

Referencias y lecturas adicionales

¿No te cansas de lumænaut? Lee esto...