原题目:
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
匹配字符串,“.”能够代替所有字符,“*”代表前一个或前多个字符,例如,b*,可以代表0个b,也可以代表bbbb多个b,这便是难点。
算法分析:
算法1:
如果s的第一个字符和p的第一个字符不相等并且p的第一个字符不是'.',直接返回false
情况1:当p的第二个字符不是‘*’,属于简单情况,如果*前面的字符和s的字符不相等并且p的前一个字符不是'.',则直接返回false。其中,利用substring(1)来截掉第一个字符(substring(index)用来从头开始截掉Index长度的字符串)
//case1:when the second char of p is not '*',easy case.if(p.charAt(1) != '*'){if(s.length() < 1){return false;}if((p.charAt(0) != s.charAt(0)) &&(p.charAt(0) != '.')){return false;}else{return isMatch(s.substring(1),p.substring(1));}}
情况2:当p的第二个字符是“*”的时候,复杂情况:
要分0代表0个前导字符或者1个或多个前导字符的情况。
//when the '*' stands for 0 preceding element
if(isMatch(s,p.substring(2))){ //if * its after element could match s,then ,*replace before 0 element
return true;
}
/*when the '*' stands for 1 or more preceding element,
try every possible number*/
int i = 0;
while(i < s.length() && (s.charAt(i) == p.charAt(0) ||
p.charAt(0) == '.')){
if(isMatch(s.substring(i+1),p.substring(2))){
return true;
}
i++;
}
算法:2:
利用动态规划。
动态规划基本思想就是把我们计算过的历史信息记录下来,等到要用到的时候就直接使用,不用重新计算。在这个题里面,假设我们维护一个布尔数组res[i][j],代表s的前i个字符和p的前j个字符是否匹配(注意这里res的维度是s.length()+1,p.length()+1)。递推公式跟上面类似,分三种种情况:
(1)p[j+1]不是'*'。情况比较简单,只要判断如果当前s的i和p的j上的字符一样(如果有p在j上的字符是'.',也是相同),并且res[i][j]==true,则res[i+1][j+1]也为true,res[i+1][j+1]=false;
(2)p[j+1]是'*',但是p[j]!='.'。那么只要以下条件有一个满足即可对res[i+1][j+1]赋值为true:
1)res[i+1][j]为真('*'只取前面字符一次);
2)res[i+1][j-1]为真('*'前面字符一次都不取,也就是忽略这两个字符);
3)res[i][j+1] && s[i]==s[i-1] && s[i-1]==p[j-1](这种情况是相当于i从0到s.length()扫过来,如果p[j+1]对应的字符是‘*’那就意味着接下来的串就可以依次匹配下来,如果下面的字符一直重复,并且就是‘*’前面的那个字符)。
(3)p[j+1]是'*',并且p[j]=='.'。因为".*"可以匹配任意字符串,所以在前面的res[i+1][j-1]或者res[i+1][j]中只要有i+1是true,那么剩下的res[i+1][j+1],res[i+2][j+1],...,res[s.length()][j+1]就都是true了。
这道题有个很重要的点,就是实现的时候外层循环应该是p,然后待匹配串s内层循环扫过来。
LeetCode提交源码:
算法1:
public boolean isMatch(String s,String p){
//base case
if(p.length() == 0){
return s.length() == 0;
}
//special case
if(p.length() == 1){
//if the length of s is 0 return false
if(s.length() < 1){
return false;
}
/* if the first char of s and the first char of p is not the same
and the char of p is not '.',return false*/
else if((p.charAt(0) != s.charAt(0)) && (p.charAt(0) != '.')){
return false;
}
//otherwise, compare the rest of the stirng of s and p
else{
return isMatch(s.substring(1),p.substring(1));
}
}
//case1:when the second char of p is not '*',easy case.
if(p.charAt(1) != '*'){
if(s.length() < 1){
return false;
}
if((p.charAt(0) != s.charAt(0)) &&(p.charAt(0) != '.')){
return false;
}
else{
return isMatch(s.substring(1),p.substring(1));
}
}
//case2: when the second char of p is '*',complex case
else{
//when the '*' stands for 0 preceding element
if(isMatch(s,p.substring(2))){ //if * its after element could match s,then ,*replace before 0 element
return true;
}
/*when the '*' stands for 1 or more preceding element,
try every possible number*/
int i = 0;
while(i < s.length() && (s.charAt(i) == p.charAt(0) ||
p.charAt(0) == '.')){
if(isMatch(s.substring(i+1),p.substring(2))){
return true;
}
i++;
}
return false;
}
}
算法2:
public boolean isMatch(String s,String p){
int m = s.length();
int n = p.length();
if(m == 0 && n == 0)
return true;
if(n == 0){
return false;
}
boolean dp[][] = new boolean[m+1][n+1]; //boolean类型数组默认为false;
/*for(int k = 0; k < m; k++){
for(int q = 0; q < n; q++){
System.out.println(dp[k][q]);
}
}*/
dp[0][0] = true;
for(int j = 0;j < n; j++){
if(p.charAt(j) == '*'){
if(j > 0 && dp[0][j-1])
dp[0][j+1] = true;
if(j < 1)
continue;
/* p[j+1]是'*',但是p[j]!='.'。那么只要以下条件有一个满足即可对res[i+1][j+1]赋值为true:
1)res[i+1][j]为真('*'只取前面字符一次);
2)res[i+1][j-1]为真('*'前面字符一次都不取,也就是忽略这两个字符);
3)res[i][j+1] && s[i]==s[i-1] && s[i-1]==p[j-1](这种情况是相当于i从0到s.length()扫过来,
如果p[j+1]对应的字符是‘*’那就意味着接下来的串就可以依次匹配下来,
如果下面的字符一直重复,并且就是‘*’前面的那个字符)*/
if(p.charAt(j-1) != '.'){
for(int i = 0; i < m;i++){
if(dp[i+1][j] || j > 0 && dp[i+1][j-1] ||
i > 0 && j > 0 && dp[i][j+1] && s.charAt(i)
== s.charAt(i-1)&& s.charAt(i-1) == p.charAt(j-1))
dp[i+1][j+1] = true;
}
}
else{
int i = 0;
while(j > 0 && i < m && !dp[i+1][j-1] && !dp[i+1][j])
i++;
for(;i < m; i++){
dp[i+1][j+1] = true;
}
}
}
else{
for(int i = 0;i < m;i++){
if(s.charAt(i) == p.charAt(j) || p.charAt(j) == '.')
dp[i+1][j+1] = dp[i][j];
}
}
}
return dp[m][n];
}
完整运行程序:
/**************************************************************
* Copyright (c) 2016
* All rights reserved.
* 版 本 号:v1.0
* 题目描述: Regular Expression Matching
* 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).
* 输入描述:请输入第一个字符串:
* bbbbba
* 请输入第二个字符串:
* b*a
* 程序输出:算法1:两个字符串是否匹配:false
* 算法2:两个字符串是否匹配:true
* 算法3:两个字符串是否匹配:true
* 问题分析:
* 算法描述:算法1:简单判断,分别考虑多种情况,但算法运行时间较长
* 算法2:利用动态规划
* 动态规划基本思想就是把我们计算过的历史信息记录下来,等到要用到的时候就直接使用,不用重新计算。
* 在这个题里面,假设我们维护一个布尔数组res[i][j],代表s的前i个字符和p的前j
* 个字符是否匹配(注意这里res的维度是s.length()+1,p.length()+1)。
* 完成时间:2016-11-23
***************************************************************/
package org.GuoGuoFighting.LeetCode010;
import java.util.Scanner;
/*算法1:错误的算法,无法得到正确结果*/
class SolutionMethod1{
public boolean isMatch(String s,String p){
if(s == null || p == null){
return false;
}
int slen = s.length();
int plen = p.length();
int i = slen-1;
int j = plen-1;
//boolean flag = false;
while(i > 0 && j > 0){
while(s.charAt(i) == s.charAt(i-1)){
i--;
}
if(s.charAt(i) == p.charAt(j) || p.charAt(j) == '.'){
if(i > 0 && j >0){
i--;
j--;
System.out.println("i=" + i + "j=" + j);
return isMatch(s.substring(0, i), p.substring(0, j));
//return MatchFunction(s,i, p,j);
}
else if(i == 0 && j == 0){
return true;
}
else if(s.charAt(i) == s.charAt(i-1)){
i--;
}
}
else if(p.charAt(j) == '*' ){
j--;
return isMatch(s.substring(0, i), p.substring(0, j));
}
else{
return false;
}
}
if( i == 0 && j== 0 && s.charAt(i)== p.charAt(j)){
return true;
}
else
return false;
}
}
class SolutionMethod2{
public boolean isMatch(String s,String p){
//base case
if(p.length() == 0){
return s.length() == 0;
}
//special case
if(p.length() == 1){
//if the length of s is 0 return false
if(s.length() < 1){
return false;
}
/* if the first char of s and the first char of p is not the same
and the char of p is not '.',return false*/
else if((p.charAt(0) != s.charAt(0)) && (p.charAt(0) != '.')){
return false;
}
//otherwise, compare the rest of the stirng of s and p
else{
return isMatch(s.substring(1),p.substring(1));
}
}
//case1:when the second char of p is not '*',easy case.
if(p.charAt(1) != '*'){
if(s.length() < 1){
return false;
}
if((p.charAt(0) != s.charAt(0)) &&(p.charAt(0) != '.')){
return false;
}
else{
return isMatch(s.substring(1),p.substring(1));
}
}
//case2: when the second char of p is '*',complex case
else{
//when the '*' stands for 0 preceding element
if(isMatch(s,p.substring(2))){ //if * its after element could match s,then ,*replace before 0 element
return true;
}
/*when the '*' stands for 1 or more preceding element,
try every possible number*/
int i = 0;
while(i < s.length() && (s.charAt(i) == p.charAt(0) ||
p.charAt(0) == '.')){
if(isMatch(s.substring(i+1),p.substring(2))){
return true;
}
i++;
}
return false;
}
}
}
class SolutionMethod3{
public boolean isMatch(String s,String p){
int m = s.length();
int n = p.length();
if(m == 0 && n == 0)
return true;
if(n == 0){
return false;
}
boolean dp[][] = new boolean[m+1][n+1]; //boolean类型数组默认为false;
/*for(int k = 0; k < m; k++){
for(int q = 0; q < n; q++){
System.out.println(dp[k][q]);
}
}*/
dp[0][0] = true;
for(int j = 0;j < n; j++){
if(p.charAt(j) == '*'){
if(j > 0 && dp[0][j-1])
dp[0][j+1] = true;
if(j < 1)
continue;
/* p[j+1]是'*',但是p[j]!='.'。那么只要以下条件有一个满足即可对res[i+1][j+1]赋值为true:
1)res[i+1][j]为真('*'只取前面字符一次);
2)res[i+1][j-1]为真('*'前面字符一次都不取,也就是忽略这两个字符);
3)res[i][j+1] && s[i]==s[i-1] && s[i-1]==p[j-1](这种情况是相当于i从0到s.length()扫过来,
如果p[j+1]对应的字符是‘*’那就意味着接下来的串就可以依次匹配下来,
如果下面的字符一直重复,并且就是‘*’前面的那个字符)*/
if(p.charAt(j-1) != '.'){
for(int i = 0; i < m;i++){
if(dp[i+1][j] || j > 0 && dp[i+1][j-1] ||
i > 0 && j > 0 && dp[i][j+1] && s.charAt(i)
== s.charAt(i-1)&& s.charAt(i-1) == p.charAt(j-1))
dp[i+1][j+1] = true;
}
}
else{
int i = 0;
while(j > 0 && i < m && !dp[i+1][j-1] && !dp[i+1][j])
i++;
for(;i < m; i++){
dp[i+1][j+1] = true;
}
}
}
else{
for(int i = 0;i < m;i++){
if(s.charAt(i) == p.charAt(j) || p.charAt(j) == '.')
dp[i+1][j+1] = dp[i][j];
}
}
}
return dp[m][n];
}
}
public class RegularExpressionMatching {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
System.out.println("请输入第一个字符串:");
String s = scanner.nextLine();
System.out.println("请输入第二个字符串:");
String p = scanner.nextLine();
scanner.close();
SolutionMethod1 solution1 = new SolutionMethod1();
System.out.println("算法1:两个字符串是否匹配:" + solution1.isMatch(s, p));
SolutionMethod2 solution2 = new SolutionMethod2();
System.out.println("算法2:两个字符串是否匹配:" + solution2.isMatch(s, p));
SolutionMethod3 solution3 = new SolutionMethod3();
System.out.println("算法3:两个字符串是否匹配:"+ solution3.isMatch(s, p));
}
}
程序运行结果: