目录

题目描述

193. 有效电话号码

题意分析

文件 file.txt 每行是一个电话号码,合法格式只有两种:xxx-xxx-xxxx(xxx) xxx-xxxx,其中每个 x 都必须是十进制数字。题目保证每行前后没有多余空格。

这不是“行中包含一个电话号码”即可,而是整行必须完整符合格式。因此正则必须同时使用 ^$ 锚定行首和行尾,否则 abc987-123-4567xyz 也可能被误匹配。

两种格式只有前半段不同:要么是 三位数字 + 连字符,要么是 左括号 + 三位数字 + 右括号 + 一个空格;后半段共同为 三位数字-四位数字。把公共后缀提出来,可以避免写两份重复正则。

官方样例中的 987-123-4567(123) 456-7890 合法,而 123 456 7890 因分隔符不符合任一格式被过滤。

解法:grep 扩展正则匹配整行

核心思路

使用 grep -E 启用扩展正则表达式。前缀写成两个分支:

  • \([0-9]{3}\) 匹配 (123) ,括号需要转义,右括号后必须恰好有一个空格;
  • [0-9]{3}- 匹配 123-

两个分支之后共享 [0-9]{3}-[0-9]{4}。最外层用 ^...$ 包围,保证行内没有额外字符。

这里使用 [0-9] 而不是 \d\d 属于 PCRE 等方言,在 POSIX ERE 的 grep -E 中不具备可移植的“数字”含义。

解题步骤

  • ^ 固定匹配起点为行首。
  • 匹配括号格式或连字符格式的三位区号。
  • 匹配公共的 三位数字-四位数字 后缀。
  • $ 要求匹配在行尾结束。
  • grep 原样输出所有匹配行,无需额外 awksed

代码实现

grep -E '^(\([0-9]{3}\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}$' file.txt

复杂度分析

  • 时间复杂度:$O(N)$,其中 $N$ 是文件总字符数;正则长度固定,grep 逐行扫描每个字符常数次。
  • 空间复杂度:相对整个文件为 $O(L)$,其中 $L$ 是最长单行长度,工具只需缓冲当前行;固定正则本身占 $O(1)$ 空间。

关键点总结

  • 整行格式校验必须使用首尾锚点,不能只匹配一个合法子串。
  • 括号在正则中有分组含义,匹配字面括号必须写成 \(\)
  • (xxx) 后的空格属于格式的一部分,不能写成任意空白或直接省略。
  • 把两个格式的公共后缀提取出来,能显著降低分支写错的概率。
  • 面试追问可移植性时,说明 grep -E 使用 POSIX ERE;grep -P\d 依赖 PCRE 支持,并非所有 Unix 环境都有。

易错点总结

  • 省略 ^$tel: 987-123-4567 会因包含合法子串而被误判,但整行格式并不合法。
  • 忘记转义括号([0-9]{3}) 在正则中只是分组,不会要求输入真的出现 ()
  • 使用 \d 配合 grep -E:在 POSIX ERE 中它不是可移植的数字类,应使用 [0-9]
  • 漏掉右括号后的空格:会错误接受 (123)456-7890,同时拒绝题目规定的 (123) 456-7890 写法。
  • 把连字符写成可选:例如 [ -]? 会额外接受 123 456-78901234567890,超出题目允许的两种格式。
  • 使用基本 grep 却保留 ERE 的 |{3} 写法:不同实现下可能按字面量解释;应显式加 -E

相似题目

题目 难度 考察点
192. 统计词频 中等 Shell 管道中的分词、聚合和排序
194. 转置文件 中等 使用 awk 动态字段访问重组文本
195. 第十行 简单 按精确行号筛选并提前结束读取