题目:

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

代码:

class Solution {
public:
bool isMatch(string s, string p) {
// both s and p reach their ends
if ( p.empty() ) return s.empty();
// p's next is not *
if ( p[]!='*' )
{
return ( s[]==p[] || (p[]=='.' && !s.empty()) ) && Solution::isMatch(s.substr(), p.substr());
}
// p's next is * and curr s match curr p
int i = ;
for ( ; s[i]==p[] || (p[]=='.' && i<s.size()); ++i)
{
if ( Solution::isMatch(s.substr(i), p.substr()) ) return true;
}
// p's next is * but curr s not match curr p
return Solution::isMatch(s.substr(i), p.substr());
}
};

tips:

这个代码在Leetcode最新接口上,是可以AC的。

思路有些复杂:主要判断下一个p是否是*,并分类讨论。

虽然代码AC了,但是有些疑惑的地方依然存在:

1. p[1]!='*' 这个语句是会遇到p[1]不存在(索引越界)的情况的,在我的mac上返回的结果是NUL,但是OJ可以通过。

2. p.substr(2)这个语句也会遇到与1同样的问题。

虽然OJ通过了,但是还有各种边界隐患。如果string索引越界了,应该是引发未定义行为,但是不知道为何能通过。增加了一个修正的版本,如下,对于p.size()==1的情况单独处理。

class Solution {
public:
bool isMatch(string s, string p) {
// both s and p reach their ends
if ( p.empty() ) return s.empty();
if ( p.size()== )
{
if ( s.empty() )
{
return false;
}
else
{
return ( s[]==p[] || p[]=='.' ) && Solution::isMatch(s.substr(), p.substr());
}
}
// p's next is not *
if ( p[]!='*' )
{
return ( s[]==p[] || (p[]=='.' && !s.empty()) ) && Solution::isMatch(s.substr(), p.substr());
}
// p's next is * and curr s match curr p
int i = ;
for ( ; s[i]==p[] || (p[]=='.' && i<s.size()); ++i)
{
if ( Solution::isMatch(s.substr(i), p.substr()) ) return true;
}
// p's next is * but curr s not match curr p
return Solution::isMatch(s.substr(i), p.substr());
}
};

这个版本是可以AC的,但自测的case还是有问题:

string s = "aaa"

string p = "c*a*b**"

对于连续两个*的就有问题,不知道是否是题意理解有误,不能出现连续两个*。

后续想清楚了,连续两个*属于非法输入,因为*相当于没有前驱节值了

===============================================================================

改用DP做一遍,看一下边界条件是否可以简化些。

代码:

class Solution {
public:
bool isMatch(string s, string p) {
const size_t len_s = s.length();
const size_t len_p = p.length();
bool dp[len_s+][len_p+];
// dp pre setting
dp[][] = true;
for (size_t i = ; i <=len_s; ++i) dp[i][] = false;for (size_t j = 1; j <=len_p; ++j)
{
if (p[j-]!='*')
{
dp[][j] = false;
}
else
{
dp[][j] = dp[][j-];
}
}
// dp process
for ( size_t i = ; i <=len_s; ++i )
{
for ( size_t j = ; j <=len_p; ++j )
{
if ( p[j-]!='*' )
{
dp[i][j] = ( s[i-]==p[j-] || p[j-]=='.' ) && dp[i-][j-] ;
}
else
// why p[j-2] and dp[i][j-2] not exceed the range?
// 'cause if a '*' occur, it must have a preceeding char
// So, j-2 is guaranteed non-negative
{
if ( dp[i][j-] || dp[i][j-] )
{
dp[i][j] = true;
}
else if ( dp[i-][j] )
{
dp[i][j] = s[i-]==p[j-] || p[j-]=='.';
}
else
{
dp[i][j] = false;
}
}
}
}
return dp[len_s][len_p];
}
};

tips:

DP的解法效率很高(8ms 9ms过大集合)。

大体思路:与递归算法类似,都是按照当前元素的下一个是否是*来分类讨论的。客观上dp效率更高,主观上个人觉得dp对于边界的界定更强。

1.关于dp[len_s+1][len_p+1]

这里定义了dp[len_s+1][len_p+1]的数组,dp[i][j]代表s[0~i-1]与p[0~j-1]是否匹配。

