1. 程式人生 > >(Java) LeetCode 44. Wildcard Matching —— 通配符匹配

(Java) LeetCode 44. Wildcard Matching —— 通配符匹配

htm leetcode ole code str 下一個 cond 由於 tco

Given an input string (s) and a pattern (p), implement wildcard pattern matching with support for ‘?‘ and ‘*‘.

‘?‘ Matches any single character.
‘*‘ Matches any sequence of characters (including the empty sequence).

The matching should cover the entire input string (not partial).

Note:

  • s could be empty and contains only lowercase letters a-z
    .
  • p could be empty and contains only lowercase letters a-z, and characters like ? or *.

Example 1:

Input:
s = "aa"
p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".

Example 2:

Input:
s = "aa"
p = "*"
Output: true
Explanation: ‘*‘ matches any sequence.

Example 3:

Input:
s = "cb"
p = "?a"
Output: false
Explanation: ‘?‘ matches ‘c‘, but the second letter is ‘a‘, which does not match ‘b‘.

Example 4:

Input:
s = "adceb"
p = "*a*b"
Output: true
Explanation: The first ‘*‘ matches the empty sequence, while the second ‘*‘ matches the substring "dce".

Example 5:

Input:
s = "acdcb"
p = "a*c?b"
Output: false

正則匹配問題其實是一種非確定有限狀態自動機的構建。本題是通配符匹配,但和正則匹配很相似。只要能構建出來這道題就解決了。嘗試構建之前先看看自動機是如何模擬正則匹配的。首先要明確特殊字符‘?‘與‘*‘分別代表了什麽。‘?‘很好理解,即它可以和任何單個非空字符匹配。而‘*‘可以和任何字符串(包括空串)匹配。也就是說,只要‘*‘出現,那麽它既可以使自動機向後運行,即創造了向後的通路;也可以使自動機的下一個狀態回到它所在的位置,即創造了後一個節點回到其本身的通路。因為它可以匹配任何字符串,即當需要的時候,它可以“吸收”一切使得自動機停滯不前的字符。用第四個例子舉例,構建的p串自動機如下:技術分享圖片

這裏人為添加一個實心節點作為終點。如果s全被掃描,且p最後到達了終點,即是匹配成功。模擬自動機之前,要清楚自動機總是向著匹配成功的目標運行,即要優先考慮匹配,才去向下移動二者的指針。如果不匹配,再考慮二者有沒有因為‘*‘產生的向後或者向前的通路。而且如果s串由於不匹配而無法前進的狀態只能停留一次。這裏要特別解釋一下這一點:如果s串遇到了一個字符,同時p串遇到了‘*‘,盡管i此時可以向下移動(因為p對應的是‘*‘),也可以不移動,那麽本著匹配優先向下移動的原則,i是此時應該優先停在原地,只有j可以向下移動。而如果p的下一個字符依然不能和s的字符匹配,那麽p的指針j要沿著由於‘*‘產生的向前通路回到‘*‘處,而i已經上輪已經停留在此了,所以這次不能再本著匹配才能向下移動的原則留在此地了,畢竟它不是無路可走,它可以向下。如果它不想下,就會產生死循環。說的很拗口,還是舉例用例子4來說,新建兩個指針i和j,i和j分別指向s串與p串自動機的第一個字符,模擬如下:

1. i -> ‘a‘ / j -> ‘*‘ :不匹配發生,因為p串指向的字符是‘*‘,所以s與p同時產生向下的通路,但由於這是第一次i到這個位置,本著匹配優先的原則,i留在原地,只有j後移。且同樣由於此時字符是‘*‘,產生下一個字符回溯到當前位置的通路,如圖所示;

2. i -> ‘a‘ / j -> ‘a‘:找到匹配,s串和p串指針同時向後移動;

3. i -> ‘d‘ / j -> ‘*‘:不匹配發生,因為p串指向的字符是‘*‘,所以s與p同時產生向下的通路,但由於這是第一次i到這個位置,本著匹配優先的原則,i留在原地,只有j後移。且同樣由於此時字符是‘*‘,產生下一個字符回溯到當前位置的通路,如圖所示;

