★leetcode10_Regular Expression Matching[附动态规划]

来源:互联网 发布:java迭代器 实现类 编辑:程序博客网 时间:2024/06/10 19:54

一.问题描述

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") → falseisMatch("aa","aa") → trueisMatch("aaa","aa") → falseisMatch("aa", "a*") → trueisMatch("aa", ".*") → trueisMatch("ab", ".*") → trueisMatch("aab", "c*a*b") → true
注:这个题目还是需要认真审题的,'c*'是一体的,代表0个或多个c;'.*'则代表0个或多个任意字符。

二.代码编写

这一题需要考虑的情况很多,我在自行设计算法的时候没考虑清楚,提交的时候跳出来的特殊情况导致了我的整个算法都得推翻重来。其中,'.*'的匹配情况完全没办法通过打补丁来解决。

多次推导重来之后再网上搜索了该题的解法。解法基本上就是递归和dp两种思想,但是递归解法会导致TLE。


三.算法思想

递归思想:参考http://pengbos.com/algorithms/leetcode-regular-expression-matching-solution

String match, and it is a typical DP problem.

create a two dimension table dp[s.length()+1][p.length()+1]

so dp[i][j] means whether string [0, i-1] matches pattern [0, j-1]
and dp[i+1][j+1] means whether string [0, i] matches pattern [0, j]

to initialize, dp[0][0] should be true, which means empty string matches empty string.
and dp[i][0] should be always false, when string is not empty, but the pattern is empty.
and dp[0][j] depends on whether there are ‘*’, because * could match empty string, for example ‘a*’ could match an empty string

to get the value dp[i+1][j+1]:
1. the simplest way, if s[i]==p[j]|| p[j]==’.’, that means we need to check whether s[0, i-1] matches p[0, j-1]
dp[i+1][j+1] = dp[i][j]

2. the complicated way, if(p[j]==’*’), that means we need to check the wildcard
the wildcard may take effective from 0 time to many times
if the wild card plays 0 time, then it matches an empty string with the previous character.
in this scenario, dp[i+1][j+1] = dp[i+1][j-1]

if the wild card plays once, then dp[i+1][j+1] = dp[i][j-1] && (s[i]==p[j-1] || p[j-1] == ‘.’)

if the wild card plays many times, the dp[i+1][j+1] = dp[i][j+1] && (s[i]==p[j-1] || p[j-1] == ‘.’)

3. the other scenarios, the default value “false” will be populated for the rest.

看完这个解法之后觉得自己有必要补一补动态规划的思想了- - (2016.09.18)

决定09.19补一补dp再来更新!!! 这道题卡了几天,,,我决定从再去刷一道非hard的题重拾信心。。。。


四.扩展--动态规划介绍

【注:关于dp思想理解的一个很好的故事http://www.cnblogs.com/sdjl/articles/1274312.html】

(1)动态规划dynamic programming:

通常用于求解某种具有最优性质的问题,一般用于多阶段决策问题。其思路是将待求解问题分成若干个非相互独立的(避免了重复求解,不同于分治法)子问题,先求子问题,最终得到原问题的解。【由于dp面向的问题多数有重叠子问题的特点,为避免重复计算,对每个子问题只解一次病将不同阶段的不同状态保存在一个二维数组中。】

(2)dp的适用范围

适用dp的问题必须满足最优化原理和无后效性。

1.最优化原理:如果问题的最优解包含的子问题的解也是最优解,则称该问题具有最有子结构,即满足最优化原理。(也即子结构最优时通过选择后一定最优)

2.无后效性某阶段的状态一旦确定,则此后过程的演变不再受此前各种状态及决策的影响,简单的说,就是“未来与过去无关”,当前的状态是此前历史的一个完整总结,此前的历史只能通过当前的状态去影响过程未来的演变。

3.重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)。其实本质上dp就是一个以空间换时间的做法,为了降低时间复杂度,不对子问题进行重复计算,其必须存储过程中的各种状态。

(3)动态规划问题的求解过程

 动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。如图所示。动态规划的设计都有着一定的模式,一般要经历以下几个步骤。

    初始状态→│决策1│→│决策2│→…→│决策n│→结束状态

                      动态规划决策过程示意图

    A.划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。

    B.确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性

    C.确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。

    D.寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。

    一般,只要解决问题的阶段、状态和状态转移决策确定了,就可以写出状态转移方程(包括边界条件)。

(4)经典dp问题之-最大连续子串和:

问题:给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j <= K。最大连续子序列是所有连续子序中元素和最大的一个, 例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和为20。

边界条件:max_sum[0]=0代表空数组,max_sum[1]=data[0]。

状态转移方程: max_sum[i]=max(data[i], max_sum[i-1]+data[i])


五.动态规划与leetcode10

(1)划分阶段:

(2)确定状态:dp[i][j](代表s[:i-1]p[:j-1]是否match)

(3)边界条件:

A. dp[0][0] = True (代表两个空数组match)

B. dp[i][0] = False (代表s为空,p不为空则必然不匹配)

C. dp[0][j] = dp[0][j-2] if p[j-1]=='*' and j>=2

(4)状态转移方程:(分情况考虑)

A. p[j] != '*' 时,

if p[j] != '*' :

dp[i+1][j+1] = dp[i][j] and (s[i] == p[j] or p[j] == '.' )

B. p[j] == '*' : (重点考虑该情况)

if  p[j] == '*' :

a. 该 '*' match了s中的0个字符:【注:该处(及下方)提到的match了x个字符都是就当前而言的,不考虑s及p的第i、j之后的字符match情况】

dp[i+1][j+1] = dp[i+1][j-1] 

b. 该 '*' match了s中的1个字符:

dp[i+1][j+1] = dp[i][j-1] and(s[i] == p[j-1] or p[j-1] == '.' )

c. 该 '*' match了s中的多个字符:

dp[i+1][j+1] = dp[i][j+1] and (s[i] == p[j-1] or p[j-1] == '.' )

有了边界条件和状态转移方程,实现就变得十分简单了,代码如下:

class Solution(object):    def isMatch(self, s, p):        """        :type s: str        :type p: str        :rtype: bool        """        # dynamic programming        len_s = len(s)        len_p = len(p)        # initialize        dp = [[False for i in range (len_p+1)] for j in range(len_s+1)]        dp[0][0] = True        # initialize dp[0][j]        for j in range(1,len_p):            if p[j] == '*':                dp[0][j+1] = dp [0][j-1]        for i in range(1,len_s+1):            for j in range(1,len_p+1):                if s[i-1] == p[j-1] or p[j-1] == '.':                    dp[i][j] = dp[i-1][j-1]                elif p[j-1] == '*':                    dp[i][j] = (dp[i-1][j-2] and (p[j-2] == s[i-1] or p[j-2] == '.')) \                               or dp[i][j-2] or (dp[i-1][j] and (s[i-1] == p[j-2] or p[j-2] =='.'))        return dp[len_s][len_p]



1 0
原创粉丝点击