LeetCode.10.正则表达式匹配题解

341 阅读4分钟

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. 处理边界条件
  2. 选择数据结构
  3. 遍历处理逻辑
  4. 结果判定条件

边界条件

首先限制边界条件,即:

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()
    }

其他思路

力扣上的官方解题思路是通过动态规划实现的,可参考下方的官方题解。

官方题解:leetcode.cn/problems/re…

存疑

但是在测试 case 中,存在一组 case :

字符串:"aab" 
字符规律:"c*a*b"
预期结果:true

但题目描述为必须完全匹配,我认为是存在一些表述不清的。如果要求字符串和字符规律完全匹配 。这组用例的结果应该是 false。

评论区也有一些相同的困惑:

image-20220717140035919.png

按照这个思路,题目的意思是字符串要满足字符规律中的后半部分或全部。