趣题:用正则表达式判断一个二进制数是否能被3整除

    我们之前已经见过了正则表达式的一些很特殊的用法。这里我们再来看一个:用正则表达式判断数的整除性。例如,下面这个表达式可以匹配01串S当且仅当S是一个可以被3整除的二进制数。

^1((10*1)|(01*0))*10*$

    如果你不信的话,不妨把下面这段代码粘贴进浏览器的地址栏,然后回车运行一下:

javascript:alert(/^1((10*1)|(01*0))*10*$/.test("1000000100"))

    被test的是516的二进制表达。516可以被3整除,因此程序返回true。你可以自己把1000000100换成其它的二进制数试试。
    但是呢,从这个正则表达式里我们竟看不出任何端倪。奇怪了,为什么这个正则表达式可以用于判断整除性?能被3整除的二进制数究竟有何规律?

 
 
    其实,能被3整除的二进制数并没有什么明显的规律。这个正则表达式的求法可以说是相当暴力的。这一切的谜底很简单——判断一个数的整除性能轻易地用有限状态自动机实现,而有限状态自动机又可以翻译成正则表达式。

  

    注意到,一个二进制数后面加一个“0”相当于该数乘以2,一个二进制数后面加一个“1”相当于该数乘2加1。设定三个状态,分别叫做0、1和2,它们表示当前的数除以3所得的余数。如果对于某个i和j,有i*2≡j (mod 3),就加一条路径i→j,路径上标一个字符“0”;如果i*2+1≡j (mod 3),则在路径i→j上标记“1”。状态0既是我们的初始状态,也是我们的最终状态。我们的自动机就做好了。现在,假如二进制数10010走进来了。从状态0出发,机器首先读到一个“1”,于是当前位置挪到状态1,表明目前该数模3余1;然后,系统读了一个“0”,我们紧跟着走到状态2,表明二进制数“10”被3除余2;下一步,我们回到状态1,表明“100”除以3余1;再往后,我们得知“1001”能被3整除。最后呢,我们读到一个0,“1001”的两倍当然还是能被3整除,我们依旧停留在原位。我们得到结论:二进制数10010能被3整除。
    有限状态自动机是可以转化为正则表达式的。上面的这个自动机转化起来非常容易。我们可以先试着用自然语言叙述一下。首先,每个二进制数第一位必然为“1”。到达状态1后,我们可以随意地、任意多次地在状态1周围绕圈圈,最终回到状态1。临近末尾,我们再读到一个“1”返回状态0,这之后随便读多少个“0”都可以了。现在问题分解为:我们又如何用正则表达式表述“从状态1出发随意地走最终回到状态1”呢?在本例中,这是很好描述的:它可以是字符串“1000..001”和“0111..110”的任意组合。把这些东西用正则表达式写出来,就是我们刚才那个神秘的式子:1((10*1)|(01*0))*10* 。
    从原理上说,我们可以这种方法求出任意进制下用于任意数的整除性判断的正则表达式。只不过,它们所对应的自动机都更加复杂,从而得出一长串令人眼花缭乱的表达式。本文开头我把1((10*1)|(01*0))*10*的来源作为一个有趣的问题提出来,因为这个式子恰好不长,让人很难想到这背后藏有一个普适的暴力算法。

强大的“反引用”:http://www.sxnsx.com/niubility-regular-and-divisibility-deciding/

