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

回溯算法 Backtracking

乍看之下,该问题很像一个 字符串匹配 问题。那么我们怎么样去匹配 ' * ' ?
通用的解决算法是,使用 贪心算法: 【尽可能多的匹配 ' * ' 之前的字符】。下面我们分析这种算法可能存在的问题。

s = “abbbc”, p = “ab*c”
假设我们先匹配了 字符 'a',然后我们开始匹配 ‘b*’,我们忽略所有的s中的 'b',到最后,我们遇到字符 'c' ,p和s匹配。

s = “ac”, p = “ab*c”
在匹配‘a’后,因为s中没有任何字符'b',所以略过 'b*',直接匹配最后一个字符 'c',p和s匹配。
到目前为止,贪心算法 表现良好。接着看:

s = “abbc”, p = “ab*bbc”
当匹配‘b*’,我们会略过s中的所有字符'b';之后所有的'b'就无法再被'bb'匹配,使用 贪心算法,这个匹配的返回值将会是false,但我们预计的是 true。
有些人可能会对此提出改进,在s中,计算连续的字符 'b' 的个数,如果个数比在p中‘b*’后的连续b个数小或相等,那么我们认为匹配。

看起来似乎是解决了该问题。我们再看下一个例子:
s = “abcbcd”, p = “a.*c.*d”
这里,p中的“.*” 意味着‘.’ 重复0 or 随意次。因为 ‘.’ 可以匹配任意字符,所以无法确定 ‘.’ 到底应该重复多少次。p中的 ‘c’ 到底去匹配第一个,还是第二个'c' ,不得而知。

所以我们需要使用 回溯backtracking 当匹配失败的时候。 我们返回到上一次匹配成功的状态,并用‘*‘ 匹配更多的s中的字符。 该算法需要使用到 递归recursion。

递归recursion 在以下两种情况下停止:                                                         
  • 如果p中的下一个字符不是 ‘*’, 则它必须匹配当前s中的字符。并继续用下一个字符,去匹配s中的字符。
  • 如果p中的下一个字符是 ‘*’, 那我们必须使用 暴力搜索brute force 去匹配s中的当前字符,不断重复,直到不再匹配

代码

120ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
    bool isMatch(string s, string p) {
        if (p.size() == 0) return s.size() == 0;//一定不能写反
        if (p[1] != '*')
            return (s[0] == p[0] || p[0] == '.'  && !s.empty()) && isMatch(s.substr(1), p.substr(1));
        int i = 0;
        while (s[i] == p[0] || (p[0] == '.' && i < s.size())) {
            if (isMatch(s.substr(i), p.substr(2))) return true;
            ++i;
        }
        return isMatch(s.substr(i), p.substr(2));
    }
};

现实应用中,实际上是使用 正则表达式 grep tool 工具来匹配字符的。

动态规划 Dynamic Programming

首先说明一下规则,dp[m][n] 数组返回值boolean.。它告诉我们长度n的正则表达式,是否匹配长度m的字符串。

  1. dp[m][n]: if s[0..m-1] matches p[0..n-1]
下面看算法的base case:
dp[0][0] 永远是 true
dp[m][0] 当 m > 0, 永远false。空的正则表达式不会匹配任何字符串。
dp[0][n] 可以是 true 或 false。如果p[j - 1] 是'*' 并且p[0..j - 3] 匹配空字符串,那么p[0.., j - 3, j - 2, j - 1] 也匹配空字符串s,
1
2
3
4
5
6
dp[0][0] = true;
for (int i = 1; i <= m; i++)
    dp[i][0] = false;
// p[0.., j - 3, j - 2, j - 1] matches empty if p[j - 1] is '*' and p[0..j - 3] matches empty
for (int j = 1; j <= n; j++)
    dp[0][j] = j > 1 && '*' == p[j - 1] && dp[0][j - 2];
下面来关注 递推关系 recurrence relationship
  1. * dp[i][j]: if s[0..i-1] matches p[0..j-1]
  2. * if p[j - 1] != '*'
  3. * dp[i][j] = dp[i - 1][j - 1] && s[i - 1] == p[j - 1]
  4. * if p[j - 1] == '*', denote p[j - 2] with x
  5. * dp[i][j] is true if any of the following is true
  6. * 1) "x*" repeats 0 time and matches empty: dp[i][j - 2]
  7. * 2) "x*" repeats >= 1 times and matches "x*x": s[i - 1] == x && dp[i - 1][j]
  8. * '.' matches any single character

