【力扣算法】44-通配符匹配

题目

给定一个字符串 (s) 和一个字符模式 § ,实现一个支持 ‘?’ 和 ‘*’ 的通配符匹配。

‘?’ 可以匹配任何单个字符。
‘*’ 可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。

说明:

s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。
示例 1:

输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串。
示例 2:

输入:
s = “aa”
p = ""
输出: true
解释: '
’ 可以匹配任意字符串。
示例 3:

输入:
s = “cb”
p = “?a”
输出: false
解释: ‘?’ 可以匹配 ‘c’, 但第二个 ‘a’ 无法匹配 ‘b’。
示例 4:

输入:
s = “adceb”
p = “ab”
输出: true
解释: 第一个 ‘’ 可以匹配空字符串, 第二个 '’ 可以匹配字符串 “dce”.
示例 5:

输入:
s = “acdcb”
p = “a*c?b”
输入: false

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/wildcard-matching
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

无官方题解

感想

一开始没用回溯,碰了一鼻子灰,好不容易实现了能回溯的版本,又撞上一个特别长的用例1708 / 1809“abbabaaabbabbaababbabbbbbabbbabbbabaaaaababababbbabababaabbababaabbbbbbaaaabababbbaabbbbaabbbbababababbaabbaababaabbbababababbbbaaabbbbbabaaaabbababbbbaababaabbababbbbbababbbabaaaaaaaabbbbbaabaaababaaaabb”
“**aa*****ba*a*bb**aa*ab****a*aaaaaa***a*aaaa**bbabb*b*b**aaaaaaaaa*a********ba*bbb***a*ba*bb*bb**a*b*bb”
,让我意识到要用dp数组记录结果来剪枝(这应该算是动态规划记录法吧?不是很确定,毕竟自己没有利用状态转移方程。可能只是算剪枝吧)。发现得用这些,陆陆续续加上后代码却显得很丑了,而且巨慢。

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

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

class Solution {
    char[] sChars;
    char[] pChars;
    short[][] dp;//这个数组是用来记录isMatchChar的结果,来方便回溯剪枝的。一开始用short是因为以为可以存1代表成功,0代表未计算,-1代表失败。后来想想发现其实用boolean就行了,false代表未计算,true代表计算过失败的——成功的就直接return了,不需要存储。

    public boolean isMatch(String s, String p) {
        if (p == null || p.length() == 0) {
            if (s != null && s.length() > 0) return false;
            else return true;
        }
        pChars = p.toCharArray();
        if (s == null || s.length() == 0) {
            for (int i = 0; i < pChars.length; i++) {
                if (pChars[i] != '*') return false;
            }
            return true;
        }
        sChars = s.toCharArray();
        dp = new short[sChars.length][pChars.length];
        return isMatchChar(0, 0);
    }

    private boolean isMatchChar(int sIndex, int pIndex) {
        //System.out.println("isMatchChar():"+sIndex+","+pIndex);
        if (pIndex >= pChars.length) {
            if (sIndex < sChars.length) {
                return false;
            } else return true;
        }

        if (sIndex >= sChars.length) {
            for (int i = pIndex; i < pChars.length; i++) {
                if (pChars[i] != '*') return false;
            }
            return true;
        }
        if (dp[sIndex][pIndex] != 0) return dp[sIndex][pIndex] > 0;
        for (int i = pIndex; i < pChars.length; i++) {
            if (pChars[i] != '*') {
                if (i == pIndex) {
                    if (pChars[i] == '?' || sChars[sIndex] == pChars[i]) {
                        //System.out.println("匹配首个s:"+sChars[sIndex]+"和p:"+pChars[i]+",调用下一个");
                        if (dp[sIndex][pIndex] != 0) return dp[sIndex][pIndex] > 0;
                        else return isMatchChar(sIndex + 1, pIndex + 1);
                    } else {
                        dp[sIndex][pIndex] = -1;
                        return false;
                    }
                } else {
                    int count = 0;
                    int countQuestionMark = 0;
                    while (i + count < pChars.length && (pChars[i + count] == '?' || pChars[i + count] == '*')) {
                        if (pChars[i + count] == '?') countQuestionMark++;
                        count++;
                    }
                    if (i + count >= pChars.length) {
                        return sChars.length - sIndex >= countQuestionMark;
                    }
                    int index = findInSChars(pChars[i + count], sIndex + countQuestionMark);
                    while (index < countQuestionMark) {
                        if (index < 0) {
                            dp[sIndex][pIndex] = -1;
                            return false;
                        }
                        index = findInSChars(pChars[i + count], index + 1);
                    }
                    while (!isMatchChar(index + 1, i + count + 1)) {
                        if (index < 0) {
                            dp[sIndex][pIndex] = -1;
                            return false;
                        }
                        index = findInSChars(pChars[i + count], index + 1);
                        if (index < 0) {
                            dp[sIndex][pIndex] = -1;
                            return false;
                        }
                    }
                    return true;
                }
            }
        }
        return true;
    }