4. i -> ‘d‘ / j -> ‘b‘:不匹配發生,因為不匹配,且p指向的不是‘*‘,所以i無法向後走,j亦無法向後,為了保證自動機運行,j只能沿著之前產生的通路回到之前的位置,即j回到第二個‘*‘處;

5. i -> ‘d‘ / j -> ‘*‘:不匹配發生,因為p串指向的字符是‘*‘,所以s與p同時產生向下的通路,此時這已經是i第二次走到這個位置了,所以i必須向後運行才可以避免死循環,所以i後移。j亦後移(其實j可以不後移,但如果j停留在‘*‘,那麽下一步一定是i指向的字符又和‘*‘不匹配而導致j自己後移,所以此時不如直接將j後移到‘*‘的下一個位置,進而省略一步);

6. i -> ‘c‘ / j -> ‘b‘:此時情況和第4步一樣,j先向回走,i因為不是匹配留在原地一次,進而下一步的時候需要和j同時後移。所以最後的結果是和經歷和第5步一樣狀態(i -> ‘c‘ / j -> ‘*‘)後,i和j同時向後,即i從‘c‘移動到最後一個字符‘b‘,而j從‘*‘向後重新移動到‘b‘;

7. i -> ‘b‘ / j -> ‘b‘:找到匹配,s串和p串指針同時向右移動。此時p已經移動到實心終點,而s亦全部掃描完成,返回匹配true。

模擬出來自動機運行,證明了這個就是可以尋找匹配的過程,那麽下面就要開始構建自動機:

首先本著優先匹配後移的原則,如果s[i] == p[j],那麽i和j同時後移。這裏可以把字符‘?‘加進去,因為遇到‘?‘和遇到匹配的情況完全一樣,所以s[i] == p[j] || p[j] == ‘?‘,i與j後移;

如果不匹配,但j指向‘*‘,那麽要做三件事:1) 構建p的回路;2) 標記此刻i因為不匹配優先而停留的位置,以便i下次不會有路走而不走;3) 後移j。這時就要引入兩個變量,一個來存儲此時j指向的位置,用來構建回路。一個來記錄i的位置,用來提醒i下次不要有路走而不走。即,pStar = j;sStar = i;j++;

同樣如果不匹配,但是i並不是無路可走(即sStar存在),那麽i走到sStar的下一個位置。由於sStar存在,pStar一定存在(因為二者同時建立),那麽j此刻要到pStar+1的位置(理由見步驟5的括號中解釋);

如果i與j不匹配還都無路可走,那麽只能返回false,即s和p無論如何不能匹配。

構建自動機的過程亦是完成代碼的過程,詳見下文代碼解釋。


Java

class Solution {
    public boolean isMatch(String s, String p) {
        char[] S = s.toCharArray(), P = p.toCharArray();
        int i = 0, j = 0, sStar = -1, pStar = -1;
        while (i < s.length()) {
            if (j < p.length() && (S[i] == P[j] || P[j] == ‘?‘)) { //如果匹配,兩指針同時後移
                i++;
                j++;
            }
            else if (j < p.length() && P[j] == ‘*‘) { //如果不匹配但j指向‘*‘,那麽記錄此時j的位置以構建回路,同時記錄i的位置以標記i此時可以後移卻停留在此一次,同時j後移
                pStar = j++;
                sStar = i;
            }
            else if (sStar >= 0) { //仍然不匹配,但是i有路可走,且i已經停在那一次了,那麽i要後移,連同i停留的位置也要更新,j直接到回路‘*‘的後一個位置。此時j也可以取pStar,但運行速度會變慢
                j = pStar + 1;
                i = ++sStar;
            }
            else return false; //仍然不匹配,i與j均已無路可走,返回false
        }
        while (j < p.length() && P[j] == ‘*‘) j++; //i掃描完成後要看j能不能夠到達終點,即j可以沿著‘*‘行程的通路一直向下
        return j == p.length(); //i與j同時到達終點完成匹配
    }
}

(Java) LeetCode 44. Wildcard Matching —— 通配符匹配