从而可以得到下面的代码:12ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        vector<vector<bool> > dp(m + 1, vector<bool>(n + 1, false));
 
        dp[0][0] = true;
        for (int i = 1; i <= m; ++i) dp[i][0] = false;
        for (int j = 1; j <= n; ++j) dp[0][j] = j > 1 && p[j - 1] == '*' && dp[0][j - 2];
 
        for (int i = 1; i <= m; ++i)
            for (int j = 1; j <= n; ++j) {
                if (p[j - 1] != '*')
                    dp[i][j] = dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j-1] == '.');
                else
                    dp[i][j] = dp[i][j - 2] //如果* 代表不重复,即空字符
                    || dp[i - 1][j] && (s[i - 1] == p[j - 2] || '.' == p[j - 2]);//* 代表重复>=1次
            }
        return dp[m][n];
    }
};


10. Regular Expression Matching的更多相关文章

  1. leetcode 10 Regular Expression Matching(简单正则表达式匹配)

    最近代码写的少了,而leetcode一直想做一个python,c/c++解题报告的专题,c/c++一直是我非常喜欢的,c语言编程练习的重要性体现在linux内核编程以及一些大公司算法上机的要求,pyt ...

  2. leetcode 10. Regular Expression Matching 、44. Wildcard Matching

    10. Regular Expression Matching https://www.cnblogs.com/grandyang/p/4461713.html class Solution { pu ...

  3. Leetcode 10. Regular Expression Matching(递归,dp)

    10. Regular Expression Matching Hard Given an input string (s) and a pattern (p), implement regular ...

  4. 刷题10. Regular Expression Matching

    一.题目说明 这个题目是10. Regular Expression Matching,乍一看不是很难. 但我实现提交后,总是报错.不得已查看了答案. 二.我的做法 我的实现,最大的问题在于对.*的处 ...

  5. leetcode problem 10 Regular Expression Matching(动态规划)

    Implement regular expression matching with support for '.' and '*'. '.' Matches any single character ...

  6. [LeetCode] 10. Regular Expression Matching 正则表达式匹配

    Given an input string (s) and a pattern (p), implement regular expression matching with support for  ...

  7. [LeetCode] 10. Regular Expression Matching

    Implement regular expression matching with support for '.' and '*'. DP: public class Solution { publ ...

  8. 【leetcode】10.Regular Expression Matching

    题目描述: Implement regular expression matching with support for '.' and '*'. '.' Matches any single cha ...

  9. Java [leetcode 10] Regular Expression Matching

    问题描述: Implement regular expression matching with support for '.' and '*'. '.' Matches any single cha ...

随机推荐

  1. jenkins jmeter持续集成批处理jmx脚本

    这篇文章介绍jenkis jmeter的持续集成,利用jenkins定时任务去批处理执行jmeter的jmx脚本文件,并且生成测试报告 1:jmeter的安装这里我就不在赘述了,如有问题可参考我的jm ...

  2. 关于java的设计模式(转)

    设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  3. Linux安装mysql mysql5.5.40 <NIOT>

    一.    操作系统与软件 操作系统及版本 Centos 6.4 依赖包 gcc.gcc-c++.cmake.ncurses-devel 下载目录 /opt Mysql安装目录 /usr/local/ ...

  4. BGP多线单IP技术实现形式以及其他双线对比

    自从电信与网通分离之后,北方网通与南方电信网络的互联瓶颈问题一直没有得到很好的解决,这个问题也严重困扰广大的ICP服务商.ICP也只能根据自己网站主流用户群是在南方还是在北方,服务重点是在南方还是北方 ...

  5. python demo整理

    1 变量作用域 #!/usr/bin/python # coding=utf-8 name = "whole global name" class Person: name = & ...

  6. redis5--set的操作

    Set集合类型(1)介绍redis的set是string类型的无序集合set元素最大可以包含(2的32次方-1)个元素关于set集合类型除了基本的添加删除操作,其它有用的操作还包含集合的取并集(uni ...

  7. JAVA类与对象(课堂总结)

    一:"=="的不同含义 当"=="施加于原始数据类型变量时,是比较变量所保存的数据是否相等当"=="施加于引用类型变量时,是比较这两个变量是 ...

  8. 初识Spark(Spark系列)

    1.Spark Spark是继Hadoop之后,另外一种开源的高效大数据处理引擎,目前已提交为apach顶级项目. 效率: 据官方网站介绍,Spark是Hadoop运行效率的10-100倍(随内存计算 ...

  9. Gdiplus 贴图(助记) -------------------从资源中载入PNG图片

    从资源中载入图片,亦可改为从内从中加载: void LoadResImage(int nResID,Image * &lpImage) { HINSTANCE hIns=AfxGetInsta ...

  10. 转:Android应用性能测试

    Android用户也许会经常碰到以下的问题: 1)应用后台开着,手机很快没电了——应用耗电大 2)首次/非首次启动应用,进入应用特别慢——应用启动慢 3)应用使用过程中,越来越卡——CPU能力不足/内 ...