那么多了1个长度是干什么的呢?为了使得dp过程能进行下去,可以想象在s和p字符串前面都插入了一个空白字符。

因此dp[0][0]代表s和p都是空白的情况,自然也匹配(所以dp[0][0]=true)。在3.再说如何初始化dp[][]

2. 状态转移递推公式

这部分是dp的核心,先按照p的下一个元素是否是*来分类的

如果不是*,则需要dp[i-1][j-1] && s[i-1]==p[j-1] || p[j-1]=='.'

如果是*,则分以下三种:

  a. 如果dp[i][j-1]==true,则证明不用这个‘*’也能给匹配了,则直接dp[i][j]=true

  b. 如果dp[i][j-2]==true,则证明不用这个*,并且捎带脚把p[j-2]也给去了,还能匹配上,那就更是dp[i][j]=true

  c. 如果dp[i-1][j]==true,则说明,加上这个*,能把s[0]~s[i-1]都匹配上;现在多了一个*,能不能匹配呢?再分两种情况:

    c1. 如果p[j-2]==s[i-1],则加上p[j-1]的这个*,就相当于多复制出来一个p[j-2]与s[i-1]对位,则dp[i][j]=true

    c2. 如果p[j-2]=='.',则加上p[j-1]的这个*,相当于由万能的‘.’衍生出来一个与s[i-1]相同的字符再与s[i-1]对位,则dp[i][j]=true

  其余情况,则dp[i][j]=false;

  这个过程可以看到,利用dp做这个题目比greedy算法要有优势。因为dp保存了每一轮子比较的结果,可以方便回溯;而greedy方法,一般只看当前的情况(如果要保存每一轮的教过,则灰常麻烦),本人一开始就试图用greedy方法,最后无奈放弃了。

3. dp[][]的初始化

再确定了dp的思路之后,为了要让dp过程能够起来,必须对dp状态进行初始化。我的逻辑顺序是,先确定dp的大体过程,然后再考虑如何初始化dp数组让过程起来

考虑dp[][]定义多了一维(即s和p开头都多了一个空字符),因此需要初始化两种极端情况。

  a. dp[i][0] 即s取前i个元素,p一个不取:显然都是false,不能匹配

  b. dp[0][j] 即s一个不取,p取前j个元素:

    b1. 如果p[j-1]!='*',则必然无法都消除p[0~j-1],至少留着了p[j-1]

    b2. 如果p[j-1]=='*',则*前面的p[j-2]可以被这个*消除了,如果再有dp[0][j]==dp[0][j-2]

至此,dp初始化完成,都可以跑起来了。

4 关于如何保证'j-2'这类下标不会越界

这个问题困扰了我一段时间。后来终于明白了,OJ中给出的case都是合法的:即只要出现*,则它前面必然要有不是*的某个字符。

这种合法性具体体现在两点:

  a. p开头不会有*

   b. p任何位置不会有连续的两个*

注意,但凡出现'j-2'这类下标的地方,都是需要向前回溯的;又因为,只有下一个元素是*的时候,才需要向前回溯;因此,j-2这类下标就无形中得到了保证,不会越界。(但这种保障是leetcode的test case保证的,实际中不一定行)

Dynamic Programming方法在这道题上非常恰当,因为Greedy显然不利于回溯,而这道题要处理的case还多,几乎都涉及到前一个状态,后一个状态。因此毫无疑问DP是最佳的选择。

最后记录几个参考的日志:

http://www.bubuko.com/infodetail-604758.html

http://www.makuiyu.cn/2015/01/LeetCode_10.%20Regular%20Expression%20Matching/

http://blog.csdn.net/yangliuy/article/details/43834477

================================================

第二次过这道题,对于其他的方法已经都忘记了,首先想到的就是dp;基本照着原来的代码重新记着写了一遍。

class Solution {
public:
bool isMatch(string s, string p) {
bool dp[s.size()+][p.size()+];
std::fill_n(&dp[][], (s.size()+)*(p.size()+), false);
dp[][] = true;
for ( int i=; i<=s.size(); ++i ) dp[i][] = false;
for ( int j=; j<=p.size(); ++j )
{
if ( p[j-]!='*' ){
dp[][j] = false;
}
else{
dp[][j] = dp[][j-];
}
}
for ( int i=; i<=s.size(); ++i )
{
for ( int j=; j<=p.size(); ++j )
{
if ( p[j-]!='*' )
{
dp[i][j] = dp[i-][j-] && (s[i-]==p[j-] || p[j-]=='.');
}
else
{
if ( dp[i][j-] || dp[i][j-] )
{
dp[i][j] = true;
}
else if ( dp[i-][j] )
{
dp[i][j] = p[j-]==s[i-] || p[j-]=='.';
}
else
{
dp[i][j] = false;
}
}
}
}
return dp[s.size()][p.size()];
}
};

