44. 通配符匹配

题目:https://leetcode-cn.com/problems/wildcard-matching/

双指针法的解题思路参考力扣上的代码范例

代码运行结果:

执行用时 :3 ms, 在所有 Java 提交中击败了99.76%的用户

内存消耗 :40.1 MB, 在所有 Java 提交中击败了50.00%的用户

1. 方法一:动态规划(基础版)

使用动态规划时,解题思路和 10. 正则表达式匹配 非常相似,代码如下:

class Solution {
    public boolean isMatch(String s, String p) {
        int sl = s.length(), pl = p.length();
        
        //空的pattern只能匹配空字符串
        if(pl == 0)
            return sl == 0;
        
        //dp[i][j]表示p的前j个字符能否匹配s的前i个字符
        boolean[][] dp = new boolean[sl + 1][pl + 1];
        
        //空的pattern可以匹配空字符串
        dp[0][0] = true;

        //空的pattern不能匹配非空字符串
        for(int i = 1; i <= sl; i++)
            dp[i][0] = false;

        //pattern为"*"时可以匹配空字符串
        dp[0][1] = p.charAt(0) == '*';

        //当pattern长度大于1时,只有当pattern是"****"形式的才可以匹配空字符串
        for(int j = 2; j <= pl; j++)
            dp[0][j] = p.charAt(j - 1) == '*' && dp[0][j - 1];
        
        //开始动态规划
        char sc, pc;
        for(int i = 1; i <= sl; i++){
            sc = s.charAt(i - 1);
            for(int j = 1; j <= pl; j++){
                pc = p.charAt(j - 1);

                //如果pc不是'*',说明pc代表了某一个具体的字符
                //此时需要sc和pc匹配且dp[i - 1][j - 1]为true时dp[i][j]才为true
                if(pc != '*')
                    dp[i][j] = isMatchChar(sc, pc) && dp[i - 1][j - 1];
                
                //如果pc是'*',那么pc可以代表任意长的字符串,我们假设此时p前j个字符为"abc*"
                
                //1. 假设'*'匹配的是空字符串,那么只有当dp[i][j - 1]为true时dp[i][j]才是true
                //   举例:如果s前i个字符为"abd",那么p前j - 1个字符"abc"不能匹配s前i个字符"abd"
                //         那么"abc*"肯定也不能匹配"abd"
                
                //2. 假设'*'匹配的是任一字符,那么只有当dp[i - 1][j - 1]为true时dp[i][j]才是true
                //   举例:如果s前i个字符为"abcd",那么p前j - 1个字符"abc"能匹配s前i - 1个字符"abc"
                //         那么"abc*"肯定也能匹配"abcd"
                
                //3. 假设'*'匹配的是任两字符,那么只有当dp[i - 2][j - 1]为true时dp[i][j]才是true
                //   举例:如果s前i个字符为"abddd",那么p前j - 1个字符"abc"不能匹配s前i - 2个字符"abd"
                //         那么"abc*"肯定也不能匹配"abddd"

                //综上,dp[0][j - 1]到dp[i][j - 1]中只要有一个为true,那么dp[i][j]就为true
                else
                    dp[i][j] = existTrue(dp, i, j - 1);
            }
        }
        return dp[sl][pl];
    }

    private boolean isMatchChar(char s, char p){
        return p == '?' || s == p;
    }

    //查看dp[0:i][j]中是否存在至少一个为true
    private boolean existTrue(boolean[][] dp, int i, int j){
        for(int x = i; x >= 0; x--)
            if(dp[x][j])
                return true;
        return false;
    }
}

2. 方法二:动态规划(改进版)

上面的方式由于每次循环都可能需要调用existTrue()方法,时间复杂度是很高的(执行用时110ms左右)

因此再定义一个数组来保存中间量,避免重复计算,代码如下(改进后执行用时32ms左右):

