正则表达式匹配

132 阅读1分钟

leetcode 10 正则表达式匹配 一道比较有意思的动态规划算法题。

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。

  • '.' 匹配任意单个字符
  • '*' 匹配零个或多个前面的那一个元素

所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

下面简单记录下解题思路。

一、从右到左对字符串进行扫描

  • base如下:
  1. dp[0][0]=true,代表两个空字符串能互相匹配(尽管题目中表示1 <= s.length <= 20 1 <= p.length <= 20);

2) p空则不能匹配,略过;

3) s空,p不空,则只要考虑*的情况,则dp[0][j]取决于dp[0][j-2]的值,即dp[0][j] = dp[0][j-2]

  • 建立dp数组,dp[i][j] 表示s[...i]和 p[...j]能匹配;

把问题分为字符匹配和字符不匹配两种情况

  1. 字符匹配,则有s[i] 和 p[j] 完全匹配或者 p[j]为 . 两种情况;

2)考虑p[j]有*的情况,同样可分为字符匹配和字符不匹配两种情况:

不匹配即*匹配0次前面的字符;

匹配则又可分为*匹配0次,*匹配1次,*匹配多次.

/**
 * @param {string} s
 * @param {string} p
 * @return {boolean}
 */
var isMatch = function(s, p) {
    let m = s.length,n = p.length
    // dp[i][j] 表示s[...i]和 p[...j]能匹配
    let dp = new Array(m+1).fill().map(() => new Array(n+1).fill(false))
    dp[0][0] = true
    // s空 p不空
    for (let j = 1; j < n + 1; j++) {
        if (p[j-1] === '*') {
            dp[0][j] = dp[0][j-2]
        }
    }
    for (let i = 1; i < m + 1; i++) {
        for (let j = 1; j < n+1; j++) {
            // 字符匹配
            if (s[i-1] === p[j-1] || p[j-1] === '.') {
                dp[i][j] = dp[i-1][j-1]
            // p有*
            } else if (p[j-1] === '*') {
                if (s[i-1] === p[j-2] || p[j-2] === '.') {
                    // 分别匹配0/1/多次
                    dp[i][j] = dp[i][j-2] || dp[i-1][j-2] || dp[i-1][j]
                } else {
                    dp[i][j] = dp[i][j-2]
                }
            }
        }
    }
    return dp[m][n]
};

二、从左到右对字符串进行扫描

base如下:

1)p已经扫描完,此时如果s也刚好走完,则能匹配;

2)s已经扫描完,则此时p只有符合普通字符和成对出现的情况才能与s匹配,如 ab* ;

建立memo备忘录,memo[i][j]表示s[i...]和p[j...]能匹配。

同样把问题分为字符匹配和字符不匹配两种情况:

  1. 字符匹配,则有s[i] 和 p[j] 完全匹配或者 p[j]为 . 两种情况;

由于是从左到右扫描字符,此时需考虑p[j+1]为*的情况:

此时如果*匹配0个字符,则只需将p指针继续往右前进2步,s不需动,即memo[i][j] = dp(i, j+2);如果匹配多个字符,则只需将s指针向右前进一步,而p不动,即memo[i][j] = dp(i+1, j)。

2)字符不匹配,此时如果p[j+1]为*,则还能继续往下扫描;否则,则匹配失败。

var isMatch = function(s, p) {
    let m = s.length,n = p.length
    let memo = new Array(m).fill().map(() => new Array(n).fill())
    return dp(0, 0)
    // 从左到右扫描字符,若字符相同,仍需继续比对下一个字符是否有*
    // dp表示s[i...]和p[j...]能匹配
    function dp(i, j) {
        // p走完,字符刚好匹配完
        if (j === n) return i === m
        // s走完,则p需符合普通字符和*成对出现才行,如 a*b* 
        if (i === m) {
            if ((n-j) % 2 === 1) return false
            for (;j < n - 1; j+=2) {
                if (p[j+1] !== '*') return false
            }
            return true
        }
        if (memo[i][j] !== undefined) return memo[i][j]
        if (s[i] === p[j] || p[j] === '.') {
            // 还需看字符下一位是否有*
            if (p[j+1] === '*') {
                memo[i][j] = dp(i, j+2) || dp(i+1, j)
            } else {
                memo[i][j] = dp(i+1, j+1)
            }
        } else {
            if (p[j+1] === '*') {
                memo[i][j] = dp(i, j+2)
            } else {
                memo[i][j] = false
            }
        }
        return memo[i][j] 
    }
};