    private int findInSChars(char c, int start) {
        for (int i = start; i < sChars.length; i++) {
            if (sChars[i] == c) return i;
        }
        return -1;
    }
}

看了一下提交记录的各个时间范例,可以看到有几个区域分布人数比较多,对应的方法也比较典型:

首先是30ms~40ms,这个区域的典型做法是二维数组然后利用dp转移方程推出结果:

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

然后就是10ms左右的一枝独秀的大佬了:

看了一下提交的4ms范例,大致思路就是用双指针加回溯。回溯是通过startIndex实现的,之所以只用一个值就可以回溯,是因为这道题还有贪心算法的性质:第二个星号出现的时候,前一个星号的匹配就可以确定了,不会出现回溯到第一个星号的情况。

class Solution {
    public boolean isMatch(String s, String p) {
        // 指定双指针,sIndex指向s字符串,pIndex指向p字符串
        int sIndex = 0;
        int pIndex = 0;
        int match = 0;
        int startIndex = -1;
        // 从s字符串作为基准进行比对
        while (sIndex < s.length()){
            // 当pIndex位置的字符和sIndex位置的相同或者pIndex位置的为?时,驱动双指针向前移动一位
            if (pIndex < p.length()  && (p.charAt(pIndex) == '?' || s.charAt(sIndex) == p.charAt(pIndex))) {
                sIndex++;
                pIndex++;
            }
            // 当发现'*'时,对p指针直接移动到下一位,将'*'的index传给startIndex处理,同时将s指针传给match指针
            else if (pIndex < p.length() && p.charAt(pIndex) == '*') {
                startIndex = pIndex;
                match = sIndex;
                pIndex++;
            }
            // 当'*'匹配字符时,pIndex始终保持startIndex+1,即'*'的下一位,然后移动match指针和sIndex指针
            // 当出现s和p指针重新匹配时,会被第一个if语句拦截,移动双指针
            // 当移动到一个新的'*'时,在第二个else if重新更新sIndex
            // 可能会出现匹配短了的情况,比如匹配"adcbeb"和"*a*b",程序会在第一个b就直接跳出,但此时程序检查到后续不匹配的情况
            // 此时重新把startIndex+1赋给pIndex,相当于把第一个b包含进*中进行匹配,然后再出现下一个b时再匹配
            else if (startIndex != -1) {
                pIndex = startIndex + 1;
                match++;
                sIndex = match;
            }

            else
                return false;
        }

        // 检查p剩余的子串
        while (pIndex < p.length() && p.charAt(pIndex) == '*')
            pIndex++;

        return pIndex == p.length();
    }
}

可以看出自己的代码的最大差距在于dp备忘录如何优化——可以从二维数组优化到一维数组,甚至像最优代码那样再利用贪心算法的性质优化到一个值。。。动态规划的转移方程自己也是一直不怎么会推,看自己回溯和dp杂交的代码感觉自己怕不是学了个假的动态规划。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值