(1)要按照p中当前是否是*讨论

(2)其余的都条件判断就是背一背吧

【 Regular Expression Matching 】cpp的更多相关文章

  1. 【LeetCode算法题库】Day4:Regular Expression Matching & Container With Most Water & Integer to Roman

    [Q10] Given an input string (s) and a pattern (p), implement regular expression matching with suppor ...

  2. 【leetcode】Regular Expression Matching

    Regular Expression Matching Implement regular expression matching with support for '.' and '*'. '.' ...

  3. 【leetcode】Regular Expression Matching (hard) ★

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

  4. 【JAVA、C++】LeetCode 010 Regular Expression Matching

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

  5. 【leetcode】10.Regular Expression Matching

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

  6. 【一天一道LeetCode】#10. Regular Expression Matching

    一天一道LeetCode系列 (一)题目 Implement regular expression matching with support for '.' and '*'. '.' Matches ...

  7. 【leetcode刷题笔记】Regular Expression Matching

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

  8. 【LeetCode】010. Regular Expression Matching

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

  9. 【WildCard Matching】cpp

    题目: Implement wildcard pattern matching with support for '?' and '*'. '?' Matches any single charact ...

随机推荐

  1. Android 中断线程的处理

    我现在对一个用户注册的功能1.用ProgressDialog将当前页面设成不可操作(保留返回键 退出ProgressDialog)2.用一个线程clientThread执行数据的提交和返回 问题:考虑 ...

  2. java 求第n小的质数

    package a小米; import java.util.Scanner; public class prime { public static boolean isPrime(int n){ fo ...

  3. dnw-linux的安装及使用

    <Tiny6410刷机指南>介绍了如何用USB线下载uboot,kernel,filesystem到开发板的nand flash,USB下载文件用到工具dnw.遗憾的是该教程提供的是win ...

  4. 搭建高性能计算环境(二)、远程登录Linux服务器

    一般操作Linux系统都是通过远程登录使用的,本节介绍几种远程登录Linux.上传下载文件的工具. 1. Secure Shell SSH 简单方便.既能使用命令行登陆也能传文件,免费. 打开SSH ...

  5. Everyday is an Opportunity

    Quote Of The Day: “Everyday is an Opportunity to Learn and Grow, Don’t Waste Your Opportunity.” – Al ...

  6. EasyUI datagrid checkbox数据设定与取值(转自http://blog.csdn.net/baronyang/article/dnetails/9323463,感谢分享,谢谢)

    这一篇将会说明两种使用 jQuery EasyUI DataGrid 的 Checkbox 设定方式,以及在既有数据下将 checked 为 true 的该笔数据列的 Checkbox 设定为 Che ...

  7. 小菜的系统框架界面设计-数据的完美呈现(DataGridView扩展)

    背景 今天在做系统报表的过程中,我想实现批量操作DataGridView中的数据,在列中加复选框,通过一个事件触发进行全选或取消,可是在外面添加按钮,这种模式虽然能够实现,但是从系统界面设计的角度,美 ...

  8. Apache中RewriteCond规则参数介绍

    Apache中 RewriteCond语句对于我来说一直是个难点,多次试图去把它搞明白,都没有结构,这次我终于算大概知道它的意思了.RewriteCond就像我们程序中的if语句一样,表示如果符合某个 ...

  9. struts2集成javamail发邮件(带附件)实践记录

    一.代码预览 这两天在做struts2上的邮件发送.以前的项目有用到spring,用spring提供的邮件支持类很方便可以完成这个功能,但是现在只用struts2的话,就碰到了一系列的问题. 代码是从 ...

  10. Android Activity生命周期以及Fragment生命周期的区别与分析

    Android Fragment生命周期图: Activity生命周期图: 对照图: Fragment生命周期分析: 1. 当一个fragment被创建的时候,它会经历以下状态. onAttach() ...