[LeetCode

来源:互联网 发布:java中xml文件的作用 编辑:程序博客网 时间:2024/05/29 15:52

1 问题

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

2 分析

2.1 该问题的递归性

如果不考虑".""*", 检查两个字符串是否匹配的方法是逐位比较,如果这两个字符串有一个位置上的字符不相等,那么这两个字符串不匹配,如果两个字符串所有位置均对应相等,那么这两个字符串匹配。
接下来考虑存在".""*"的情况:

  • "."匹配任意一个字符,如果待比较的两个字符串某一位置上的字符是".", 则认为这两个位置上的字符相等,继续下一个位置的比较。
  • "*"可以匹配0个或者多个紧邻的前一个字符。即"*"的存在使该字符串变成了多个可能的字符串。因此,必须将"*"能够表示的所有情况与另外一个字符串相比较,如果存在一种匹配的情况,那么这两个字符串匹配,反之,这两个字符串不匹配。
    • "*"存在时,"*"和其紧邻的前一个字符(设为m)有如下三种可能的匹配情况:
    • 空字符串”“
    • 单一字符m
    • 多个字符mm...

在对"*"表示的所有可能情况,进行搜索、确定是否匹配的过程中,如果一种情况匹配失败,就要回溯,选择下一种可能情况来进行操作。因此,该问题具有递归性。

2.2 递归解法

递归,回溯解决该问题的伪代码如下:

isMatch(String s, String p):

  • base case:
    • if p == "": //对应情况:模式字符串匹配完毕
      • if s == "": return true
      • else: return false
    • if s == "": //对应情况:源字符串匹配完毕,模式字符串有剩余
      • if p的长度大于1,且p的第二个字符是*: return isMatch(s,p[2:end])
      • else: return false
  • 递归过程:
    • if p的长度大于1 且 p[1] == "*"://对应情况:str*的匹配
      • if s[0] == p[0] or p[0] == ".":
        • if isMatch(s[1:end],p[2:end]): return true//对应情况:将str*视为单个字符
        • if isMatch(s[1:end], p): return true; //对应情况:将str*视为多个字符
      • if isMatch(s,p[2, end]): return true;//对应情况:将str*视为空
      • return false
    • else if s[0] == p[0] or p[0] == ".":
      • return isMatch(s[1:end], p[1:end])
    • else: return false

匹配字符串str*至少会存在3种子问题,因此, 算法的时间复杂度与模式字符串中"*"的个数有关系,设其为n,则递归算法的时间复杂度是O(3n)

2.3 使用动态规划优化代码

  1. 2.1-2.2节分析得出该问题具有递归性,可以分解为多个子问题求解。下面证明:该问题的子问题是重叠的。
    "a*b*c为例:
    重叠子问题
    上图中虚线方框内的子问题在递归过程中被重复求解,因此子问题是重叠的。
  2. 该问题具有最优子结构,如果sp整个字符串匹配,那么它们任何一个对应子字符串匹配。

通过上述分析,可以得出结论: 动态规划可以用于优化代码。
dp[i][j]表示s[0:i],p[0,j]是否匹配。 dp[i][j]可以通过2.2节中分析的子问题关系逐步计算出来,计算方法为:

 If p.charAt(j) == s.charAt(i) :  dp[i][j] = dp[i-1][j-1]; If p.charAt(j) == '.' : dp[i][j] = dp[i-1][j-1]; If p.charAt(j) == '*':     if p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2]       if p.charAt(i-1) == s.charAt(i) or p.charAt(i-1) == '.':                              dp[i][j] = dp[i-1][j]                           or dp[i][j] = dp[i][j-1]                           or dp[i][j] = dp[i][j-2]

经过动态规划优化后算法的时间复杂度为O(mn), 其中m,n分别表示s,p的长度。

3 代码

3.1 递归解法:

public class Solution {    public boolean isMatch(String s, String p) {        System.out.println("S: "+s + " P: " + p);        //base case        if(p.length() == 0){            if(s.length() == 0){                return true;            }else{                return false;            }        }        if(s.length() == 0){            if(p.length() > 1 && p.charAt(1) == '*'){                return isMatch(s, p.substring(2));            }else{                System.out.println(s+" "+p);                return false;            }        }        //recursive procedure        if(p.length() > 1 && p.charAt(1) == '*'){            if( s.charAt(0) == p.charAt(0) || p.charAt(0) == '.'){                if(isMatch(s.substring(1), p.substring(2))){                    return true;                }                if(isMatch(s.substring(1), p)){                    return true;                }            }            if(isMatch(s, p.substring(2))){                return true;            }            return false;        }else if(s.charAt(0) == p.charAt(0) || p.charAt(0) == '.'){            return isMatch(s.substring(1), p.substring(1));        }else{            return false;        }    }}

3.2 dp解法

public class Solution {    public boolean isMatch(String s, String p) {        if (s == null || p == null) {        return false;    }    boolean[][] dp = new boolean[s.length()+1][p.length()+1];    dp[0][0] = true;    for (int i = 0; i < p.length(); i++) {        if (p.charAt(i) == '*' && dp[0][i-1]) {            dp[0][i+1] = true;        }    }    for (int i = 0 ; i < s.length(); i++) {        for (int j = 0; j < p.length(); j++) {            if (p.charAt(j) == '.') {                dp[i+1][j+1] = dp[i][j];            }            if (p.charAt(j) == s.charAt(i)) {                dp[i+1][j+1] = dp[i][j];            }            if (p.charAt(j) == '*') {                if (p.charAt(j-1) != s.charAt(i) && p.charAt(j-1) != '.') {                    dp[i+1][j+1] = dp[i+1][j-1];                } else {                    dp[i+1][j+1] = (dp[i+1][j] || dp[i][j+1] || dp[i+1][j-1]);                }            }        }    }    return dp[s.length()][p.length()];}

[1] dp解法的代码来源于 https://discuss.leetcode.com/topic/40371/easy-dp-java-solution-with-detailed-explanation

0 0
原创粉丝点击