罗马数字规则

1. 题目考点

这道题主要考:

  1. 字符串判断
  2. 正则表达式 re
  3. 罗马数字规则
  4. 整体匹配:从开头到结尾都必须合法

这题不是让你把罗马数字转换成整数,而是判断一个字符串是不是一个合法的罗马数字。


2. 审题

题目给你一个字符串,比如:

CDXXI

你要判断它是不是 1 到 3999 之间的合法罗马数字。

如果合法,输出:

True

否则输出:

False

关键点是:题目要求用 regular expression 正则表达式 来判断。


3. 思路提示:不要一开始想完整正则

这道题如果直接想完整表达式,会很乱。正确思路是把罗马数字按位拆开。

罗马数字可以分成四个部分:

千位 + 百位 + 十位 + 个位

例如:

CDXXI

可以看成:

CD + XX + I

也就是:

400 + 20 + 1 = 421

所以我们不是一次性判断整个字符串,而是分别判断:

千位是否合法
百位是否合法
十位是否合法
个位是否合法

最后把四部分拼成一个完整正则表达式。


4. 完整设计思路

第一步:处理千位

题目范围是 1 到 3999。

千位只能是:

空
M
MM
MMM

也就是最多 3 个 M

正则可以写成:

M{0,3}

意思是:

M 出现 0 到 3 次

第二步:处理百位

百位可能是:

100  = C
200  = CC
300  = CCC
400  = CD
500  = D
600  = DC
700  = DCC
800  = DCCC
900  = CM

观察一下,百位合法情况可以分成三类:

CM             表示 900
CD             表示 400
D?C{0,3}       表示 0、100、200、300、500、600、700、800

所以百位正则是:

(CM|CD|D?C{0,3})

这里:

|

表示“或者”。

D? 表示 D 可以出现 0 次或 1 次。

C{0,3} 表示 C 可以出现 0 到 3 次。


第三步:处理十位

十位和百位规则完全类似,只是字母换了。

十位可能是:

10  = X
20  = XX
30  = XXX
40  = XL
50  = L
60  = LX
70  = LXX
80  = LXXX
90  = XC

所以十位正则是:

(XC|XL|L?X{0,3})

第四步:处理个位

个位可能是:

1 = I
2 = II
3 = III
4 = IV
5 = V
6 = VI
7 = VII
8 = VIII
9 = IX

所以个位正则是:

(IX|IV|V?I{0,3})

第五步:拼成完整表达式

四部分合起来:

M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})

但是还不够。

因为我们要判断整个字符串都符合规则,所以要加:

^

表示从字符串开头开始匹配。

还要加:

$

表示一直匹配到字符串结尾。

最终正则:

^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$

5. 代码实现

import re

regex_pattern = r"^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$"

s = input()

print(str(bool(re.match(regex_pattern, s))))

如果想更严格一点,避免空字符串也被匹配,可以写成:

import re

regex_pattern = r"^(?=.)M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$"

s = input()

print(str(bool(re.match(regex_pattern, s))))

这里的:

(?=.)

意思是要求字符串至少有一个字符。HackerRank 这题一般输入本身就是一行罗马字符,所以第一种写法通常已经够用。


6. 运行演示

以样例:

CDXXI

程序会这样判断:

千位:没有 M,合法
百位:CD,表示 400,合法
十位:XX,表示 20,合法
个位:I,表示 1,合法

所以整个字符串合法,输出:

True

再看一个非法例子:

IIV

它想表达 3 或 5 附近的数字,但罗马数字里不能这样写。

个位部分允许:

I
II
III
IV
V
VI
VII
VIII
IX

没有 IIV 这种写法,所以输出:

False

再看:

MMMM

因为 3999 最大是:

MMMCMXCIX

千位最多只能有 3 个 M,所以 MMMM 不合法,输出:

False

7. 方法总结

这类题的关键不是马上写代码,而是先把规则拆小。

遇到“判断某种格式是否合法”的题,可以按这个顺序想:

  1. 这个字符串可以拆成哪几部分?
  2. 每一部分有哪些合法情况?
  3. 每一部分能不能用正则表达式表示?
  4. 最后用 ^$ 限制必须整体匹配。

本题的核心拆法就是:

罗马数字 = 千位 + 百位 + 十位 + 个位

所以正则也按这四部分写。


8. 练习

请判断下面这些罗马数字是否合法,并说明原因:

XIX
IL
MMMDCCCLXXXVIII
VX
CMXCIX

提示:

先不要急着查答案,按照四部分拆:

千位 + 百位 + 十位 + 个位

特别注意:

4、9、40、90、400、900

这些数有特殊写法。

还是不太理解,处理千位和处理百位这2块内容

先抓住一个核心:罗马数字是按“位”拆的

我们平时写阿拉伯数字,比如:

1421

可以拆成:

1000 + 400 + 20 + 1

罗马数字也可以按这个思路拆:

MCDXXI

拆成:

M + CD + XX + I

也就是:

1000 + 400 + 20 + 1

所以这道题的正则不是乱写的,而是按下面四块拼起来的:

千位 + 百位 + 十位 + 个位

你现在主要卡在:

M{0,3}

和:

(CM|CD|D?C{0,3})

我们单独讲这两块。


一、处理千位:为什么是 M{0,3}

罗马数字里:

M = 1000

题目限制数字范围是:

1 到 3999

所以千位最多只能到 3000。

也就是说,千位部分只可能有这几种情况:

