资讯专栏INFORMATION COLUMN

Leetcode 10 Regular Expression Matching 简单正则匹配

OnlyLing / 2796人阅读

摘要:难度这道题要求我们实现简单的正则表达式的匹配只要求普通字符的匹配了解正则的同学都清楚代表任意单个字符代表个或多个前面的字符比如可以匹配到空字符串也可以匹配等等题目还要求我们判定正则是否匹配给定的字符串要判定整个字符串而不是其中一部分匹配就算

Implement regular expression matching with support for "." and "*".

"." 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)

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

难度: Hard

这道题要求我们实现简单的正则表达式的匹配, 只要求普通字符 . *的匹配, 了解正则的同学都清楚, .代表任意单个字符, *代表0个或多个前面的字符, 比如a*可以匹配到空字符串, 也可以匹配 a, aaa等等. 题目还要求, 我们判定正则是否匹配给定的字符串, 要判定整个字符串, 而不是其中一部分匹配就算ok.

这是个典型的动态规划的题, 作者在leetcode出来之前几乎不做算法, 本着死磕到底不看答案不看别人解答的精神, 我尝试了不止一种解法, 这里贴出两个AC的解法.
任意确定字符, 必须精确匹配, . 匹配一个任意字符, 这些都很好判定, 关键在于, * 到底匹配多少个字符? 这就不好判定了, 只能根据一定规则去尝试, 这里就是用到动态规划的地方.

第一个AC的解法主要思路为:

切分正则的pattern, 将带*号的都切开, 比如.aa* 可以切为 .~a~a* 三段

使用一个栈, 对不同的可能性(这些可能性实际上形成一个搜索树)做深度优先搜索(也可以不用栈, 直接递归, 我的代码里没有展示)

对于不带*的分段, 直接严格匹配

对于带*的分段, 如果跟当前游标处的字符相同, 则尝试匹配一个字符(下次判定还可以匹配一个, 这样实际可以做到匹配多个), 或者匹配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 {
        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 pl = new ArrayList();
        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> q = new Stack>();
        q.push(new Pair(0, 0));
        while (!q.isEmpty()) {
            Pair pr = q.pop();
            if (isMatch(s, pr.first, pl, pr.second, q)) {
                return true;
            }
        }
        return false;
    }

    private boolean isMatch(String s, int sPos, List pl, int pPos, Stack> 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(sPos, pPos + 1));
                    q.push(new Pair(sPos + 1, pPos));
                } else {
                    q.push(new Pair(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 plist = new ArrayList();
        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 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的解.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66509.html

相关文章

  • [Leetcode] Regular Expression Matching 正则表达式匹配

    摘要:为的条件是为,且第个字符也能被成功匹配。而从后往前匹配则不会影响该星号后面星号所匹配的部分,因为已经匹配的部分我们会直接跳过。这样才能防止最后字母没有匹配上,而前面的部分反而把的结尾给匹配了。 Regular Expression Matching Implement regular expression matching with support for . and*. . Mat...

    Vixb 评论0 收藏0
  • [LeetCode] 10. Regular Expression Matching

    Problem Given an input string (s) and a pattern (p), implement regular expression matching with support for . and *. . Matches any single character. * Matches zero or more of the preceding element. Th...

    mo0n1andin 评论0 收藏0
  • 手动实现.*正则表达式匹配函数

    摘要:手动实现正则表达式匹配函数思路使用迭代,当每次判断后令当时特殊处理,注意可以代表到多个之前一个的字符当时,循环判断代表多少个之前一个的字符,如果可以匹配之后的模式,返回,否则注意处理边界值的情况,和为空串时代码可以代表一到多次本题以及其它题 手动实现.*正则表达式匹配函数 regular expression matching . Matches any single charact...

    muddyway 评论0 收藏0
  • Wildcard Matching

    摘要:题目链接这道题还是可以用的方法,用的数组来解,空间复杂度较高。和不同,这道题的符号和前面的没有关系,不需要一起考虑。最坏的情况下,间隔出现且每个都要匹配很多字符,设一个平均匹配里面个字符,。其中,是的长度,是的长度。 Wildcard Matching 题目链接:https://leetcode.com/problems...这道题还是可以用Regular Expression Mat...

    galaxy_robot 评论0 收藏0

发表评论

0条评论

OnlyLing

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<