Description

實作正則表達式匹配,支援 .(匹配任意單一字元)和 *(匹配零個或多個前一個字元)。匹配需要覆蓋整個字串。

Example:

Input: s = “aab”, p = “cab” Output: true (c* 匹配零個 c, a* 匹配兩個 a, b 匹配 b)

Intuition#

  • * 不會單獨出現,總是跟在某個字元後面形成「x*」的組合
  • 兩種情況:
    • p[j-1] 不是 *:直接比較 s[i-1]p[j-1]
    • p[j-1]*
      • 匹配零次:dp[i][j] = dp[i][j-2](跳過「x*」)
      • 匹配一次以上:dp[i][j] = dp[i-1][j](前提是 s[i-1] 匹配 p[j-2])

Approaches#

1: 遞迴 + 記憶化#

  • 概念: 從頭開始匹配,遇到 * 時分支處理,用 memo 快取結果。
  • 時間複雜度: O(m * n)
  • 空間複雜度: O(m * n)
class Solution {
    fun isMatch(s: String, p: String): Boolean {
        val memo = Array(s.length + 1) { arrayOfNulls<Boolean>(p.length + 1) }
        return dp(s, p, 0, 0, memo)
    }

    private fun dp(s: String, p: String, i: Int, j: Int, memo: Array<Array<Boolean?>>): Boolean {
        if (memo[i][j] != null) return memo[i][j]!!
        val result: Boolean
        if (j == p.length) {
            result = i == s.length
        } else {
            val firstMatch = i < s.length && (p[j] == '.' || s[i] == p[j])
            result = if (j + 1 < p.length && p[j + 1] == '*') {
                dp(s, p, i, j + 2, memo) || (firstMatch && dp(s, p, i + 1, j, memo))
            } else {
                firstMatch && dp(s, p, i + 1, j + 1, memo)
            }
        }
        memo[i][j] = result
        return result
    }
}

⭐2: Bottom-Up DP#

  • 概念: 建立 (m+1) x (n+1) 的表格,從後往前或從前往後填寫。
  • 時間複雜度: O(m * n)
  • 空間複雜度: O(m * n)
class Solution {
    fun isMatch(s: String, p: String): Boolean {
        val m = s.length
        val n = p.length
        val dp = Array(m + 1) { BooleanArray(n + 1) }
        dp[0][0] = true
        // 處理 p 的前綴匹配空字串(如 a*, a*b*, ...)
        for (j in 2..n) {
            if (p[j - 1] == '*') {
                dp[0][j] = dp[0][j - 2]
            }
        }
        for (i in 1..m) {
            for (j in 1..n) {
                if (p[j - 1] == '*') {
                    // 匹配零次
                    dp[i][j] = dp[i][j - 2]
                    // 匹配一次以上
                    if (p[j - 2] == '.' || p[j - 2] == s[i - 1]) {
                        dp[i][j] = dp[i][j] || dp[i - 1][j]
                    }
                } else {
                    if (p[j - 1] == '.' || p[j - 1] == s[i - 1]) {
                        dp[i][j] = dp[i - 1][j - 1]
                    }
                }
            }
        }
        return dp[m][n]
    }
}

🔑 Takeaways#

  • Pattern: 字串匹配 DP — 處理特殊匹配符號的轉移邏輯
  • 關鍵技巧: * 的關鍵是「匹配零次 vs 一次以上」的分支;初始化時要處理 a*b* 這種能匹配空字串的 pattern