🐧
#3:LeetCode Roman to Integer
問題(GPT翻訳)
- Roman to Integer (Easy)
与えられたローマ数字文字列 s を、その整数値に変換せよ。
ローマ数字の基本記号と値:I=1, V=5, X=10, L=50, C=100, D=500, M=1000
通常は左から右へ加算するが、小さい値が大きい値の左にあるときは減算する
(例:IV=4, IX=9, XL=40, XC=90, CD=400, CM=900)。
制約
解法
初めに思いついた解法
- 方針
- 前から順にルールの通り次の値を見て、減算か加算かを判断する
from typing import Final
ROMAN_TO_VALUE: Final[dict[str, int]] = {
"M": 1000,
"D": 500,
"C": 100,
"L": 50,
"X": 10,
"V": 5,
"I": 1,
}
class Solution:
def romanToInt(self, s: str) -> int:
total = 0
for i in range(len(s) - 1):
if ROMAN_TO_VALUE[s[i]] < ROMAN_TO_VALUE[s[i + 1]]:
total -= ROMAN_TO_VALUE[s[i]]
else:
total += ROMAN_TO_VALUE[s[i]]
total += ROMAN_TO_VALUE[s[-1]]
return total
- 時間計算量・空間計算量
- 時間計算量:入力分を走査しているだけであるため、O(n)
- 空間計算量:
ROMAN_TO_VALUE
は固定サイズのマップ(定数)なので O(1)
最終的な解法
逆側から読んでいくことで、これまで必要だったforループ後の末尾加算が不要になりました。
足し忘れのようなロジック漏れも防げて、わかりやすくなりました。
from typing import Final
ROMAN_TO_VALUE: Final[dict[str, int]] = {
"M": 1000,
"D": 500,
"C": 100,
"L": 50,
"X": 10,
"V": 5,
"I": 1,
}
class Solution:
def romanToInt(self, s: str) -> int:
total, prev = 0, 0
for r in reversed(s):
cur = ROMAN_TO_VALUE[r]
if cur < prev:
total -= cur
else:
total += cur
prev = cur
return total
別解
Pythonぽさを出すならitertools.pairwise(Python 3.10+)
を使用する方法もあります。
同じような解法でzip(s, s[1:])
を使った方法もありますが、今回のようにペアでzipしようと思うとs[1:]
のスライスが新しい文字列を生成するため追加のO(n)メモリ/コピーが発生するので今回は採用しませんでした(時間計算量はどちらもO(n))。
itertools.pairwise(Python 3.10+)
であれば、コピーを作らずペアを返してくれます。
from itertools import pairwise
from typing import Final
ROMAN_TO_VALUE: Final[dict[str, int]] = {
"M": 1000,
"D": 500,
"C": 100,
"L": 50,
"X": 10,
"V": 5,
"I": 1,
}
class Solution:
def romanToInt(self, s: str) -> int:
total = 0
# pairwise は隣接要素のタプルを逐次返す
for cur, nxt in pairwise(s):
if ROMAN_TO_VALUE[cur] < ROMAN_TO_VALUE[nxt]:
total -= ROMAN_TO_VALUE[cur]
else:
total += ROMAN_TO_VALUE[cur]
total += ROMAN_TO_VALUE[s[-1]]
return total
テストケース
s = Solution()
test_cases = [
(1, "I", 1),
(2, "III", 3),
(3, "IV", 4),
(4, "IX", 9),
(5, "LVIII", 58),
(6, "XC", 90),
(7, "CD", 400),
(8, "CM", 900),
(9, "MCMXCIV", 1994),
(10, "MMMCMXCIX", 3999)
]
for no, roman, expected in test_cases:
got = s.romanToInt(roman)
assert got == expected, f"Test No{no} failed for '{roman}'. expected {expected}, got {got}"
学び・気づき
- ローマ数字を初めて理解した
- 逆順にすれば終端処理をしなくて済むことがわかったし、計算量にも大きな影響ではない
- itertoolsは便利なものがいっぱいあるので、使い方を学びアウトプットしてみる
Discussion