class Solution {
    public boolean isMatch(String s, String p) {
        int sl = s.length(), pl = p.length();
        
        //空的pattern只能匹配空字符串
        if(pl == 0)
            return sl == 0;
        
        //dp[i][j]表示p的前j个字符能否匹配s的前i个字符
        boolean[][] dp = new boolean[sl + 1][pl + 1];

        //colExistTrue[j]表示第j列是否已经出现了true
        boolean[] colExistTrue = new boolean[pl + 1];
        
        //空的pattern可以匹配空字符串
        dp[0][0] = true;

        //第0列已经出现了true
        colExistTrue[0] = true;

        //空的pattern不能匹配非空字符串
        for(int i = 1; i <= sl; i++)
            dp[i][0] = false;

        //pattern为"*"时可以匹配空字符串,如果可以匹配,那么第一列出现了true
        colExistTrue[1] = dp[0][1] = p.charAt(0) == '*';

        //当pattern长度大于1时,只有当pattern是"***"形式的才可以匹配空字符串,并实时更新colExistTrue
        for(int j = 2; j <= pl; j++)
            colExistTrue[j] = dp[0][j] = p.charAt(j - 1) == '*' && dp[0][j - 1];
        
        char sc, pc;
        for(int i = 1; i <= sl; i++){
            sc = s.charAt(i - 1);
            for(int j = 1; j <= pl; j++){
                pc = p.charAt(j - 1);
                if(pc != '*')
                    dp[i][j] = isMatchChar(sc, pc) && dp[i - 1][j - 1];
                else
                    dp[i][j] = colExistTrue[j - 1];
                
                //dp[i][j]确定后,如果colExistTrue[j]为false,那么colExistTrue[j]有可能更新为true
                if(!colExistTrue[j])
                    colExistTrue[j] = dp[i][j];
            }
        }
        return dp[sl][pl];
    }

    private boolean isMatchChar(char s, char p){
        return p == '?' || s == p;
    }
}

3. 方法三:双指针+回溯

主要思路是定义 pIdx 和 sIdx 两个指针分别指向 p 和 s 的开头

如果 pIdx 指向的不是 '*' 且两个指针指向的字符能匹配,则两个指针都右移一格

如果 pIdx 指向了 '*' ,那么先假设星号匹配的是空字符串,如果后续没有出现不匹配,说明假设成立;否则继续假设星号匹配任一个字符;再有不匹配则假设星号匹配任意两个字符;以此类推

class Solution {
    public boolean isMatch(String s, String p) {
        int sLen = s.length(), pLen = p.length();

        //定义两个指针,sIdx指向s的开头,pIdx指向p的开头
        int sIdx = 0, pIdx = 0;

        //这两个值分别用于记录pIdx和sIdx,便于回溯
        int starIdx = -1, sTmpIdx = -1;
		
        while (sIdx < sLen) {
            
            //如果pIdx指向的字符不是'*'且能和sIdx指向的字符匹配,则两者都向右移动
            if (pIdx < pLen && (p.charAt(pIdx) == '?' || p.charAt(pIdx) == s.charAt(sIdx))) {
                ++sIdx;
                ++pIdx;
            
            //如果pIdx指向了'*',则假设该星号匹配的是空字符串,如果后面没出现问题,说明假设成立
            //此时记录当前的pIdx和sIdx,且只有pIdx右移
            } else if (pIdx < pLen && p.charAt(pIdx) == '*') {
                starIdx = pIdx;
                sTmpIdx = sIdx;
                ++pIdx;

            //==================如果不是以上两种情况,说明出现了不匹配现象==================

            //如果starIdx为-1,说明之前根本没遍历过'*',此时出现了不匹配,只能说明p和s不匹配
            } else if (starIdx == -1) {
                return false;
            
            //如果starIdx不是-1,且出现了不匹配,说明之前假设(星号匹配空字符串)有误
            //因此我们需要假设星号匹配任意一个字符,如果后面没出现问题,说明假设成立
            //因此pIdx回到星号后面的字符,同时sIdx回到之前记录位置的后一个位置,并将该位置记录下来
            //若之后发现匹配一个字符也不行,则继续执行这段代码,此时假设星号匹配任意两个字符,以此类推
            } else {
                pIdx = starIdx + 1;
                sIdx = sTmpIdx + 1;
                sTmpIdx = sIdx;
            }
        }

        //如果s已经遍历完了,但p还有剩余,那么剩余部分必须都是星号才说明匹配了
        for (int i = pIdx; i < pLen; i++) 
            if (p.charAt(i) != '*')
                return false;
        return true;
    }
}

 

你可能感兴趣的:(LeetCode,-,New)