
本文介绍如何使用正则表达式配合零宽断言,在单个数字字符串(如 '12345')中完整提取所有可能的一位数和两位数子序列(如 1,2,3,4,5,12,23,34,45),解决普通贪婪/非贪婪匹配无法覆盖重叠窗口的问题。
本文介绍如何使用正则表达式配合零宽断言,在单个数字字符串(如 `'12345'`)中完整提取所有可能的一位数和两位数子序列(如 `1,2,3,4,5,12,23,34,45`),解决普通贪婪/非贪婪匹配无法覆盖重叠窗口的问题。
在处理数字字符串时,若需枚举所有长度为 1 或 2 的连续子数字串(尤其要求重叠匹配,例如 '123' 中应同时捕获 '1', '2', '3', '12', '23'),直接使用 \d|\d{2} 或 \d(\d)? 会失败——因为正则引擎默认按“消费字符”方式匹配,一旦匹配了 '12',下一个匹配就从 '3' 开始,导致遗漏 '2' 和 '23' 等重叠项。
正确解法是借助正向先行断言((?=...)):它不消耗字符,仅检查位置后的模式是否成立,从而允许在同一位置发起多次匹配。核心正则表达式为:
/(?=((\d)\d?))/
- (?=...) 是零宽正向先行断言,确保匹配位置后存在符合内部模式的子串;
- ((\d)\d?) 是捕获组结构:
- 外层 (...)(Group 1)捕获整个匹配内容(1 或 2 位数字);
- 内层 (\d)(Group 2)仅捕获第一个数字(即该窗口的起始数字);
- 当匹配到两位数(如 '12'),Group 1 = '12',Group 2 = '1'(二者不等);
- 当匹配到一位数(如末尾的 '5'),\d? 匹配空,Group 1 = '5',Group 2 = '5'(二者相等)。
配合 PREG_SET_ORDER 标志,preg_match_all() 将每个匹配结果组织为独立数组,便于逐个判断并分类存储。
以下是完整、健壮的 PHP 实现:
$s = "12345";
$res = [];
if (preg_match_all('/(?=((\d)\d?))/', $s, $m, PREG_SET_ORDER)) {
$singles = [];
$doubles = [];
foreach ($m as $match) {
$full = $match[1]; // Group 1: 完整匹配("1", "12", "2", "23", ...)
$first = $match[2]; // Group 2: 首位数字("1", "1", "2", "2", ...)
if ($full === $first) {
// 单数字:Group 1 与 Group 2 相同 → 仅一位数
$singles[] = $full;
} else {
// 双数字:Group 1 是两位数,Group 2 是其首位
$singles[] = $first; // 添加首位(如 "1" 来自 "12")
$doubles[] = $full; // 添加两位数(如 "12")
}
}
// 合并:先所有单数字(按出现顺序),再所有双数字(按出现顺序)
$res = array_merge($singles, $doubles);
}
print_r($res);
// 输出:Array ( [0] => 1 [1] => 2 [3] => 3 [4] => 4 [5] => 5 [6] => 12 [7] => 23 [8] => 34 [9] => 45 )✅ 关键注意事项:
- 此方案依赖零宽断言实现重叠扫描,不可替换为普通捕获;
- PREG_SET_ORDER 是必需的,否则 $m 结构将按组索引组织,难以遍历;
- 逻辑上需区分单/双数字:通过比较 Group 1 与 Group 2 是否相等来判断;
- 若字符串含非数字字符(如 'a12b34'),建议先清理或调整正则为 (?=(\d{1,2})) 并额外校验边界(如 (?=(\d{1,2})(?!\d)) 防止三位数截断);
该方法兼顾准确性与可扩展性——若后续需支持三位数,只需将 \d? 改为 \d{0,2} 并增加分组判别逻辑,是处理滑动数字窗口问题的标准范式。

















