官方题解的 js 版,以及个人理解的详细注释

/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param str string字符串 
 * @param pattern string字符串 
 * @return bool布尔型
 */
function match( str ,  pattern ) {
    // write code here
    const m = str.length, n = pattern.length;
    // 1. dp 下标的含义
    // dp[i][j] 表示从 str 首字母到 i ,以及从 pattern 首字母到 j 的子字符串是否匹配
    // 只要存在 i = m - 1 时,有一个 j 为 true 即匹配
    const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(false));
    // 2. 初始化
    dp[0][0] = true;    // '' 和 '' 是匹配的
    // str 为 '' ,初始化 pattern 不为空串的情况
    // 例如:
    // '' 与 a* 这种情况时,dp[0][2] = true;
    // 这里从 2 开始,因为如果从 1 开始,即使 1 的位置是 '*' 也是无意义的,因为 * 和空串不匹配
    for(let i = 2; i <= n; i++) {   
        if(pattern[i - 1] === '*') dp[0][i] = dp[0][i - 2];
    }
    // 3. 状态转移
    // 状态转移时只需要分别考虑 '*' 和 其他字符时的情况
    // 其中 '*' 因为表示 0 次或者多次,需要特别注意 0 次的情况
    for(let i = 1; i <= m; i++) {
        for(let j = 1; j <= n; j++) {
            if(pattern[j - 1] !== '*') {    // 非 '*' 的情况
                if((pattern[j - 1] === '.' || pattern[j - 1] === str[i - 1])) {
                    // 如果当前两字符相同 或者 模式字符串指向字符为 '.'
                    // 则 dp[i][j] 是否匹配取决于前一个字符
                    dp[i][j] = dp[i - 1][j - 1] 
                }
            } else if(j >= 2) {    // '*' 的情况,这里 j 限定了 >=2 与初始化的意思是一致的
                 // 如果 '*' 前一个字符为 '.' 或者 '*' 的前一个字符与当前字符相同
                if(pattern[j - 2] == '.' || pattern[j - 2] === str[i - 1]) {   
                    // `有 i - 1 这个字符` 时,取决于 '*' 的前一个位置是否匹配
                    // 或,`没有 i - 1 这个字符` 时是否匹配
                    dp[i][j] = dp[i][j - 2] || dp[i - 1][j]; 
                } else {
                    // 如果 '*' 前面的字符 j - 1 与 i - 1 位置指向字符不同,或者不为 '.'
                    // 则,当前位置 dp[i][j] 是否匹配
                    // 取决于 `没有 j - 1 这个字符` 时的情况,也就是 dp[i][j - 2] 没有 j - 1 字符的状态
                    dp[i][j] = dp[i][j - 2];
                }
            }
        }
    }
    // 返回整个子串的匹配情况
    return dp[m][n];
}
module.exports = {
    match : match
};