<think>好的,我需要帮用户解决四个正则表达式的问题。先逐一分析每个问题的要求,再考虑如何构建合适的正则表达式。 第一个问题是“以01结尾的二进制串”。二进制串由0和1组成,必须以01结尾。所以,前面可以是任意0或1的组合,包括空字符串吗?不过通常二进制可能至少有一个字符,但题目没有明确是否允许空。不过以01结尾的话,最短的是01本身。所以正则表达式应该是^(0|1)*01$。这里用^和$来确保整个字符串匹配,中间可以有任意量的0或1,最后以01结尾。 第二个问题“能被5整除的十进制整”。十进制整不能以0开头,除非是0本身。能被5整除最后一位是0或5。但需要考虑整个的有效性。例如,0是合法的,5、10、15等等。正则表达式需要匹配两种情况:以0结尾或者以5结尾,并且至少有一位字。但要注意,不能有前导零,比如0123是不允许的,除了单独的0。所以需要分情况:一种是0,另一种是非零字开头,后面跟任意字,最后以0或5结尾。所以正则表达式可以写成^(0|([1-9]\d*[05]))$。这里[1-9]确保不以0开头,后面跟任意字,最后以0或5结束。 第三个问题“包含奇个1或奇个0的二进制串”。二进制串由0和1组成,且其中1的个是奇,或者0的个是奇,或者两者都是奇。这里需要注意,当同时有奇的1和0时,总长度是偶,而如果其中一个一个,总长度是奇。可能需要分开处理两种情况,或者找到一个更简洁的方式。但可能需要考虑异或的情况。不过正则表达式处理奇偶性通常需要利用结构,比如偶次用(..)*,奇次则加一个额外的字符。例如,奇个1的正则表达式可以表示为^0*(10*10*)*1$,类似地,奇个0的是^1*(01*01*)*0$。但题目是“或”,所以需要将两者结合起来,但需要注意可能会有同时满足的情况。因此,整体正则表达式应该是两者的联合,使用|运算符,但需要确保没有歧义。可能需要写成^(0*(10*10*)*1|1*(01*01*)*0)$。不过这样可能会漏掉同时满足的情况,不过根据逻辑,“或”关系下,只要满足其中一个即可。因此这个表达式应该可以覆盖所有情况。 第四个问题“英文字母组成的所有符号串,要求符号串中的字母依照字典序排列”。这里字典序排列可能指的是严格递增,比如abc,而不是abba。每个后续字母必须大于或等于前一个。例如,aab是允许的,因为a <= a <= b,但acb不允许,因为c之后是b,不符合顺序。正则表达式需要匹配这样的字符串。可以使用正向先行断言来确保每个字符都不小于前一个。但正则表达式本身在处理这种递增关系时比较困难,因为需要记住前一个字符。可能需要逐个位置检查。例如,对于每个字符,后面的字符应该大于等于前一个。可以用类似^(a*)(b*)(c*)...$(直到z),但这只处理严格分组的情况,无法处理混合的情况,比如aabbcc。更灵活的方式是使用类似^(a*b*c*...z*)$,但这样每个字母只能出现一次,并且顺序固定,但实际上允许重复,比如aabbbcc。所以正确的做法是允许每个字母后面跟随大于等于它的字母。例如,使用类似^(a*)(b*)(c*)...$,但用正则表达式实现的话,可以写成^a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*w*x*y*z*$,不区分大小写的话可能需要添加A-Z,但题目说是英文字母,可能包括大小写?不过用户可能要求的是小写或大写,但题目没有说明,假设是不区分大小写或者全小写。不过问题中的“字典序”通常指同一大小写的顺序。所以如果是全小写的话,正则表达式可以写成^a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*w*x*y*z*$,但这样只能匹配严格按顺序排列且每个字母出现多次的情况,比如aabbcc确实符合,但中间不能有后面的字母再出现前面的。例如,abac是不允许的,因为c在a之后又出现了a。所以这个正则表达式是正确的,因为它强制每个字母只能在其顺序位置出现,并且之后的字母只能在其后。但这样无法处理像abc这样的混合情况,比如a后面可以有b,再c,但原正则表达式允许a*b*c*,所以abc会被匹配,因为a后面有b,然后c,符合a*后面跟b*,以此类推。是的,这个正则表达式可以正确匹配所有按字典序排列的字符串,无论是否有重复。例如,aabbcc会匹配,因为a*后面是b*,再c*,而abc同样符合,因为a*(1个a),b*(1个b),c*(1个c)。而像bcd这样的字符串,前面没有a,但正则表达式中a*可以为零,所以允许。不过这样写的话,正则表达式会非常长,有没有更简洁的方式?或者是否可以使用某种递归或正向断言? 另一种方法是使用类似^(?!.*ba)等来排除后面的字母出现在前面字母之后的情况,但这样需要为每一对字母添加负向断言,非常繁琐。例如,要确保没有b后面有a,没有c后面有a或b,等等。这可能不太现实,因为需要排除所有逆序的情况。 因此,最可行的还是按字母顺序排列的正则表达式,允许每个字母出现零次或多次,并按顺序排列。即: ^[a]*[b]*[c]*... [z]*$ (假设小写) 因此,对于第四个问题,正确的正则表达式应该是按照字母顺序,每个字母部分可以出现零次或多次,并且顺序不能打乱。所以最终的表达式应该如上面所述。</think>以下是四个问题的正则表达式及其解释: --- ### **(1) 以 `01` 结尾的二进制串** **正则表达式**: ```regex ^[01]*01$ ``` **解释**: - `^[01]*`:以任意量的 `0` 或 `1` 开头(包括空字符串)。 - `01$`:必须以 `01` 结尾。 **示例**:`"101"`(无效),`"1101"`(有效)。 --- ### **(2) 能被5整除的十进制整** **正则表达式**: ```regex ^(0|([1-9]\d*[05]))$ ``` **解释**: - `^0$`:单独的字 `0` 是合法的。 - `[1-9]\d*[05]`:非零开头的整,最后一位是 `0` 或 `5`(能被5整除的特征)。 - 排除前导零(如 `0123`)。 **示例**:`"5"`(有效),`"100"`(有效),`"123"`(无效)。 --- ### **(3) 包含奇个1或奇个0的二进制串** **正则表达式**: ```regex ^(?=([^1]*1[^1]*1[^1]*)*$)(.*)|(?=([^0]*0[^0]*0[^0]*)*$)(.*)$ ``` **简化版本**(分步匹配): ```regex ^(0*(10*10*)*1|1*(01*01*)*0)$ ``` **解释**: - **奇个1**:`0*(10*10*)*1`,即 `1` 出现奇次。 - **奇个0**:`1*(01*01*)*0`,即 `0` 出现奇次。 - 两者满足其一即可(逻辑“或”)。 **示例**:`"010"`(奇个0,有效),`"111"`(奇个1,有效)。 --- ### **(4) 按字典序排列的英文字母串** **正则表达式**(仅小写字母): ```regex ^a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*w*x*y*z*$ ``` **解释**: - 每个字母段(如 `a*`)表示该字母可重复任意次(包括0次)。 - 严格遵循字母顺序,不允许逆序(如 `ba` 无效)。 **示例**:`"aabbcde"`(有效),`"abdz"`(有效),`"acb"`(无效)。 --- ### **总结** | 问题 | 正则表达式 | |------|------------| | (1) 以 `01` 结尾的二进制串 | `^[01]*01$` | | (2) 能被5整除的十进制整 | `^(0|([1-9]\d*[05]))$` | | (3) 含奇个1或0的二进制串 | `^(0*(10*10*)*1|1*(01*01*)*0)$` | | (4) 按字典序排列的字母串 | `^a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*w*x*y*z*$` | **注意**:正则表达式需根据实际需求调整(如大小写敏感、允许空串等)。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值