题目描述
https://leetcode-cn.com/problems/zheng-ze-biao-da-shi-pi-pei-lcof/
解法(递归思维、动态规划思维)
第一次,直接思考动态规划:
做不出来,下标问题:
class Solution {
public boolean isMatch(String s, String p) {
//边界先处理掉
if(p==null || p.length()==0) return s==null?true:s.length()==0;
//dp[i][j]表示s前i个元素匹配p前j个结果
//这里要加1,则表示s 的前 i 个字符与 p 中的前 j 个字符是否能够匹配
boolean [][]dp = new boolean[s.length()+1][p.length()+1];
//dp的边界处理
dp[0][0] = true;//"" 配 ""为true
//dp[0][..]都是false,dp[..][0]也是false
//递推方程:
for(int i=1;i<=s.length();i++){
for(int j=1;j<=p.length();j++){
if(s.charAt(i-1)==p.charAt(j-1) || p.charAt(j-1)=='.'){
dp[i][j] = dp[i-1][j-1];//匹配一个
}
else if(p.charAt(j-1)=='*' && j>=2){
//必须两个以上
dp[i][j] = dp[i][j-2];//之前匹配的结果
if(s.charAt(i-1)==p.charAt(j-2)){
//匹配1次则继续匹配
dp[i][j] = dp[i-1][j-1] || dp[i][j];
}else{
//不匹配,则丢弃
dp[i][j] = dp[i][j-2];//i不变,j直接跳两位
}
}
}
}
return dp[s.length()][p.length()];
}
}
第二次,一步一步思考,使用递归:
第一次匹配,看谁能走。分别处理三种情况:正常字符、.
字符和*
字符
class Solution {
public boolean isMatch(String s, String p) {
if(p==null || p.length()==0) return s==null?true:s.length()==0;
boolean flag=false;//当前匹配的结果
//匹配第一个字符
if(s!=null &&s.length()>0 && p.length()>0){
if(s.charAt(0)==p.charAt(0) || p.charAt(0)=='.'){
//保留之前的匹配结果
flag = true;
}
}
if(s.length()==0 && p.length()==0) return true;
//出来
// 处理*号的问题
if(p.length()>=2 && p.charAt(1)=='*'){
//整体作为匹配,p【0】和p【1】
//匹配0次,则直接跳过该* 或者选择匹配一次,那么s的下标移动
return isMatch(s,p.substring(2)) || (flag && isMatch(s.substring(1),p));//匹配1次
}else{
//不匹配,直接跳
return flag && isMatch(s.substring(1),p.substring(1));//普通的匹配
}
}
}
使用带备忘录的递归:
boolean的备忘录有点难搞,我们需要使用多一个数组来记录。
class Solution {
boolean [][]memo;//记录结果
boolean [][]res;//记录是否计算过
public boolean isMatch(String s, String p) {
memo = new boolean[s.length()+1][p.length()+1];
res = new boolean[s.length()+1][p.length()+1];//创建大一点防止越界
return isMatch(s,p,0,0);
}
public boolean isMatch(String s, String p,int i,int j) {
if(res[i][j])
return memo[i][j];
if(p==null || p.length()==0){
memo[i][j] = s==null?true:s.length()==0;
res[i][j] = true;
return memo[i][j];
}
boolean flag=false;//当前匹配的结果
//匹配第一个字符
if(s!=null && s.length()>0 && p.length()>0){
if(s.charAt(0)==p.charAt(0) || p.charAt(0)=='.'){
//保留之前的匹配结果
flag = true;
}
}
if(s.length()==0 && p.length()==0) {
memo[i][j] = true;
res[i][j] = true;;//计算过了
return memo[i][j];
};
//出来
// 处理*号的问题
if(p.length()>=2 && p.charAt(1)=='*'){
//整体作为匹配,p【0】和p【1】
//匹配0次,则直接跳过该* 或者选择匹配一次,那么s的下标移动
memo[i][j] = isMatch(s,p.substring(2),i,j+2) || (flag && isMatch(s.substring(1),p,i+1,j));//匹配1次
}else{
//不匹配,直接跳
memo[i][j] = flag && isMatch(s.substring(1),p.substring(1),i+1,j+1);//普通的匹配
}
res[i][j] = true;
return memo[i][j];
}
}
动态规划
认真看题解,然后自己写了一遍:
最容易想错的,是没把类似“a*”整体匹配。
正确的:
dp[i][j] = dp[i][j] | dp[i-1][j];//非短路与
错误:
dp[i][j] = dp[i][j] | dp[i-1][j-1];
class Solution {
public boolean isMatch(String s, String p) {
//边界先处理掉
if(p==null || p.length()==0) return s==null?true:s.length()==0;
//dp[i][j]表示s前i个元素匹配p前j个结果
//这里要加1,则表示s 的前 i 个字符与 p 中的前 j 个字符是否能够匹配
boolean [][]dp = new boolean[s.length()+1][p.length()+1];
//dp的边界处理
dp[0][0] = true;//"" 配 ""为true
//""匹配 a*b* 是true dp[0][....]未知 所以下标i要从0开始
//递推方程:
for(int i=0;i<=s.length();i++){
for(int j=1;j<=p.length();j++){
if(p.charAt(j-1)=='*'){
//需要匹配0次或者1次
// if(j>=2){
dp[i][j] = dp[i][j-2];
// }
//做选择:
if(isMatch(s,p,i,j-1)){
dp[i][j] = dp[i][j] //一次都没匹配
|dp[i-1][j];// 匹配一次 -----------这里为什么不是j-1,我们这里必须整体左匹配,因为如果我们不是整体做匹配,这里如果选择了dp[i-1][j-1]下一轮则没有*了。
// or 两个都要计算,非短路或
//匹配一次,这里就是匹配了一次之后的结果也不能决定总体的结果,
//比如此时是false,但是我一次都不匹配却得到了true
}
}
else{
if(isMatch(s,p,i,j)){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j] = false;
}
}
}
}
return dp[s.length()][p.length()];
}
/*
*建立一个用于匹配的方法,匹配s的第i个字符 == p的第j个字符
*/
public boolean isMatch(String s,String p,int i,int j){
if(i==0){
//都没有字符,
return false;
}
if(p.charAt(j-1)=='.') return true;// .匹配任意字符
return s.charAt(i-1)==p.charAt(j-1);
}
}