徒手挖地球十七週目

徒手挖地球十七週目

NO.44 通配符匹配 困難

在這裏插入圖片描述
1O3sPJ.png

這道題和NO.10正則表達式匹配看起來很像,正則匹配的題解可以參考徒手挖地球十六週目中的記錄。

這兩道題目的區別在於’*‘的處理不同,正則中的星號是星號前的字符可以出現0次、1次或多次,而本題中通配符中的星號則是可以匹配任意字符。但是正則中的’.‘和通配符中的’?'作用是一樣的。

所以說這道題的難點一樣是對於’*'的處理。

思路一:雙指針貪心算法 重點就是我們如何充分的利用’*’。

我們用i和j分別標記s和p的第一個字符下標,即都初始化爲0。用istart和jstart分別標記s和p中’*'匹配過的位置,即初始化爲-1。

和普通字符串匹配的思路差不多,已經匹配成功的部分就不再考慮了,所以要用i和j標記當前正在比較的字符;但是最近匹配過的'*'可能會被重複使用去匹配更多的字符,所以我們要用istart和jstart分別標記s和p中最近匹配過'*'的位置。可以參考徒手挖地球十六週目NO.正則表達式匹配的思路一是如何從普通情況延伸到特殊字符的。

s和p匹配過程中可能會遇到的情況:

  1. 如果i和j標記的字符正好相等或者j字符是'?'匹配成功,則"移除"i和j元素,即自增i、j
  2. 否則如果j字符是'*'依然可以匹配成功,則用istart和jstart分別標記i元素和j元素之後自增j
  3. 再否則如果istart>-1說明之前匹配過’*’,因爲’*‘可以匹配多個字符,所以這裏要再次利用這個最近匹配過的’*'匹配更多的字符,移動i標記istart的下一個字符,再讓istart重新標記i元素同時移動j標記jstart的下一個字符
  4. 上述三種情況都不滿足,則匹配失敗,返回false

最後當s中的字符都判斷完畢,則認爲s爲空,此時需要p爲空或者p中只剩下星號的時候,才能成功匹配。

public boolean isMatch(String s, String p) {
    if (p==null||p.isEmpty())return s==null||s.isEmpty();
    int i=0,j=0,istart=-1,jstart=-1,slen=s.length(),plen=p.length();
    //判斷s的所有字符是否匹配
    while (i<slen){
        //三種匹配成功情況以及匹配失敗返回false
        if (j<plen&&(s.charAt(i)==p.charAt(j)||p.charAt(j)=='?')){
            i++;
            j++;
        }else if (j<plen&&p.charAt(j)=='*'){
            istart=i;
            jstart=j++;
        }else if (istart>-1){
            i=++istart;
            j=jstart+1;
        }else {
            return false;
        }
    }
    //s中的字符都判斷完畢,則認爲s爲空,此時需要p爲空或者p中只剩下星號的時候,才能成功匹配。
    //如果p中剩餘的都是*,則可以移除剩餘的*
    while (j<plen&&p.charAt(j)=='*')j++;
    return j==plen;
}

時間複雜度:O(mn) m、n分別是s和p的長度。

思路二:動態規劃法 分析步驟依然按照徒手挖地球十六週目NO.正則表達式匹配思路二的步驟來。

dp數組的含義:dp[i][j]意思是s的前i個元素能否被p的前j個元素成功匹配。

知道了dp數組的含義之後,我們就知道了初始化細節:

  1. boolean類型的dp數組,大小是[s.length+1][p.length+1],因爲存在s前0個字符和p前0個字符的情況。
  2. dp[0][0]一定是true,因爲s空串和p空串是可以匹配成功的;dp[1][0]~dp[s.length][0]一定都是false,因爲s不爲空串而p爲空串是不能匹配成功的。
  3. dp[0][1]~dp[0][p.length]當s爲空串的時候,而p不是空串的時候,當且僅當p的j字符以及前面都爲’*'才爲true。
  4. dp[s.length][p.length]就得到了s和p最終的匹配情況。

有了上述理解之後,就可以初始化dp數組了。

然後填寫dp數組剩餘部分即可,狀態轉移方程:

  1. s[i]==p[j]或者p[j]=='?',則dp[i][j]=dp[i-1][j-1]。可以理解爲當前字符成功匹配後,只需要考慮之前的字符串是否匹配即可;也可以理解爲當前字符匹配成功之後,"移除"當前元素(即不需要再考慮當前元素)。
  2. p[j]=='*',則dp[i][j]=dp[i-1][j]||dp[i][j-1]。可以理解爲當字符爲’*‘的時候會出現兩種情況,第一種是’*‘需要作爲一個字母與s[i]進行匹配;第二種是’*‘需要作爲空字符(即不需要’*'可以直接"移除"),所以dp[i][j-1];用邏輯或將兩種情況連接,是因爲只要有一種情況可以匹配成功則當前匹配成功,有點暴力算法的感覺。
  3. 最後當s[i]!=p[j]&&p[j]!='*'dp[i][j]=false。這步可以省略,因爲dp數組元素的默認值就是false,所以不必要進行顯式的賦值爲false。
public boolean isMatch(String s, String p) {
    if (p==null||p.isEmpty())return s==null||s.isEmpty();
    int slen = s.length(),plen=p.length();
    boolean[][] dp=new boolean[slen+1][plen+1];
    //初始化dp數組,dp[1][0]~dp[s.length][0]默認值flase不需要顯式初始化爲false
    dp[0][0]=true;
    //dp[0][1]~dp[0][p.length]只有p的j字符以及前面所有字符都爲'*'才爲true
    for (int j=1;j<=plen;j++)dp[0][j]=p.charAt(j-1)=='*'&&dp[0][j-1];
    //填寫dp數組剩餘部分
    for (int i = 1; i <= slen; i++) {
        for (int j = 1; j <= plen; j++) {
            char si = s.charAt(i - 1),pj=p.charAt(j-1);
            if (si==pj||pj=='?'){
                dp[i][j]=dp[i-1][j-1];
            }else if (pj=='*'){
                dp[i][j]=dp[i-1][j]||dp[i][j-1];
            }
        }
    }
    return dp[slen][plen];
}

時間複雜度:O(mn) m、n分別是s和p的長度。


本人菜鳥,有錯誤請告知,感激不盡!

更多題解和學習記錄博客:博客github

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章