萬用字元——*可以當空串或任意串(一)

weixin_34365417發表於2018-11-22

LeetCode_44_WildcardMatching

題目分析:

 s = "acdcb", p = "a*?b";
     a * ? b
   T F F F F
 a F T T F F
 c F F T T F
 d F F T T F
 c F F T T F
 b F F T T T
 
 s內容固定,唯有p中?和*的情況特殊,?其實最簡單,直接當匹配即可,*要特殊對待
 1.p.charAt(j - 1) != '*'
  就簡單了 要麼一樣 要麼p是? 且左上相等
  dp[i][j] = (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '?') && dp[i - 1][j - 1];
 2.p.charAt(j - 1) == '*'
 s = s[0, i] = "acdcb" p = p[0, j] = "a*"為例
 此刻關心 三種情況
 
   2.1.dp[i - 1][j - 1]      *當0個
   2.2.dp[i][j - 1]          *當1個
   2.3.dp[i - 1][j]          *當多個
 
   dp[i][j] = dp[i - 1][j] || dp[i][j - 1] || dp[i - 1][j - 1];

 這裡可以簡化一下
 因為在這個式子的遞推方式下
 上方dp[i - 1][j]和 左上方dp[i - 1][j - 1] 有一些特殊關係
 首先上方 也就是j不變,所以p.charAt(j - 1) == '*' 成立
 dp[i - 1][j] = dp[i - 2][j] || dp[i - 1][j - 1] || dp[i - 2][j - 1];
 可以看到 左上方結果dp[i - 1][j - 1] 已經被"或"進了上方結果dp[i - 1][j]中
 所以左上方可以忽略 寫成
 dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
 PS:左方結果就沒有這個關係了,因為左方j已經變了 p.charAt(j - 2) 不一定等於 '*'。

解法一:

public static boolean isMatch(String s, String p) {
    int m = s.length(), n = p.length();
    boolean [][]dp = new boolean[m + 1][n + 1];
    dp[0][0] = true;
    for (int i = 1; i <= n; ++i) {
        if (p.charAt(i - 1) == '*') dp[0][i] = dp[0][i - 1];
    }
    for (int i = 1; i <= m; ++i) {
        for (int j = 1; j <= n; ++j) {
            if (p.charAt(j - 1) == '*') {
                dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
            } else {
                dp[i][j] = (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '?') && dp[i - 1][j - 1];
            }
        }
    }
    return dp[m][n];
}

解法二:

 /**
 * O(m*n) 貪心 回溯
 * 回溯部分:
 * 本質就是遇到*就挨個嘗試讓*從空串開始用,之後繼續,
 * 如果之後配不上,可能是*用錯了,讓*多配一個,sp分別回溯再試
 * 貪心部分:
 * 只用關心最後一個*
 * 如果之前的*和最後一個*之間沒有非*字母,也就是連續的全是*,那麼可以當成一個*,也就成了關心最後一個*
 * 如果之前的*和最後一個*之間有非*字母,也就是*(非*,可能是普通字母a,也可能是?)*,配到最後一個*,表示之前的非*都被消耗了,是最理想情況,絕對不用回溯。
 */
public static boolean isMatch(String s, String p) {
    int sIndex = 0, pIndex = 0, match = 0, starIdx = -1;
    while (sIndex < s.length()){
        if (pIndex < p.length()  && (p.charAt(pIndex) == '?' || s.charAt(sIndex) == p.charAt(pIndex))){
            sIndex++;
            pIndex++;
        }
        else if (pIndex < p.length() && p.charAt(pIndex) == '*'){
            /**
             * 儲存p中最後出現的*位置
             */
            starIdx = pIndex;
            /**
             * 儲存s中遇到最後*時候的字元位置
             */
            match = sIndex;
            /**
             * 只讓p位置++因為*可以當空串,所以s的不能被跳過一個
             */
            pIndex++;
        }
        /**
         * 沒配上,如果之前p出現過*,那麼可能是*沒用對,回溯再試
         */
        else if (starIdx != -1){
            /**
             * p的最後一個*的上一次配法不行 要嘗試躲讓*配一個
             * 也就是匹配回溯了
             */
            match++;
            /**
             * s p位置進行相應回溯 s回溯到match位置 + 1 p回溯到最後一個*後方
             */
            pIndex = starIdx + 1;
            sIndex = match;
        }
        /**
         * p不是*也沒配上 GG
         */
        else return false;
    }

    /**
     * 上方保證了s被配完
     * 因為*可以被當空串,所以s配完後 如果p後面有多餘的*是依然可以配上的
     */
    while (pIndex < p.length() && p.charAt(pIndex) == '*')
        pIndex++;

    return pIndex == p.length();
}

相關文章