LeetCode.10.正则表达式匹配
题目描述
给你一个字符串s和一个字符规律 p ,请你来实现一个支持 '.'和'*'的正则表达式匹配。
'.'匹配任意单个字符'*'匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖整个字符串 s 的,而不是部分字符串。
用例:
输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。
============================================
输入:s = "aa", p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
============================================
输入:s = "ab", p = ".*"
输出:true
解释:".*" 表示可匹配零个或多个('*')任意字符('.')。
提示: 1 <= s.length <= 20 1 <= p.length <= 30 s 只包含从 a-z 的小写字母。 p 只包含从 a-z 的小写字母,以及字符 . 和 * 。 保证每次出现字符 * 时,前面都匹配到有效的字符。
解题思路
正则表达式匹配是一个对特殊字符串进行处理的题目,所以需要至少遍历一遍字符串中的字符。然后根据规则变化字符内容。
根据这道题目的描述,可以通过下面四个步骤进行:
- 处理边界条件
- 选择数据结构
- 遍历处理逻辑
- 结果判定条件
边界条件
首先限制边界条件,即:
1. 1 <= s.length <= 20
2. 1 <= p.length <= 30
3. s 只包含从 a-z 的小写字母。
4. p 只包含从 a-z 的小写字母,以及字符 . 和 * 。
val CHAR = arrayOf(
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'g', 'k',
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z')
val MATCH_CHAR = arrayOf(
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'g', 'k',
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '.', '*')
// 限制长度
if (s.length < 1 || s.length > 20) {
return false
}
if (p.length < 1 || p.length > 30) {
return false
}
// 过滤不合法字符
s.forEach {
if (!CHAR.contains(it)) {
return false
}
}
p.forEach {
if (!MATCH_CHAR.contains(it)) {
return false
}
}
这里的不合法字符判断可以使用 ASCII 码值范围来优化。
选择数据结构
通过使用队列结构来处理字符串和字符规律,这样每处理一个表达式字符时,就能够排除一部分字符串,最后如果整个字符串都可以用字符规律表达式覆盖,那么就可以返回 true 了。
// 分别将 s 和 p 转换为队列结构
val queue = LinkedList(s.toCharArray().toList())
val expressionQueue = LinkedList(p.toCharArray().toList())
字符匹配逻辑
针对不同的字符表达式,可以将其视为不同的状态,整个匹配逻辑就可以抽象成一个状态机:
// 记录 * 号的前置字符
var prev: Char? = null
// 遇到 * 时,将排除的相同字符串记录下来,用来处理后续存在相同字符的规则时,过滤多余规则
// 例如 aa, a*a ,当遇到 * 时,starTempQueue = [a] , 执行下一个 a 规则时,从 starTempQueue
// 中取出一个 a ,确保 * 后的相同字符满足规则。
var starTempQueue = LinkedList<Char>()
// 状态机
while (expressionQueue.isNotEmpty()) {
// 每次取出一个字符规则
val current = expressionQueue.pop()
when(current) {
'.' -> { // '.' 匹配任意单个字符
if (queue.isNotEmpty()) {
queue.pop()
}
}
'*' -> {
// 同一个字符串存在多个 * 时,上一个和下一个 * 代表的字符相同时,不做多余处理
if (prev != starTempQueue.peek()) {
starTempQueue.clear()
}
if (prev == null) {
return false // 确保 * 号前一定有字符
} else if (prev == '.') {
prev = queue.peek()
}
while (queue.peek() == prev) {
starTempQueue.push(queue.pop()) // 过滤相同字符
}
}
else -> {
if (queue.isNotEmpty() && current == queue.peek()) {
queue.pop()
} else {
// 在存在多余 * 的情况下进行处理,因为 * 可以代表 0 个字符
if (starTempQueue.isNotEmpty() && current == starTempQueue.peek()) {
starTempQueue.pop()
} else {
return false
}
}
}
}
prev = current
}
最终判定条件
最终的判定条件是通过给定字符串遍历结束,如果便利结束后全部满足字符规律,即是匹配的,所以这里取的是 queue.isEmpty() 。
完整代码
fun isMatch(s: String, p: String): Boolean {
val CHAR = arrayOf('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'g', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z')
val MATCH_CHAR = arrayOf('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'g', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '.', '*')
// 1. 过滤不合法字符
s.forEach {
if (!CHAR.contains(it)) {
return false
}
}
p.forEach {
if (!MATCH_CHAR.contains(it)) {
return false
}
}
// 2. 处理 * 尾部字符
val queue = LinkedList(s.toCharArray().toList())
val expressionQueue = LinkedList(p.toCharArray().toList())
var prev: Char? = null
var starTempQueue = LinkedList<Char>()
while (expressionQueue.isNotEmpty()) {
val current = expressionQueue.pop()
when(current) {
'.' -> {
if (queue.isNotEmpty()) {
queue.pop()
}
}
'*' -> {
if (prev != starTempQueue.peek()) {
starTempQueue.clear()
}
if (prev == null) {
return false
} else if (prev == '.') {
prev = queue.peek()
}
while (queue.peek() == prev) {
starTempQueue.push(queue.pop())
}
}
else -> {
if (queue.isNotEmpty() && current == queue.peek()) {
queue.pop()
} else {
if (starTempQueue.isNotEmpty() && current == starTempQueue.peek()) {
starTempQueue.pop()
} else {
return false
}
}
}
}
prev = current
}
return queue.isEmpty()
}
其他思路
力扣上的官方解题思路是通过动态规划实现的,可参考下方的官方题解。
存疑
但是在测试 case 中,存在一组 case :
字符串:"aab"
字符规律:"c*a*b"
预期结果:true
但题目描述为必须完全匹配,我认为是存在一些表述不清的。如果要求字符串和字符规律完全匹配 。这组用例的结果应该是 false。
评论区也有一些相同的困惑:
按照这个思路,题目的意思是字符串要满足字符规律中的后半部分或全部。