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)expressSome 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
切分正則的pattern, 將帶*號的都切開, 好比.aa* 能夠切爲 .~a~a* 三段leetcode
使用一個棧, 對不一樣的可能性(這些可能性實際上造成一個搜索樹)作深度優先搜索(也能夠不用棧, 直接遞歸, 個人代碼裏沒有展現)
對於不帶*的分段, 直接嚴格匹配
對於帶*的分段, 若是跟當前遊標處的字符相同, 則嘗試匹配一個字符(下次斷定還能夠匹配一個, 這樣實際能夠作到匹配多個), 或者匹配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了, 可是存在如下問題:
執行速度較慢, 只超過了百分之幾的成功提交.(直接改遞歸在java中會更快的)
代碼思路不是很清晰
這種動態規劃比較保守, 斷定較長, 搜索的樹比較深.
因而嘗試了第二個解法, 跟第一個很不一樣, 主要思路爲:
首先仍是切分正則的pattern, 將帶*號的都切開, 好比.aa* 能夠切爲 .~a~a* 三段. 不過圖方便直接切成多個string便可, 其實也有其餘的存儲方案.
從前/後兩個方向, 遍歷子pattern列表, 把不重複(不帶*)的部分, 跟目標字符串的相應位置作嚴格匹配, 直到遇到帶*的子pattern. 此時匹配不成功能夠認爲正則匹配失敗.
剩下的子pattern列表中, 若是包含不帶*號的子pattern, 則尋找全部在目標字符串中能匹配到的字符, 對每一個這樣的字符, 把字符串和子pattern列表切分紅兩段, 看這兩段是否能夠匹配, 當且僅當這兩段都匹配成功, 纔算整個字符串匹配正則成功.
剩下的子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的解.