Leetcode 10 Regular Expression Matching 簡單正則匹配

Implement regular expression matching with support for '.' and '*'.java

'.' Matches any single character. '*' Matches zero or more of the
preceding element.正則表達式

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

The function prototype should be: bool isMatch(const char *s, const
char *p)express

Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "a*") → true
isMatch("aa", ".*") → true
isMatch("ab", ".*") → true
isMatch("aab", "c*a*b") → true測試

難度: Hardprototype

這道題要求咱們實現簡單的正則表達式的匹配, 只要求普通字符 . *的匹配, 瞭解正則的同窗都清楚, .表明任意單個字符, *表明0個或多個前面的字符, 好比a*能夠匹配到空字符串, 也能夠匹配 a, aaa等等. 題目還要求, 咱們斷定正則是否匹配給定的字符串, 要斷定整個字符串, 而不是其中一部分匹配就算ok.code

這是個典型的動態規劃的題, 做者在leetcode出來以前幾乎不作算法, 本着死磕到底不看答案不看別人解答的精神, 我嘗試了不止一種解法, 這裏貼出兩個AC的解法.
任意肯定字符, 必須精確匹配, . 匹配一個任意字符, 這些都很好斷定, 關鍵在於, * 到底匹配多少個字符? 這就很差斷定了, 只能根據必定規則去嘗試, 這裏就是用到動態規劃的地方.遞歸

第一個AC的解法主要思路爲:element

  1. 切分正則的pattern, 將帶*號的都切開, 好比.aa* 能夠切爲 .~a~a* 三段leetcode

  2. 使用一個棧, 對不一樣的可能性(這些可能性實際上造成一個搜索樹)作深度優先搜索(也能夠不用棧, 直接遞歸, 個人代碼裏沒有展現)

  3. 對於不帶*的分段, 直接嚴格匹配

  4. 對於帶*的分段, 若是跟當前遊標處的字符相同, 則嘗試匹配一個字符(下次斷定還能夠匹配一個, 這樣實際能夠作到匹配多個), 或者匹配0個(也就是將當前子pattern分段拋棄); 若是跟當前遊標處的字符不一樣, 則只能匹配0個, 將當前子pattern分段拋棄.

代碼以下

public class Solution2 {
    public class Symbol {
        public Symbol() {
            rep = false;
        }

        public Symbol(char f, boolean s) {
            c = f;
            rep = s;
        }

        public char    c;
        public boolean rep;
    }

    public class Pair<T> {
        public T first;
        public T second;

        public Pair() {
            first = null;
            second = null;
        }

        public Pair(T _f, T _s) {
            first = _f;
            second = _s;
        }
    }

    /**
     * AC, but slow
     */
    public boolean isMatch(String s, String p) {
        // parse pattern
        List<Symbol> pl = new ArrayList<Symbol>();
        int i = 0;
        while (i < p.length()) {
            char c;
            boolean rep = false;
            char a = p.charAt(i);
            if (a != '*') {
                c = a;
            } else {
                // regex wrong
                return false;
            }
            i++;
            if (i < p.length() && p.charAt(i) == '*') {
                rep = true;
                i++;
            }
            pl.add(new Symbol(c, rep));
        }
        // do match
        Stack<Pair<Integer>> q = new Stack<Pair<Integer>>();
        q.push(new Pair<Integer>(0, 0));
        while (!q.isEmpty()) {
            Pair<Integer> pr = q.pop();
            if (isMatch(s, pr.first, pl, pr.second, q)) {
                return true;
            }
        }
        return false;
    }