数值罗马写法
0
1000M
2000MM
3000MMM

注意,0 的意思不是整个数字是 0,而是这个数字可能没有千位。

例如:

421 = CDXXI

它没有千位,所以千位部分就是“空”。

所以千位部分可以写成:

M{0,3}

它的意思是:

M 出现 0 次到 3 次

具体对应关系是:

M 出现 0 次:空
M 出现 1 次:M
M 出现 2 次:MM
M 出现 3 次:MMM

所以:

M{0,3}

能匹配:

空
M
MM
MMM

但是不能匹配:

MMMM

因为 MMMM 是 4000,超过题目范围 3999。


二、处理百位:先列出所有合法百位

百位用到这几个字母:

C = 100
D = 500
M = 1000

百位部分可能是 0 到 900。

完整列出来:

百位数值罗马写法
0
100C
200CC
300CCC
400CD
500D
600DC
700DCC
800DCCC
900CM

这一张表非常关键。

我们现在要用正则表达式把这 10 种情况表示出来。


三、百位为什么分成三类

百位的合法写法可以分成三类:

900:CM
400:CD
普通情况:空、C、CC、CCC、D、DC、DCC、DCCC

所以正则写成:

(CM|CD|D?C{0,3})

这个表达式中:

|

表示“或者”。

所以它的意思是:

CM
或者 CD
或者 D?C{0,3}

也就是:

900 的情况
或者 400 的情况
或者普通情况

四、重点理解 D?C{0,3}

我们单独看:

D?C{0,3}

它由两部分组成:

D?

和:

C{0,3}

1. D? 是什么意思

D?

表示:

D 可以出现 0 次或 1 次

也就是说,可以没有 D,也可以有一个 D

不能有两个 D,因为百位里面不会出现 DD


2. C{0,3} 是什么意思

C{0,3}

表示:

C 可以出现 0 次到 3 次

也就是:

空
C
CC
CCC

3. 合起来看 D?C{0,3}

现在把它们组合起来:

D?C{0,3}

意思是:

前面可以有一个 D,也可以没有 D;
后面可以跟 0 到 3 个 C。

所以它可以匹配下面这些:

D? 部分C{0,3} 部分合起来表示
0
CC100
CCCC200
CCCCCC300
DD500
DCDC600
DCCDCC700
DCCCDCCC800

所以:

D?C{0,3}

刚好负责这些情况:

空、C、CC、CCC、D、DC、DCC、DCCC

也就是:

0、100、200、300、500、600、700、800

五、那 400 和 900 为什么要单独写?

因为 400 和 900 是特殊写法。

罗马数字里有一种“左小右大表示减法”的规则。

CD = 500 - 100 = 400
CM = 1000 - 100 = 900

所以:

400 不能写成 CCCC
900 不能写成 DCCCC

它们必须写成:

400 = CD
900 = CM

所以百位正则必须把这两个特殊情况单独列出来:

CM|CD

再加上普通情况:

D?C{0,3}

于是百位完整写法就是:

(CM|CD|D?C{0,3})

六、用例子看一下

例子 1:CDXXI

我们只看前两块:

CDXXI

千位部分:

没有 M

所以匹配:

M{0,3}

里面的“0 个 M”。

百位部分:

CD

它匹配:

(CM|CD|D?C{0,3})

里面的:

CD

所以前两块合法。


例子 2:MCMXCIX

这是 1999。

拆开看:

M + CM + XC + IX

千位:

M

匹配:

M{0,3}

百位:

CM

匹配:

(CM|CD|D?C{0,3})

里面的:

CM

所以前两块合法。


例子 3:DCCC

这是 800。

千位:

没有 M

匹配 M{0,3} 里的 0 个 M。

百位:

DCCC

可以拆成:

D + CCC

对应:

D?C{0,3}

其中:

D? 匹配 D
C{0,3} 匹配 CCC

所以 DCCC 合法。


例子 4:CCCC

这是非法的。

为什么?

百位部分如果是 400,必须写成:

CD

不能写成:

CCCC

而:

D?C{0,3}

最多只允许 3 个 C

所以:

CCC

可以,但:

CCCC

不可以。


七、把千位和百位连起来理解

现在只看前半部分正则:

M{0,3}(CM|CD|D?C{0,3})

它表示:

先匹配 0 到 3 个 M
再匹配合法的百位

比如:

MCD

拆成:

M + CD

含义是:

1000 + 400 = 1400

再比如:

MMCM

拆成:

MM + CM

含义是:

2000 + 900 = 2900

再比如:

MMMDCCC

拆成:

MMM + DCCC

含义是:

3000 + 800 = 3800

八、你可以这样记

千位很好记:

M{0,3}

因为千位只有:

空、M、MM、MMM

百位这样记:

(CM|CD|D?C{0,3})

其中:

CM:900
CD:400
D?C{0,3}:普通百位,负责 0、100、200、300、500、600、700、800

也就是说,百位不是随便写的,而是:

特殊的 900 + 特殊的 400 + 普通情况

练习

先只练千位和百位,不看十位个位。

判断下面这些“千位 + 百位”部分是否合法:

MMMCM
MMCD
MCCCC
DCCC
MMMM
CM

提示:

先问自己两个问题:

1. 前面的 M 有没有超过 3 个?
2. 百位部分是不是 CM、CD,或者 D?C{0,3} 能表示的情况?

尤其注意:

CCCC 不合法
MMMM 不合法
CM 合法,表示 900
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