    private boolean isMatch(String s, int sPos, List<Symbol> pl, int pPos, Stack<Pair<Integer>> q) {
        while (sPos < s.length() && pPos < pl.size()) {
            Symbol sym = pl.get(pPos);
            if (sym.rep) {
                if (sym.c == '.' || sym.c == s.charAt(sPos)) {
                    q.push(new Pair<Integer>(sPos, pPos + 1));
                    q.push(new Pair<Integer>(sPos + 1, pPos));
                } else {
                    q.push(new Pair<Integer>(sPos, pPos + 1));
                }
                return false;
            } else {
                if (sym.c != s.charAt(sPos) && sym.c != '.') {
                    return false;
                }
            }
            sPos++;
            pPos++;
        }
        if (sPos < s.length()) {
            return false;
        }
        if (pPos < pl.size()) {
            while (pPos < pl.size()) {
                if (!pl.get(pPos).rep) {
                    return false;
                }
                pPos++;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Solution2 s = new Solution2();
        System.out.println(s.isMatch("aa", "a"));
        System.out.println(s.isMatch("aa", "aa"));
        System.out.println(s.isMatch("aaa", "aa"));
        System.out.println(s.isMatch("aa", "a*"));
        System.out.println(s.isMatch("aa", ".*"));
        System.out.println(s.isMatch("aab", "c*a*b"));
        System.out.println(s.isMatch("ab", ".*"));
        System.out.println(s.isMatch("aaab", ".*ab"));
        System.out.println(s.isMatch("aaa", "a*a"));
        System.out.println(s.isMatch("aaa", "aaab*"));
        System.out.println(s.isMatch("bbab", "b*"));
        System.out.println(s.isMatch("bbab", "a*"));
        System.out.println(s.isMatch("bbab", "b*a*"));
        System.out.println(s.isMatch("bbab", "...."));
        System.out.println(s.isMatch("abbabaaaaaaacaa", "a*.*b.a.*c*b*a*c*"));
        System.out.println(s.isMatch("bbabaaaaaaacaa", "b.a.*c*b*a*c*"));
        System.out.println(s.isMatch("baaaaaaacaa", ".*c*b*a*c*"));
        System.out.println(s.isMatch("caa", "c*b*a*c*"));
        System.out.println(s.isMatch("caa", "c*b*a*"));
        System.out.println(s.isMatch("a", "a*b*"));
        System.out.println(s.isMatch("a", "a*.*"));
        System.out.println(s.isMatch("ab", "a*b"));
        System.out.println(s.isMatch("b", ".*b"));
        System.out.println(s.isMatch("ab", ".*b"));
        System.out.println(s.isMatch("aaaaaaaaaaaaab", "a*a*a*a*a*a*a*a*a*a*a*a*b"));
    }
}

main中包含了測試用例.
這個解法雖然AC了, 可是存在如下問題:

  1. 執行速度較慢, 只超過了百分之幾的成功提交.(直接改遞歸在java中會更快的)

  2. 代碼思路不是很清晰

  3. 這種動態規劃比較保守, 斷定較長, 搜索的樹比較深.

因而嘗試了第二個解法, 跟第一個很不一樣, 主要思路爲:

  1. 首先仍是切分正則的pattern, 將帶*號的都切開, 好比.aa* 能夠切爲 .~a~a* 三段. 不過圖方便直接切成多個string便可, 其實也有其餘的存儲方案.

  2. 從前/後兩個方向, 遍歷子pattern列表, 把不重複(不帶*)的部分, 跟目標字符串的相應位置作嚴格匹配, 直到遇到帶*的子pattern. 此時匹配不成功能夠認爲正則匹配失敗.

  3. 剩下的子pattern列表中, 若是包含不帶*號的子pattern, 則尋找全部在目標字符串中能匹配到的字符, 對每一個這樣的字符, 把字符串和子pattern列表切分紅兩段, 看這兩段是否能夠匹配, 當且僅當這兩段都匹配成功, 纔算整個字符串匹配正則成功.

  4. 剩下的子pattern列表中, 若是包含不帶*號的子pattern, 即所有都是帶*的模糊匹配. 咱們就採用貪心算法, 對每一個子pattern, 匹配儘可能多的字符, 若是能把當前字符串匹配乾淨, 就算ok.

public class Solution3 {
    /**
     * AC, fast enough
     */
    public boolean isMatch(String s, String p) {
        List<String> plist = new ArrayList<String>();
        int pi = 0;
        while (pi < p.length()) {
            if (p.charAt(pi) == '*') {
                // regex wrong
                return false;
            }
            if (pi + 1 < p.length() && p.charAt(pi + 1) == '*') {
                plist.add(p.substring(pi, pi + 2));
                pi += 2;
            } else {
                plist.add(p.substring(pi, pi + 1));
                pi += 1;
            }
        }
        return isMatch(s, 0, s.length(), plist, 0, plist.size());
    }

    /**
     *
     * @param s string to be matched
     * @param ss start position of s (inclusive)
     * @param se end position of s (exclusive)
     * @param plist pattern list
     * @param ps start position of plist (inclusive)
     * @param pe end position of plist (exclusive)
     * @return
     */
    public boolean isMatch(String s, int ss, int se, List<String> plist, int ps, int pe) {
        if (ps == pe) {
            if (ss != se) {
                return false;
            } else {
                return true;
            }
        }
        while (ps < pe && plist.get(ps).length() == 1 && ss < se) {
            char c = plist.get(ps).charAt(0);
            if (c != '.' && c != s.charAt(ss)) {
                return false;
            }
            ss++;
            ps++;
        }
        while (ps < pe && plist.get(pe - 1).length() == 1 && ss < se) {
            char c = plist.get(pe - 1).charAt(0);
            if (c != '.' && c != s.charAt(se - 1)) {
                return false;
            }
            se--;
            pe--;
        }
        if (ps == pe && ss == se) {
            return true;
        }
        if (ps == pe) {
            return false;
        }
        if (ss == se) {
            for (int i = ps; i < pe; i++) {
                if (plist.get(i).length() == 1) {
                    return false;
                }
            }
            return true;
        }
        // select one single sub-pattern
        int pi = 0;
        for (pi = ps; pi < pe; pi++) {
            if (plist.get(pi).length() == 1) {
                break;
            }
        }
        if (pi < pe) {
            // found single sub-pattern
            char c = plist.get(pi).charAt(0);
            for (int si = ss; si < se; si++) {
                if (c == '.' || s.charAt(si) == c) {
                    boolean b1 = isMatch(s, ss, si, plist, ps, pi);
                    if (!b1) {
                        // early termination
                        continue;
                    }
                    boolean b2 = isMatch(s, si + 1, se, plist, pi + 1, pe);
                    if (b2) {
                        return true;
                    }
                }
            }
            return false;
        }
        // single sub-pattern not found, all * patterns
        // do greedy
        for (pi = ps; pi < pe; pi++) {
            char c = plist.get(pi).charAt(0);
            if (c == '.') {
                // will consume all
                return true;
            }
            while (ss < se && (s.charAt(ss) == c)) {
                ss++;
            }
            if (ss == se) {
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Solution3 s = new Solution3();
        System.out.println(s.isMatch("aa", "a"));
        System.out.println(s.isMatch("aa", "aa"));
        System.out.println(s.isMatch("aaa", "aa"));
        System.out.println(s.isMatch("aa", "a*"));
        System.out.println(s.isMatch("aa", ".*"));
        System.out.println(s.isMatch("aab", "c*a*b"));
        System.out.println(s.isMatch("ab", ".*"));
        System.out.println(s.isMatch("aaab", ".*ab"));
        System.out.println(s.isMatch("aaa", "a*a"));
        System.out.println(s.isMatch("aaa", "aaab*"));
        System.out.println(s.isMatch("bbab", "b*"));
        System.out.println(s.isMatch("bbab", "a*"));
        System.out.println(s.isMatch("bbab", "b*a*"));
        System.out.println(s.isMatch("bbab", "...."));
        System.out.println(s.isMatch("abbabaaaaaaacaa", "a*.*b.a.*c*b*a*c*"));
        System.out.println(s.isMatch("bbabaaaaaaacaa", "b.a.*c*b*a*c*"));
        System.out.println(s.isMatch("baaaaaaacaa", ".*c*b*a*c*"));
        System.out.println(s.isMatch("caa", "c*b*a*c*"));
        System.out.println(s.isMatch("caa", "c*b*a*"));
        System.out.println(s.isMatch("a", "a*b*"));
        System.out.println(s.isMatch("a", "a*.*"));
        System.out.println(s.isMatch("ab", "a*b"));
        System.out.println(s.isMatch("b", ".*b"));
        System.out.println(s.isMatch("ab", ".*b"));
        System.out.println(s.isMatch("aaaaaaaaaaaaab", "a*a*a*a*a*a*a*a*a*a*a*a*b"));
    }
}

main中包含了測試用例.
這個解法看代碼直覺就能夠感到搜索深度會相對於上個解法小不少, 由於能在本次斷定中完成的, 就在本次斷定中完成, 不放到下次. 實際也能夠超過75%的提交, 而且思路相對清晰的多, 雖然代碼長了點.
能夠看到, 對於動態規劃的問題, 須要作的是:

  • 肯定的部分儘快匹配, 給出答案

  • 不肯定的部分, 單獨切分出來, 使用動態規劃

以上是個人解法, 不知道是否有更好更清晰的解.

另: 這道題跟 Leetcode 44 通配符匹配很類似, 稍後給出Leetcode 44的解.

相關文章
相關標籤/搜索